diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp index 7bc0bff93e..f36b1eb4c6 100644 --- a/Common/GPU/Vulkan/VulkanFramebuffer.cpp +++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp @@ -292,6 +292,11 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) { VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) { bool isBackbuffer = rpType == RenderPassType::BACKBUFFER; + if (isBackbuffer) { + _dbg_assert_(key.colorLoadAction != VKRRenderPassLoadAction::KEEP); + _dbg_assert_(key.depthLoadAction != VKRRenderPassLoadAction::KEEP); + _dbg_assert_(key.stencilLoadAction != VKRRenderPassLoadAction::KEEP); + } bool hasDepth = RenderPassTypeHasDepth(rpType); bool multiview = RenderPassTypeHasMultiView(rpType); bool multisample = RenderPassTypeHasMultisample(rpType); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index a376b8dcd8..4c26d78187 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -982,20 +982,27 @@ void VulkanRenderManager::EndCurRenderStep() { curPipelineFlags_ = (PipelineFlags)0; } -void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) { +void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction colorLoad, VKRRenderPassLoadAction depthLoad, VKRRenderPassLoadAction stencilLoad, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) { _dbg_assert_(insideFrame_); + if (!fb) { + // Backbuffer render passes have some requirements. + _dbg_assert_(colorLoad != VKRRenderPassLoadAction::KEEP); + _dbg_assert_(depthLoad != VKRRenderPassLoadAction::KEEP); + _dbg_assert_(stencilLoad != VKRRenderPassLoadAction::KEEP); + } + // Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible. if (!steps_.empty() && steps_.back()->stepType == VKRStepType::RENDER && steps_.back()->render.framebuffer == fb) { u32 clearMask = 0; - if (color == VKRRenderPassLoadAction::CLEAR) { + if (colorLoad == VKRRenderPassLoadAction::CLEAR) { clearMask |= VK_IMAGE_ASPECT_COLOR_BIT; } - if (depth == VKRRenderPassLoadAction::CLEAR) { + if (depthLoad == VKRRenderPassLoadAction::CLEAR) { clearMask |= VK_IMAGE_ASPECT_DEPTH_BIT; curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL; } - if (stencil == VKRRenderPassLoadAction::CLEAR) { + if (stencilLoad == VKRRenderPassLoadAction::CLEAR) { clearMask |= VK_IMAGE_ASPECT_STENCIL_BIT; curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL; } @@ -1057,25 +1064,25 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR // Older Mali drivers have issues with depth and stencil don't match load/clear/etc. // TODO: Determine which versions and do this only where necessary. u32 lateClearMask = 0; - if (depth != stencil && vulkan_->GetPhysicalDeviceProperties().properties.vendorID == VULKAN_VENDOR_ARM) { - if (stencil == VKRRenderPassLoadAction::DONT_CARE) { - stencil = depth; - } else if (depth == VKRRenderPassLoadAction::DONT_CARE) { - depth = stencil; - } else if (stencil == VKRRenderPassLoadAction::CLEAR) { - depth = stencil; + if (depthLoad != stencilLoad && vulkan_->GetPhysicalDeviceProperties().properties.vendorID == VULKAN_VENDOR_ARM) { + if (stencilLoad == VKRRenderPassLoadAction::DONT_CARE) { + stencilLoad = depthLoad; + } else if (depthLoad == VKRRenderPassLoadAction::DONT_CARE) { + depthLoad = stencilLoad; + } else if (stencilLoad == VKRRenderPassLoadAction::CLEAR) { + depthLoad = stencilLoad; lateClearMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } else if (depth == VKRRenderPassLoadAction::CLEAR) { - stencil = depth; + } else if (depthLoad == VKRRenderPassLoadAction::CLEAR) { + stencilLoad = depthLoad; lateClearMask |= VK_IMAGE_ASPECT_DEPTH_BIT; } } VKRStep *step = new VKRStep{ VKRStepType::RENDER }; step->render.framebuffer = fb; - step->render.colorLoad = color; - step->render.depthLoad = depth; - step->render.stencilLoad = stencil; + step->render.colorLoad = colorLoad; + step->render.depthLoad = depthLoad; + step->render.stencilLoad = stencilLoad; step->render.colorStore = VKRRenderPassStoreAction::STORE; step->render.depthStore = VKRRenderPassStoreAction::STORE; step->render.stencilStore = VKRRenderPassStoreAction::STORE; @@ -1092,7 +1099,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR if (fb) { // If there's a KEEP, we naturally read from the framebuffer. - if (color == VKRRenderPassLoadAction::KEEP || depth == VKRRenderPassLoadAction::KEEP || stencil == VKRRenderPassLoadAction::KEEP) { + if (colorLoad == VKRRenderPassLoadAction::KEEP || depthLoad == VKRRenderPassLoadAction::KEEP || stencilLoad == VKRRenderPassLoadAction::KEEP) { step->dependencies.insert(fb); } } @@ -1118,7 +1125,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR } } - if (color == VKRRenderPassLoadAction::CLEAR || depth == VKRRenderPassLoadAction::CLEAR || stencil == VKRRenderPassLoadAction::CLEAR) { + if (colorLoad == VKRRenderPassLoadAction::CLEAR || depthLoad == VKRRenderPassLoadAction::CLEAR || stencilLoad == VKRRenderPassLoadAction::CLEAR) { curRenderArea_.SetRect(0, 0, curWidth_, curHeight_); } diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index c7e93fbf82..d558bc6b95 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1839,6 +1839,8 @@ DataFormat VKContext::PreferredFramebufferReadbackFormat(Framebuffer *src) { } void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) { + _dbg_assert_(fbo != nullptr || equals(tag, "BackBuffer")) + VKFramebuffer *fb = (VKFramebuffer *)fbo; VKRRenderPassLoadAction color = (VKRRenderPassLoadAction)rp.color; VKRRenderPassLoadAction depth = (VKRRenderPassLoadAction)rp.depth; diff --git a/Common/System/OSD.cpp b/Common/System/OSD.cpp index f293a61849..6ce7500f9b 100644 --- a/Common/System/OSD.cpp +++ b/Common/System/OSD.cpp @@ -129,6 +129,16 @@ void OnScreenDisplay::Show(OSDType type, std::string_view text, std::string_view entries_.insert(entries_.begin(), msg); } +void OnScreenDisplay::CancelById(std::string_view id) { + for (auto iter = entries_.begin(); iter != entries_.end();) { + if (iter->id == id) { + iter = entries_.erase(iter); + } else { + iter++; + } + } +} + void OnScreenDisplay::ShowOnOff(std::string_view message, bool on, float duration_s) { std::string msg(message); msg += ": "; diff --git a/Common/System/OSD.h b/Common/System/OSD.h index 36e871c373..dd520a9949 100644 --- a/Common/System/OSD.h +++ b/Common/System/OSD.h @@ -61,6 +61,7 @@ public: void Show(OSDType type, std::string_view text, std::string_view text2, std::string_view icon, float duration_s = 0.0f, const char *id = nullptr); void ShowOnOff(std::string_view message, bool on, float duration_s = 0.0f); + void CancelById(std::string_view id); bool IsEmpty() const { return entries_.empty(); } // Shortcut to skip rendering. diff --git a/Common/UI/Screen.cpp b/Common/UI/Screen.cpp index 2c4407009f..4dc7e0aa90 100644 --- a/Common/UI/Screen.cpp +++ b/Common/UI/Screen.cpp @@ -209,7 +209,30 @@ void ScreenManager::resized() { } ScreenRenderFlags ScreenManager::render() { + using namespace Draw; + ScreenRenderFlags flags = ScreenRenderFlags::NONE; + + // First, go through the whole stack and have every screen render any non-backbuffer render passes. + // In EmuScreen, this might result in running emulation. + for (size_t i = 0; i < stack_.size(); i++) { + const auto &layer = stack_[i]; + ScreenRenderMode mode = ScreenRenderMode::DEFAULT; + if (i == stack_.size() - 1) { + mode |= ScreenRenderMode::TOP; + } + flags |= layer.screen->PreRender(mode); + } + + // Now, start the final render pass. This is now the ONLY place where binding the null fb is allowed. + draw_->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer"); + getUIContext()->BeginFrame(); + + const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f}; + draw_->SetViewport(viewport); + draw_->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); + draw_->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres); + if (!stack_.empty()) { // Collect the screens to render TinySet layers; diff --git a/Common/UI/Screen.h b/Common/UI/Screen.h index b7486e6e3f..9f9b7fd886 100644 --- a/Common/UI/Screen.h +++ b/Common/UI/Screen.h @@ -72,11 +72,12 @@ ENUM_CLASS_BITOPS(ScreenRenderRole); class Screen { public: - Screen() : screenManager_(nullptr) { } + Screen() = default; virtual ~Screen(); virtual void onFinish(DialogResult reason) {} virtual void update() {} + virtual ScreenRenderFlags PreRender(ScreenRenderMode mode) { return ScreenRenderFlags::NONE; } virtual ScreenRenderFlags render(ScreenRenderMode mode) = 0; virtual void resized() {} virtual void dialogFinished(const Screen *dialog, DialogResult result) {} @@ -114,7 +115,7 @@ protected: } private: - ScreenManager *screenManager_; + ScreenManager *screenManager_ = nullptr; int token_ = -1; DISALLOW_COPY_AND_ASSIGN(Screen); diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index a9d7c983b9..fbfc58d964 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -147,13 +147,6 @@ void UIScreen::update() { DoRecreateViews(); - if (root_) { - DialogResult result = UpdateViewHierarchy(root_); - if (result != DR_NONE) { - TriggerFinish(result); - } - } - while (true) { QueuedEvent ev{}; { @@ -188,6 +181,13 @@ void UIScreen::update() { break; } } + + if (root_) { + DialogResult result = UpdateViewHierarchy(root_); + if (result != DR_NONE) { + TriggerFinish(result); + } + } } void UIScreen::deviceLost() { @@ -200,30 +200,7 @@ void UIScreen::deviceRestored(Draw::DrawContext *draw) { root_->DeviceRestored(draw); } -void UIScreen::SetupViewport() { - using namespace Draw; - Draw::DrawContext *draw = screenManager()->getDrawContext(); - _dbg_assert_(draw != nullptr); - // Bind and clear the back buffer - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "UI"); - screenManager()->getUIContext()->BeginFrame(); - - Draw::Viewport viewport; - viewport.TopLeftX = 0; - viewport.TopLeftY = 0; - viewport.Width = g_display.pixel_xres; - viewport.Height = g_display.pixel_yres; - viewport.MaxDepth = 1.0; - viewport.MinDepth = 0.0; - draw->SetViewport(viewport); - draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres); -} - ScreenRenderFlags UIScreen::render(ScreenRenderMode mode) { - if (mode & ScreenRenderMode::FIRST) { - SetupViewport(); - } - DoRecreateViews(); UIContext &uiContext = *screenManager()->getUIContext(); diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index c6b55cc679..3127464d42 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -77,7 +77,6 @@ protected: virtual void DrawBackground(UIContext &ui) {} virtual void DrawForeground(UIContext &ui) {} - void SetupViewport(); void DoRecreateViews(); bool recreateViews_ = true; diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 75fe18ba44..d2d7f30ae7 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -68,7 +68,6 @@ void Event::Add(std::function func) { func_ = func; } -// Call this from input thread or whatever, it doesn't matter void Event::Trigger(EventParams &e) { if (!func_) { return; @@ -76,7 +75,6 @@ void Event::Trigger(EventParams &e) { EventTriggered(this, e); } -// Call this from UI thread void Event::Dispatch(EventParams &e) { if (func_) func_(e); diff --git a/Common/UI/View.h b/Common/UI/View.h index 92b0717e36..8c1576964b 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -237,7 +237,7 @@ typedef std::function EventCallback; class Event { public: - Event() {} + Event() = default; ~Event(); // Call this from input thread or whatever, it doesn't matter void Trigger(EventParams &e); diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index 1bad784e62..841badbc7f 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -321,17 +321,17 @@ float GetTargetScore(const Point2D &originPos, int originIndex, const View *orig distance = 0.001f; } float overlap = 0.0f; - float dirX = dx / distance; - float dirY = dy / distance; + const float dirX = dx / distance; + const float dirY = dy / distance; bool wrongDirection = false; bool vertical = false; - float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds()); - float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds()); + const float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds()); + const float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds()); if (horizOverlap == 1.0f && vertOverlap == 1.0f) { if (direction != FOCUS_PREV_PAGE && direction != FOCUS_NEXT_PAGE) { - INFO_LOG(Log::UI, "Contain overlap"); - return 0.0; + INFO_LOG(Log::UI, "Contain overlap: %s, %s", origin->Tag().c_str(), destination->Tag().c_str()); + return 0.0f; } } float originSize = 0.0f; diff --git a/Core/Config.h b/Core/Config.h index 46faa3a45c..ce1dfc1c5d 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -90,7 +90,7 @@ struct DisplayLayoutConfig : public ConfigBlock { int iCardboardScreenSize = 50; // Screen Size (in %) int iCardboardXShift = 0; // X-Shift of Screen (in %) int iCardboardYShift = 0; // Y-Shift of Screen (in %) - bool bImmersiveMode = true; // Mode on Android Kitkat 4.4 and later that hides the back button etc. + bool bImmersiveMode = false; // Mode on Android Kitkat 4.4 and later that hides the back button etc. bool InternalRotationIsPortrait() const; bool CanResetToDefault() const override { return true; } diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 56669862eb..b0891ea50f 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -662,7 +662,6 @@ void __DisplayFlip(int cyclesLate) { } if (nextFrame) { gpu->SetCurFramebufferDirty(fbReallyDirty); - gpu->CopyDisplayToOutput(g_displayLayoutConfigCached); if (fbReallyDirty) { DisplayFireActualFlip(); } diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 3fa47a1c87..33951ad497 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -77,7 +77,6 @@ double g_lastSaveTime = -1.0; static bool needsProcess = false; static bool needsRestart = false; static std::mutex mutex; -static int screenshotFailures = 0; static bool hasLoadedState = false; static const int STALE_STATE_USES = 2; // 4 hours of total gameplay since the virtual PSP started the game. @@ -102,21 +101,25 @@ enum class OperationType { Load, Verify, Rewind, - SaveScreenshot, }; - struct Operation { - // The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example. - Operation(OperationType t, const Path &f, int slot_, Callback cb) - : type(t), filename(f), callback(cb), slot(slot_) {} +struct Operation { + // The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example. + Operation(OperationType t, const Path &f, int slot_, Callback cb) + : type(t), filename(f), callback(cb), slot(slot_) {} - OperationType type; - Path filename; - Callback callback; - int slot; - }; + OperationType type; + Path filename; + Callback callback; + int slot; +}; - static std::vector pending; +static std::vector g_pendingOperations; + +// If this isn't empty, a screenshot operation is pending. It's protected by mutex. +Path g_screenshotPath; + +int g_screenshotFailures; CChunkFileReader::Error SaveToRam(std::vector &data) { SaveStart state; @@ -202,7 +205,7 @@ enum class OperationType { return; } if (Achievements::HardcoreModeActive()) { - if (g_Config.bAchievementsSaveStateInHardcoreMode && ((op.type == SaveState::OperationType::Save) || (op.type == OperationType::SaveScreenshot))) { + if (g_Config.bAchievementsSaveStateInHardcoreMode && ((op.type == SaveState::OperationType::Save))) { // We allow saving in hardcore mode if this setting is on. } else { // Operation not allowed @@ -211,7 +214,7 @@ enum class OperationType { } std::lock_guard guard(mutex); - pending.push_back(op); + g_pendingOperations.push_back(op); // Don't actually run it until next frame. // It's possible there might be a duplicate but it won't hurt us. @@ -253,11 +256,6 @@ enum class OperationType { Enqueue(Operation(OperationType::Rewind, Path(), -1, callback)); } - static void SaveScreenshot(const Path &filename) { - screenshotFailures = 0; - Enqueue(Operation(OperationType::SaveScreenshot, filename, -1, nullptr)); - } - bool CanRewind() { return !rewindStates.Empty(); } @@ -383,6 +381,12 @@ enum class OperationType { } } + static void ScheduleSaveScreenshot(const Path &path) { + std::lock_guard guard(mutex); + g_screenshotPath = path; + g_screenshotFailures = 0; + } + void LoadSlot(std::string_view gamePrefix, int slot, Callback callback) { if (!NetworkAllowSaveState()) { return; @@ -481,7 +485,7 @@ enum class OperationType { DeleteIfExists(shotUndo); RenameIfExists(shot, shotUndo); } - SaveScreenshot(shot); + ScheduleSaveScreenshot(shot); Save(fn.WithExtraExtension(".tmp"), slot, renameCallback); } else { if (callback) { @@ -686,8 +690,8 @@ enum class OperationType { std::vector Flush() { std::lock_guard guard(mutex); - std::vector copy = pending; - pending.clear(); + std::vector copy = g_pendingOperations; + g_pendingOperations.clear(); return copy; } @@ -772,23 +776,21 @@ enum class OperationType { // NOTE: This can cause ending of the current renderpass, due to the readback needed for the screenshot. // TODO: This should run the actual operations on a thread. While this returns true (for example), emulation // *must* not run further, in order not to disturb the current state operation. - bool Process() { + void Process() { rewindStates.Process(); if (!needsProcess) - return false; + return; needsProcess = false; if (!__KernelIsRunning()) { ERROR_LOG(Log::SaveState, "Savestate failure: Unable to load without kernel, this should never happen."); - return false; + return; } std::vector operations = Flush(); SaveStart state; - bool readbackImage = false; - for (const auto &op : operations) { CChunkFileReader::Error result; Status callbackResult; @@ -913,40 +915,6 @@ enum class OperationType { } break; - case OperationType::SaveScreenshot: - { - _dbg_assert_(!op.callback); - - int maxResMultiplier = 2; - ScreenshotResult tempResult = TakeGameScreenshot(nullptr, op.filename, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, maxResMultiplier, [](bool success) { - if (success) { - screenshotFailures = 0; - } - }); - - switch (tempResult) { - case ScreenshotResult::ScreenshotNotPossible: - // Try again soon, for a short while. - callbackResult = Status::FAILURE; - WARN_LOG(Log::SaveState, "Failed to take a screenshot for the savestate! (%s) The savestate will lack an icon.", op.filename.c_str()); - if (coreState != CORE_STEPPING_CPU && screenshotFailures++ < SCREENSHOT_FAILURE_RETRIES) { - // Requeue for next frame (if we were stepping, no point, will just spam errors quickly). - SaveScreenshot(op.filename); - } - break; - case ScreenshotResult::DelayedResult: - case ScreenshotResult::Success: - // We might not know if the file write succeeded yet though. - callbackResult = Status::SUCCESS; - readbackImage = true; - break; - case ScreenshotResult::FailedToWriteFile: - // Can't reach here when we pass in a callback to TakeGameScreenshot. - callbackResult = Status::SUCCESS; - break; - } - break; - } default: ERROR_LOG(Log::SaveState, "Savestate failure: unknown operation type %d", (int)op.type); callbackResult = Status::FAILURE; @@ -961,8 +929,6 @@ enum class OperationType { // Avoid triggering frame skipping due to slowdown __DisplaySetWasPaused(); } - - return readbackImage; } void NotifySaveData() { @@ -1006,4 +972,44 @@ enum class OperationType { return time_now_d() - g_lastSaveTime; } } + +bool ProcessScreenshot(bool skipBufferEffects) { + Path screenshotPath; + { + std::lock_guard guard(mutex); + if (!g_screenshotPath.empty()) { + screenshotPath = g_screenshotPath; + g_screenshotPath.clear(); + } else { + return false; + } + } + + // Savestate thumbnails don't need to be bigger. + constexpr int maxResMultiplier = 2; + ScreenshotResult tempResult = TakeGameScreenshot(nullptr, screenshotPath, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, maxResMultiplier, [](bool success) { + if (success) { + g_screenshotFailures = 0; + } + }); + + switch (tempResult) { + case ScreenshotResult::ScreenshotNotPossible: + // Try again soon, for a short while. + WARN_LOG(Log::SaveState, "Failed to take a screenshot for the savestate! (%s) The savestate will lack an icon.", g_screenshotPath.c_str()); + if (coreState != CORE_STEPPING_CPU && g_screenshotFailures++ < SCREENSHOT_FAILURE_RETRIES) { + // Requeue for next frame (if we were stepping, no point, will just spam errors quickly). + ScheduleSaveScreenshot(g_screenshotPath); + } + break; + case ScreenshotResult::FailedToWriteFile: + break; + case ScreenshotResult::DelayedResult: + return true; + case ScreenshotResult::Success: + return true; + } + return false; // Didn't take a screenshot right now. } + +} // namespace SaveState diff --git a/Core/SaveState.h b/Core/SaveState.h index 8dcdd0fbc6..89d296f68c 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -110,7 +110,11 @@ namespace SaveState { bool IsOldVersion(); // Check if there's any save stating needing to be done. Normally called once per frame. - bool Process(); + void Process(); + + // Separate function to just process screenshots, as they need to be done at a specific time in a frame. + // Returns true if a screenshot was taken. + bool ProcessScreenshot(bool skipBufferEffects); // Notify save state code that new save data has been written. void NotifySaveData(); diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index 2788c91516..282b9cc4ad 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -325,8 +325,13 @@ ScreenshotResult TakeGameScreenshot(Draw::DrawContext *draw, const Path &filenam if (!gpuDebug->GetCurrentFramebuffer(buf, type == SCREENSHOT_RENDER ? GPU_DBG_FRAMEBUF_RENDER : GPU_DBG_FRAMEBUF_DISPLAY, maxRes)) { return ScreenshotResult::ScreenshotNotPossible; } - w = maxRes > 0 ? 480 * maxRes : PSP_CoreParameter().renderWidth; - h = maxRes > 0 ? 272 * maxRes : PSP_CoreParameter().renderHeight; + if (buf.IsBackBuffer()) { + w = buf.GetStride(); + h = buf.GetHeight(); + } else { + w = maxRes > 0 ? 480 * maxRes : buf.GetStride(); + h = maxRes > 0 ? 272 * maxRes : buf.GetHeight(); + } } else if (g_display.rotation != DisplayRotation::ROTATE_0) { _dbg_assert_(draw); GPUDebugBuffer temp; @@ -343,12 +348,42 @@ ScreenshotResult TakeGameScreenshot(Draw::DrawContext *draw, const Path &filenam if (callback) { g_threadManager.EnqueueTask(new IndependentTask(TaskType::IO_BLOCKING, TaskPriority::LOW, - [buf = std::move(buf), callback = std::move(callback), filename, fmt, w, h]() { + [buf = std::move(buf), callback = std::move(callback), filename, fmt, w, h, maxRes]() { u8 *flipbuffer = nullptr; u32 width = w, height = h; const u8 *buffer = ConvertBufferToScreenshot(buf, false, flipbuffer, width, height); - bool success = Save888RGBScreenshot(filename, fmt, buffer, width, height); - delete[] flipbuffer; + + bool success; + if (width <= 480 * maxRes) { + success = Save888RGBScreenshot(filename, fmt, buffer, width, height); + delete[] flipbuffer; + } else { + u8 *shrinkBuffer = new u8[width * height * 3]; + memcpy(shrinkBuffer, buffer, width * height * 3); + delete[] flipbuffer; + + // TODO: Speed this thing up. + while (width > 480 * maxRes) { + u8 *halfSize = new u8[(width / 2) * (height / 2) * 3]; + for (int y = 0; y < height / 2; y++) { + for (int x = 0; x < width / 2; x++) { + for (int c = 0; c < 3; c++) { + halfSize[(y * (width / 2) + x) * 3 + c] = + (shrinkBuffer[((y * 2) * width + (x * 2)) * 3 + c] + + shrinkBuffer[((y * 2) * width + (x * 2 + 1)) * 3 + c] + + shrinkBuffer[(((y * 2) + 1) * width + (x * 2)) * 3 + c] + + shrinkBuffer[(((y * 2) + 1) * width + (x * 2 + 1)) * 3 + c]) / 4; + } + } + } + std::swap(shrinkBuffer, halfSize); + delete[] halfSize; + width /= 2; + height /= 2; + } + success = Save888RGBScreenshot(filename, fmt, shrinkBuffer, width, height); + } + System_RunOnMainThread([success, callback = std::move(callback)]() { callback(success); }); diff --git a/Core/System.cpp b/Core/System.cpp index 58be4adb24..f120df9d9c 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -252,17 +252,23 @@ static void ShowCompatWarnings(const Compatibility &compat) { // UI changes are best done after PSP_InitStart. if (compat.flags().RequireBufferedRendering && g_Config.bSkipBufferEffects && !g_Config.bSoftwareRendering) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); - g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 10.0f); + g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 10.0f, "bufreq"); + } else { + g_OSD.CancelById("bufreq"); } if (compat.flags().RequireBlockTransfer && g_Config.iSkipGPUReadbackMode != (int)SkipGPUReadbackMode::NO_SKIP && !PSP_CoreParameter().compat.flags().ForceEnableGPUReadback) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); - g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BlockTransferRequired", "Warning: This game requires Skip GPU Readbacks be set to No."), 10.0f); + g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BlockTransferRequired", "Warning: This game requires Skip GPU Readbacks be set to No."), 10.0f, "blockxfer"); + } else { + g_OSD.CancelById("blockxfer"); } if (compat.flags().RequireDefaultCPUClock && g_Config.iLockedCPUSpeed != 0) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); - g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f); + g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f, "defaultclock"); + } else { + g_OSD.CancelById("defaultclock"); } } diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index b430409319..93e3055bb3 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1559,9 +1559,11 @@ bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig constexpr float u0 = 0.0f, u1 = 480.0f / 512.0f; constexpr float v0 = 0.0f, v1 = 1.0f; - presentation_->UpdateUniforms(textureCache_->VideoIsPlaying()); - presentation_->SourceTexture(pixelsTex, 512, 272); - presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1); + if (useBufferedRendering_) { + presentation_->UpdateUniforms(textureCache_->VideoIsPlaying()); + presentation_->SourceTexture(pixelsTex, 512, 272); + presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1); + } // PresentationCommon sets all kinds of state, we can't rely on anything. gstate_c.Dirty(DIRTY_ALL); @@ -1576,7 +1578,19 @@ void FramebufferManagerCommon::SetViewport2D(int x, int y, int w, int h) { draw_->SetViewport(viewport); } -void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) { +void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &config) { + // PresentationCommon sets all kinds of state, we can't rely on anything. + gstate_c.Dirty(DIRTY_ALL); + DiscardFramebufferCopy(); + currentRenderVfb_ = nullptr; + if (useBufferedRendering_) { + presentation_->CopyToOutput(config); + } else { + presentation_->NotifyPresent(); + } +} + +void FramebufferManagerCommon::PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) { DownloadFramebufferOnSwitch(currentRenderVfb_); shaderManager_->DirtyLastShader(); @@ -1588,11 +1602,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co DEBUG_LOG(Log::FrameBuf, "Display disabled, displaying only black"); } // No framebuffer to display! Clear to black. - if (useBufferedRendering_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "CopyDisplayToOutput"); - } - gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); - presentation_->NotifyPresent(); + presentation_->SourceBlank(); return; } @@ -1651,27 +1661,19 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co if (Memory::IsValidAddress(fbaddr)) { // The game is displaying something directly from RAM. In GTA, it's decoded video. // If successful, this effectively calls presentation_->NotifyPresent(); - if (DrawFramebufferToOutput(config, Memory::GetPointerUnchecked(fbaddr), displayStride_, displayFormat_)) { - presentation_->CopyToOutput(config); - } else { - if (useBufferedRendering_) { - // Bind and clear the backbuffer. This should be the first time during the frame that it's bound. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "CopyDisplayToOutput_DrawError"); - } - presentation_->NotifyPresent(); + if (!DrawFramebufferToOutput(config, Memory::GetPointerUnchecked(fbaddr), displayStride_, displayFormat_)) { + // No framebuffer to display! Clear to black. + presentation_->SourceBlank(); } - return; } else { DEBUG_LOG(Log::FrameBuf, "Found no FBO to display! displayFBPtr = %08x", fbaddr); // No framebuffer to display! Clear to black. - if (useBufferedRendering_) { - // Bind and clear the backbuffer. This should be the first time during the frame that it's bound. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "CopyDisplayToOutput_NoFBO"); - } // For non-buffered rendering, every frame is cleared anyway. + // TODO: Draw a black rectangle, will be important once we add backgrounds. gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); - presentation_->NotifyPresent(); - return; + // No framebuffer to display! Clear to black. + presentation_->SourceBlank(); } + return; } vfb->usageFlags |= FB_USAGE_DISPLAYED_FRAMEBUFFER; @@ -1688,6 +1690,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co displayFramebuf_ = vfb; if (vfb->fbo) { + _dbg_assert_(useBufferedRendering_); if (GetUIState() != UISTATE_PAUSEMENU) { if (Core_IsStepping()) VERBOSE_LOG(Log::FrameBuf, "Displaying FBO %08x", vfb->fb_address); @@ -1732,20 +1735,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co presentation_->UpdateUniforms(textureCache_->VideoIsPlaying()); presentation_->SourceFramebuffer(vfb->fbo, actualWidth, actualHeight); presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1); - presentation_->CopyToOutput(config); - } else if (useBufferedRendering_) { - WARN_LOG(Log::FrameBuf, "Using buffered rendering, and current VFB lacks an FBO: %08x", vfb->fb_address); - } else { - // This is OK because here we're in "skip buffered" mode, so even if we haven't presented - // we will have a render target. - presentation_->NotifyPresent(); } - - // This may get called mid-draw if the game uses an immediate flip. - // PresentationCommon sets all kinds of state, we can't rely on anything. - gstate_c.Dirty(DIRTY_ALL); - DiscardFramebufferCopy(); - currentRenderVfb_ = nullptr; } void FramebufferManagerCommon::DecimateFBOs() { @@ -3035,9 +3025,10 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB } if (!useBufferedRendering_) { - // Safety check. - w = std::min(w, PSP_CoreParameter().pixelWidth); - h = std::min(h, PSP_CoreParameter().pixelHeight); + // In this mode, we can only screenshot the backbuffer, and we can't resize with a simple blit (well technically we could, but complicated) + w = PSP_CoreParameter().pixelWidth; + h = PSP_CoreParameter().pixelHeight; + buffer.SetIsBackbuffer(true); } // TODO: Maybe should handle flipY inside CopyFramebufferToMemorySync somehow? @@ -3448,10 +3439,6 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag) { if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. - if (useBufferedRendering_) { - // Just bind the back buffer for rendering, forget about doing anything else as we're in a weird state. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "BlitFramebuffer"); - } return; } diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 2ca89b86a4..c0367ad365 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -334,7 +334,8 @@ public: void RebindFramebuffer(const char *tag); std::vector GetFramebufferList() const; - void CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty); + void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty); + void CopyDisplayToOutput(const DisplayLayoutConfig &config); bool NotifyFramebufferCopy(u32 src, u32 dest, int size, GPUCopyFlag flags, u32 skipDrawReason); void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat fmt); diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index 1a87c4270b..5e8b2b7ade 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -182,6 +182,9 @@ struct GPUDebugBuffer { u32 PixelSize() const; + void SetIsBackbuffer(bool isBackBuffer) { isBackBuffer_ = isBackBuffer; } + bool IsBackBuffer() const { return isBackBuffer_; } + private: bool alloc_ = false; u8 *data_ = nullptr; @@ -189,6 +192,7 @@ private: u32 height_ = 0; GPUDebugBufferFormat fmt_ = GPU_DBG_FORMAT_INVALID; bool flipped_ = false; + bool isBackBuffer_ = false; }; struct GPUDebugVertex { diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 6842c49cc2..045d8dbd23 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -599,6 +599,15 @@ Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, S return draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader"); } +void PresentationCommon::SourceBlank() { + DoRelease(srcTexture_); + DoRelease(srcFramebuffer_); + + srcWidth_ = 0; + srcHeight_ = 0; +} + +// If texture == null, that means there's nothing to display, so we should show a black screen in CopyToOutput. void PresentationCommon::SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight) { // AddRef before release and assign in case it's the same. texture->AddRef(); @@ -682,6 +691,11 @@ void PresentationCommon::RunPostshaderPasses(const DisplayLayoutConfig &config, } CalculateDisplayOutputRect(config, &rc_, 480.0f, 272.0f, frame, uvRotation); + if (!srcTexture_ && !srcFramebuffer_) { + // Presenting blank, no need to run post shaders. But we did compute the output rect. + return; + } + // To make buffer updates easier, we use one array of verts. int postVertsOffset = (int)sizeof(Vertex) * 4; @@ -884,9 +898,14 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config) { int lastWidth = srcWidth_; int lastHeight = srcHeight_; - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "FinalBlit"); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); + if (!srcFramebuffer_ && !srcTexture_) { + // Bound blank. We're done (although we could draw a black rectangle here). + presentedThisFrame_ = true; + return; + } + Draw::Pipeline *pipeline = (outputFlags_ & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_; if (useStereo) { diff --git a/GPU/Common/PresentationCommon.h b/GPU/Common/PresentationCommon.h index d3cc1dad47..8a0f55ab3b 100644 --- a/GPU/Common/PresentationCommon.h +++ b/GPU/Common/PresentationCommon.h @@ -124,6 +124,9 @@ public: void DeviceRestore(Draw::DrawContext *draw); void UpdateUniforms(bool hasVideo); + + // One of these must be called every frame. + void SourceBlank(); void SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight); void SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight); diff --git a/GPU/Common/VertexDecoderHandwritten.cpp b/GPU/Common/VertexDecoderHandwritten.cpp index 0b3fbc795b..13bfe4aeb5 100644 --- a/GPU/Common/VertexDecoderHandwritten.cpp +++ b/GPU/Common/VertexDecoderHandwritten.cpp @@ -163,7 +163,6 @@ void VtxDec_Tu8_C5551_Ps16(const u8 *srcp, u8 *dstp, int numVerts, const UVScale __m128i lowbits = _mm_set1_epi32(0x00070707); // Two vertices at a time, we can share some calculations. - // It's OK to accidentally decode an extra vertex. while (count >= 2) { __m128i pos0 = _mm_loadl_epi64((const __m128i *) & src[0].x); __m128i pos1 = _mm_loadl_epi64((const __m128i *) & src[1].x); @@ -181,7 +180,7 @@ void VtxDec_Tu8_C5551_Ps16(const u8 *srcp, u8 *dstp, int numVerts, const UVScale __m128d uvf = _mm_castps_pd(_mm_add_ps(_mm_mul_ps(_mm_cvtepi32_ps(uv32), uvScale), uvOff)); alpha &= col0; - // Combined RGBA + // Combined 5551 -> 8888 RGBA. Nasty. __m128i col = _mm_set1_epi64x(col0); __m128i r = _mm_slli_epi32(_mm_and_si128(col, rmask), 8 - 5); __m128i g = _mm_slli_epi32(_mm_and_si128(col, gmask), 16 - 10); @@ -224,7 +223,6 @@ void VtxDec_Tu8_C5551_Ps16(const u8 *srcp, u8 *dstp, int numVerts, const UVScale uint32x2_t lowbits = vdup_n_u32(0x00070707); // Two vertices at a time, we can share some calculations. - // It's OK to accidentally decode an extra vertex. // Doing four vertices at a time might be even better, can share more of the pesky color format conversion. while (count >= 2) { int16x4_t pos0 = vld1_s16(&src[0].x); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 49d185caa6..b4349e3437 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -125,6 +125,7 @@ public: virtual void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) = 0; virtual void SetCurFramebufferDirty(bool dirty) = 0; + virtual void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) = 0; virtual void CopyDisplayToOutput(const DisplayLayoutConfig &config) = 0; virtual bool PresentedThisFrame() const = 0; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index f2f1c199d1..042fd3a16a 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -530,7 +530,7 @@ void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } -void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) { +void GPUCommonHW::PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) { drawEngineCommon_->FlushQueuedDepth(); // Flush anything left over. drawEngineCommon_->Flush(); @@ -538,10 +538,12 @@ void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) { shaderManager_->DirtyLastShader(); // after this, render pass is active. - framebufferManager_->CopyDisplayToOutput(config, curFramebufferDirty_); - curFramebufferDirty_ = false; + framebufferManager_->PrepareCopyDisplayToOutput(config, curFramebufferDirty_); +} - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); +void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) { + framebufferManager_->CopyDisplayToOutput(config); + curFramebufferDirty_ = false; } bool GPUCommonHW::PresentedThisFrame() const { diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index f2179e6b9a..6b1032c91e 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -11,6 +11,7 @@ public: // This can fail, and if so no render pass is active. void SetCurFramebufferDirty(bool dirty) override { curFramebufferDirty_ = dirty; } + void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) override; void CopyDisplayToOutput(const DisplayLayoutConfig &config) override; void DoState(PointerWrap &p) override; void DeviceLost() override; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index f8f9c9b7a2..39b943c972 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -624,7 +624,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(const DisplayLayoutConfig &config, u1 = 1.0f; } if (!hasImage) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "CopyToCurrentFboFromDisplayRam"); + presentation_->SourceBlank(); presentation_->NotifyPresent(); return; } @@ -644,13 +644,16 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(const DisplayLayoutConfig &config, presentation_->SourceTexture(fbTex, desc.width, desc.height); presentation_->RunPostshaderPasses(config, outputFlags, config.iInternalScreenRotation, u0, v0, u1, v1); - presentation_->CopyToOutput(config); } -void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) { +void SoftGPU::PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) { drawEngine_->transformUnit.Flush(this, "output"); // The display always shows 480x272. CopyToCurrentFboFromDisplayRam(config, FB_WIDTH, FB_HEIGHT); +} + +void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) { + presentation_->CopyToOutput(config); MarkDirty(displayFramebuf_, displayStride_, 272, displayFormat_, SoftGPUVRAMDirty::CLEAR); } diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 8c276d304c..0cd5182cf9 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -138,6 +138,7 @@ public: void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void SetCurFramebufferDirty(bool dirty) override {} + void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) override; void CopyDisplayToOutput(const DisplayLayoutConfig &config) override; void GetStats(char *buffer, size_t bufsize) override; std::vector GetFramebufferList() const override { return std::vector(); } diff --git a/UI/Background.cpp b/UI/Background.cpp index b4bfac0629..dc8732d537 100644 --- a/UI/Background.cpp +++ b/UI/Background.cpp @@ -446,7 +446,7 @@ void DrawBackground(UIContext &dc, float alpha, Lin::Vec3 focus) { } uint32_t GetBackgroundColorWithAlpha(const UIContext &dc) { - return colorAlpha(colorBlend(dc.GetTheme().backgroundColor, 0, 0.5f), 0.65f); // 0.65 = 166 = A6 + return colorAlpha(colorBlend(dc.GetTheme().backgroundColor, 0, 0.5f), 0.72f); // 0.72 = 183 = B7 } enum class BackgroundFillMode { diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 00a225e528..25cb64cbcb 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -93,6 +93,7 @@ using namespace std::placeholders; #include "UI/ControlMappingScreen.h" #include "UI/DisplayLayoutScreen.h" #include "UI/GameSettingsScreen.h" +#include "UI/MiscViews.h" #include "UI/ProfilerDraw.h" #include "UI/DiscordIntegration.h" #include "UI/ChatScreen.h" @@ -286,7 +287,7 @@ void EmuScreen::ProcessGameBoot(const Path &filename) { g_OSD.Show(OSDType::MESSAGE_WARNING, "Shader cache is disabled (developer)"); } - if (g_Config.bTiltInputEnabled && g_Config.iTiltInputType != 0) { + if (g_Config.bTiltInputEnabled && g_Config.iTiltInputType != 0 && System_GetPropertyBool(SYSPROP_HAS_ACCELEROMETER)) { auto co = GetI18NCategory(I18NCat::CONTROLS); auto di = GetI18NCategory(I18NCat::DIALOG); g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions("%1: %2", co->T("Tilt control"), di->T("Enabled")), "", "I_CONTROLLER", 2.5f, "tilt"); @@ -1231,52 +1232,6 @@ void EmuScreen::touch(const TouchInput &touch) { } } -class GameInfoBGView : public UI::InertView { -public: - GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {} - - void Draw(UIContext &dc) override { - // Should only be called when visible. - std::shared_ptr ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::PIC1); - dc.Flush(); - - // PIC1 is the loading image, so let's only draw if it's available. - if (ginfo->Ready(GameInfoFlags::PIC1) && ginfo->pic1.texture) { - Draw::Texture *texture = ginfo->pic1.texture; - if (texture) { - const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()); - // Similar to presentation, we want to put the game PIC1 in the same region of the screen. - FRect frame = GetScreenFrame(config.bIgnoreScreenInsets, g_display.pixel_xres, g_display.pixel_yres); - FRect rc; - CalculateDisplayOutputRect(config, &rc, texture->Width(), texture->Height(), frame, config.iInternalScreenRotation); - - // Need to adjust for DPI here since we're still in the UI coordinate space here, not the pixel coordinate space used for in-game presentation. - Bounds bounds(rc.x * g_display.dpi_scale_x, rc.y * g_display.dpi_scale_y, rc.w * g_display.dpi_scale_x, rc.h * g_display.dpi_scale_y); - - dc.GetDrawContext()->BindTexture(0, texture); - - double loadTime = ginfo->pic1.timeLoaded; - uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3)); - dc.Draw()->DrawTexRect(bounds, 0, 0, 1, 1, color); - dc.Flush(); - dc.RebindTexture(); - } - } - } - - std::string DescribeText() const override { - return ""; - } - - void SetColor(uint32_t c) { - color_ = c; - } - -protected: - Path gamePath_; - uint32_t color_ = 0xFFC0C0C0; -}; - // TODO: Shouldn't actually need bounds for this, Anchor can center too. static UI::AnchorLayoutParams *AnchorInCorner(const Bounds &bounds, int corner, float xOffset, float yOffset) { using namespace UI; @@ -1567,7 +1522,7 @@ bool EmuScreen::checkPowerDown() { ScreenRenderRole EmuScreen::renderRole(bool isTop) const { auto CanBeBackground = [&]() -> bool { - if (g_Config.bSkipBufferEffects) { + if (skipBufferEffects_) { return isTop || (g_Config.bTransparentBackground && ShouldRunBehind()); } @@ -1627,14 +1582,37 @@ void EmuScreen::HandleFlip() { #endif } +bool EmuScreen::ShouldRunEmulation(ScreenRenderMode mode) const { + if (!(mode & ScreenRenderMode::TOP) && !ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) { + return false; + } + return true; +} + +ScreenRenderFlags EmuScreen::PreRender(ScreenRenderMode mode) { + // If a boot is in progress, update it. + ProcessGameBoot(gamePath_); + + using namespace Draw; + skipBufferEffects_ = g_Config.bSkipBufferEffects; + if (!skipBufferEffects_) { + if (ShouldRunEmulation(mode)) { + // We need to run emulation here, and perform all the normal render passes. + return RunEmulation(false); + } else { + const DeviceOrientation orientation = GetDeviceOrientation(); + const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation); + // We run just the post shaders. + gpu->PrepareCopyDisplayToOutput(displayLayoutConfig); + } + } + return ScreenRenderFlags::NONE; +} + ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { // Moved from update, because we want it to be possible for booting to happen even when the screen // is in the background, like when choosing Reset from the pause menu. - // If a boot is in progress, update it. - ProcessGameBoot(gamePath_); - - const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f}; using namespace Draw; DrawContext *draw = screenManager()->getDrawContext(); @@ -1642,81 +1620,21 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { return ScreenRenderFlags::NONE; // shouldn't really happen but I've seen a suspicious stack trace.. } - ProcessQueuedVKeys(); - - const bool skipBufferEffects = g_Config.bSkipBufferEffects; - - bool framebufferBound = false; - - if (mode & ScreenRenderMode::FIRST) { - // Actually, always gonna be first when it exists (?) - - // Here we do NOT bind the backbuffer or clear the screen, unless non-buffered. - // The emuscreen is different than the others - we really want to allow the game to render to framebuffers - // before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render - // targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here. - // We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)... - // We do, however, start the frame in other ways. - - if (skipBufferEffects && !g_Config.bSoftwareRendering) { - // We need to clear here already so that drawing during the frame is done on a clean slate. - if (Core_IsStepping() && gpuStats.numFlips != 0) { - draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::KEEP, RPAction::CLEAR, RPAction::CLEAR}, "EmuScreen_BackBuffer"); - } else { - draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000}, "EmuScreen_BackBuffer"); - } - - draw->SetViewport(viewport); - draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); - framebufferBound = true; - } - draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres); - } else { - // Some other screen bound the backbuffer first. - framebufferBound = true; - } - - g_OSD.NudgeIngameNotifications(); - - const DeviceOrientation orientation = GetDeviceOrientation(); - const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation); - __DisplaySetDisplayLayoutConfig(displayLayoutConfig); + ScreenRenderFlags screenRenderFlags = ScreenRenderFlags::NONE; if (mode & ScreenRenderMode::TOP) { System_Notify(SystemNotification::KEEP_SCREEN_AWAKE); - } else if (!ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) { - // NOTE: The strcmp is != 0 - so all popped-over screens EXCEPT DevMenu - // Just to make sure. - if (PSP_IsInited() && !skipBufferEffects) { - _dbg_assert_(gpu); - gpu->BeginHostFrame(displayLayoutConfig); - gpu->SetCurFramebufferDirty(true); + } + + const DeviceOrientation orientation = GetDeviceOrientation(); + const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation); + + if (!skipBufferEffects_ && !ShouldRunEmulation(mode)) { + if (gpu) { gpu->CopyDisplayToOutput(displayLayoutConfig); - gpu->EndHostFrame(); } - if (gpu && gpu->PresentedThisFrame()) { - framebufferBound = true; - } - if (!framebufferBound) { - draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR,}, "EmuScreen_Behind"); - } - - Draw::BackendState state = draw->GetCurrentBackendState(); - if (state.valid) { - // The below can trigger when switching from skip-buffer-effects. We don't really care anymore... - // _dbg_assert_msg_(state.passes >= 1, "skipB: %d sw: %d mode: %d back: %d tag: %s behi: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering, (int)mode, (int)g_Config.iGPUBackend, screenManager()->topScreen()->tag(), (int)g_Config.bRunBehindPauseMenu); - // Workaround any remaining bugs like this. - if (state.passes == 0) { - draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR,}, "EmuScreen_SafeFallback"); - } - } - - // Need to make sure the UI texture is available, for "darken". - screenManager()->getUIContext()->BeginFrame(); - draw->SetViewport(viewport); - draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); darken(); - return ScreenRenderFlags::NONE; + return screenRenderFlags; } if (!PSP_IsInited() || readyToFinishBoot_) { @@ -1725,42 +1643,69 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { if (mode & ScreenRenderMode::TOP) { checkPowerDown(); } - draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "EmuScreen_Invalid"); - // Need to make sure the UI texture is available, for "darken". - screenManager()->getUIContext()->BeginFrame(); - draw->SetViewport(viewport); - draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); renderUI(); - return ScreenRenderFlags::NONE; + return screenRenderFlags; } - // Freeze-frame functionality (loads a savestate on every frame). - if (PSP_CoreParameter().freezeNext) { - PSP_CoreParameter().frozen = true; - PSP_CoreParameter().freezeNext = false; - SaveState::SaveToRam(freezeState_); - } else if (PSP_CoreParameter().frozen) { - std::string errorString; - if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) { - ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str()); - PSP_CoreParameter().frozen = false; - } + if (skipBufferEffects_) { + // In skip buffer effects mode, we run emulation *after* the backbuffer bind. + screenRenderFlags = RunEmulation(true); + } + + // We might have a bad viewport after RunEmulation, reset. + Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f}; + draw->SetViewport(viewport); + + ProcessQueuedVKeys(); + + const bool skipBufferEffects = skipBufferEffects_; + + // Gotta copy the output at some point. Also this is where we take the screenshot if needed. + if (gpu) { + gpu->CopyDisplayToOutput(displayLayoutConfig); } - // Running it early allows things like direct readbacks of buffers, things we can't do - // when we have started the final render pass. Well, technically we probably could with some manipulation - // of pass order in the render managers.. runImDebugger(); - return RunEmulation(mode, framebufferBound, skipBufferEffects); + Draw::BackendState state = draw->GetCurrentBackendState(); + + if (!(mode & ScreenRenderMode::TOP)) { + renderImDebugger(); + // We're in run-behind mode, but we don't want to draw chat, debug UI and stuff. We do draw the imdebugger though. + // So, darken and bail here. + // Reset viewport/scissor to be sure. + darken(); + return screenRenderFlags; + } + + // NOTE: We don't check for powerdown if we're not the top screen. + checkPowerDown(); + + if (hasVisibleUI()) { + cardboardDisableButton_->SetVisibility(displayLayoutConfig.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE); + renderUI(); + } + + if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) { + SetVRAppMode(VRAppMode::VR_DIALOG_MODE); + } else { + SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE); + } + + renderImDebugger(); + return screenRenderFlags; } -ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebufferBound, bool skipBufferEffects) { +ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) { using namespace Draw; ScreenRenderFlags flags = ScreenRenderFlags::NONE; + g_OSD.NudgeIngameNotifications(); + const DeviceOrientation orientation = GetDeviceOrientation(); const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation); + __DisplaySetDisplayLayoutConfig(displayLayoutConfig); + DrawContext *draw = screenManager()->getDrawContext(); const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f}; @@ -1768,15 +1713,26 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe bool blockedExecution = Achievements::IsBlockingExecution(); uint32_t clearColor = 0; if (!blockedExecution) { + // We process savestates before running the frame. + SaveState::Process(); + if (gpu) { gpu->BeginHostFrame(displayLayoutConfig); } - if (SaveState::Process()) { - // We might have lost the framebuffer bind if we had one, due to a readback. - if (framebufferBound) { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_SavestateRebind"); + + // Freeze-frame functionality (loads a savestate on every frame). + if (PSP_CoreParameter().freezeNext) { + PSP_CoreParameter().frozen = true; + PSP_CoreParameter().freezeNext = false; + SaveState::SaveToRam(freezeState_); + } else if (PSP_CoreParameter().frozen) { + std::string errorString; + if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) { + ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str()); + PSP_CoreParameter().frozen = false; } } + PSP_RunLoopWhileState(); // Hopefully, after running, coreState is now CORE_NEXTFRAME @@ -1796,17 +1752,13 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe // Clear to blue background screen bool dangerousSettings = !Reporting::IsSupported(); clearColor = dangerousSettings ? 0xFF900050 : 0xFF900000; - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_RuntimeError"); - framebufferBound = true; + draw->Clear(Draw::Aspect::COLOR_BIT, clearColor, 0.0f, 0); // The info is drawn later in renderUI } else { // If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output. // This won't work in non-buffered, but that's fine. - if (!framebufferBound && PSP_IsInited()) { - // draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_Stepping"); + if (PSP_IsInited()) { gpu->SetCurFramebufferDirty(true); - gpu->CopyDisplayToOutput(displayLayoutConfig); - framebufferBound = true; } } break; @@ -1823,7 +1775,15 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe } if (gpu) { + // Run post processing and other passes. + gpu->PrepareCopyDisplayToOutput(displayLayoutConfig); gpu->EndHostFrame(); + + // The right time to run this + if (SaveState::ProcessScreenshot(skipBufferEffects) && skipBufferEffects) { + // Need to restore the backbuffer render pass, it probably got destroyed. + draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer"); + } } if (SaveState::PollRestartNeeded() && !bootPending_) { @@ -1850,58 +1810,6 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe } } - if (gpu && gpu->PresentedThisFrame()) { - framebufferBound = true; - } - - if (!framebufferBound) { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_NoFrame"); - draw->SetViewport(viewport); - draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); - } - - Draw::BackendState state = draw->GetCurrentBackendState(); - - // State.valid just states whether the passes parameter has a meaningful value. - if (state.valid) { - _dbg_assert_msg_(state.passes >= 1, "skipB: %d sw: %d mode: %d back: %d bound: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering, (int)mode, (int)g_Config.iGPUBackend, (int)framebufferBound); - if (state.passes == 0) { - // Workaround any remaining bugs like this. - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_SafeFallback"); - } - } - - screenManager()->getUIContext()->BeginFrame(); - - if (!(mode & ScreenRenderMode::TOP)) { - renderImDebugger(); - // We're in run-behind mode, but we don't want to draw chat, debug UI and stuff. We do draw the imdebugger though. - // So, darken and bail here. - // Reset viewport/scissor to be sure. - draw->SetViewport(viewport); - draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); - darken(); - return flags; - } - - // NOTE: We don't check for powerdown if we're not the top screen. - if (checkPowerDown()) { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_PowerDown"); - } - - if (hasVisibleUI()) { - draw->SetViewport(viewport); - cardboardDisableButton_->SetVisibility(displayLayoutConfig.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE); - renderUI(); - } - - if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) { - SetVRAppMode(VRAppMode::VR_DIALOG_MODE); - } else { - SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE); - } - - renderImDebugger(); return flags; } @@ -2017,21 +1925,12 @@ bool EmuScreen::hasVisibleUI() { void EmuScreen::renderUI() { using namespace Draw; - DrawContext *thin3d = screenManager()->getDrawContext(); + DrawContext *draw = screenManager()->getDrawContext(); UIContext *ctx = screenManager()->getUIContext(); ctx->BeginFrame(); // This sets up some important states but not the viewport. ctx->Begin(); - Viewport viewport; - viewport.TopLeftX = 0; - viewport.TopLeftY = 0; - viewport.Width = g_display.pixel_xres; - viewport.Height = g_display.pixel_yres; - viewport.MaxDepth = 1.0; - viewport.MinDepth = 0.0; - thin3d->SetViewport(viewport); - if (root_) { UI::LayoutViewHierarchy(*ctx, RootMargins(), root_, false, false); root_->Draw(*ctx); diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 93bce1a0f1..a902d2ef39 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -70,10 +70,11 @@ public: protected: void darken(); void focusChanged(ScreenFocusChange focusChange) override; + ScreenRenderFlags PreRender(ScreenRenderMode mode) override; private: void CreateViews() override; - ScreenRenderFlags RunEmulation(ScreenRenderMode mode, bool framebufferBound, bool skipBufferEffects); + ScreenRenderFlags RunEmulation(bool skipBufferEffects); void OnDevTools(UI::EventParams ¶ms); void OnChat(UI::EventParams ¶ms); @@ -95,6 +96,8 @@ private: void ProcessQueuedVKeys(); void ProcessVKey(VirtKey vkey); + bool ShouldRunEmulation(ScreenRenderMode mode) const; + UI::Event OnDevMenu; UI::Event OnChatMenu; bool bootPending_ = true; @@ -159,6 +162,7 @@ private: #endif bool autoLoadFailed_ = false; // to prevent repeat reloads bool readyToFinishBoot_ = false; + bool skipBufferEffects_ = false; // cached state, fetched once per frame. }; bool MustRunBehind(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 30e26644b7..6aeabcae4f 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -78,6 +78,7 @@ #include "Core/HLE/sceUsbMic.h" #include "Core/HLE/sceUtility.h" #include "GPU/Common/PostShader.h" +#include "GPU/GPU.h" #if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) #include "UI/DarwinFileSystemServices.h" @@ -499,6 +500,7 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) if (g_Config.bSkipBufferEffects) { g_Config.bAutoFrameSkip = false; } + System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED); }); skipBufferEffects->SetDisabledPtr(&g_Config.bSoftwareRendering); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 0f23a9fd84..adb47454d3 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -98,12 +98,6 @@ void HandleCommonMessages(UIMessage message, const char *value, ScreenManager *m } ScreenRenderFlags BackgroundScreen::render(ScreenRenderMode mode) { - if (mode & ScreenRenderMode::FIRST) { - SetupViewport(); - } else { - _dbg_assert_(false); - } - UIContext *uiContext = screenManager()->getUIContext(); uiContext->PushTransform({ translation_, scale_, alpha_ }); diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index b26a620faa..b36068d173 100644 --- a/UI/MiscViews.cpp +++ b/UI/MiscViews.cpp @@ -11,6 +11,7 @@ #include "UI/GameInfoCache.h" #include "Common/UI/PopupScreens.h" #include "Core/Config.h" +#include "GPU/Common/PresentationCommon.h" TextWithImage::TextWithImage(ImageID imageID, std::string_view text, UI::LinearLayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) { using namespace UI; @@ -259,3 +260,32 @@ void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool System_Notify(SystemNotification::ROTATE_UPDATED); }); } + +void GameInfoBGView::Draw(UIContext &dc) { + // Should only be called when visible. + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::PIC1); + dc.Flush(); + + // PIC1 is the loading image, so let's only draw if it's available. + if (ginfo->Ready(GameInfoFlags::PIC1) && ginfo->pic1.texture) { + Draw::Texture *texture = ginfo->pic1.texture; + if (texture) { + const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()); + // Similar to presentation, we want to put the game PIC1 in the same region of the screen. + FRect frame = GetScreenFrame(config.bIgnoreScreenInsets, g_display.pixel_xres, g_display.pixel_yres); + FRect rc; + CalculateDisplayOutputRect(config, &rc, texture->Width(), texture->Height(), frame, config.iInternalScreenRotation); + + // Need to adjust for DPI here since we're still in the UI coordinate space here, not the pixel coordinate space used for in-game presentation. + Bounds bounds(rc.x * g_display.dpi_scale_x, rc.y * g_display.dpi_scale_y, rc.w * g_display.dpi_scale_x, rc.h * g_display.dpi_scale_y); + + dc.GetDrawContext()->BindTexture(0, texture); + + double loadTime = ginfo->pic1.timeLoaded; + uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3)); + dc.Draw()->DrawTexRect(bounds, 0, 0, 1, 1, color); + dc.Flush(); + dc.RebindTexture(); + } + } +} diff --git a/UI/MiscViews.h b/UI/MiscViews.h index cc852e8516..79b31df944 100644 --- a/UI/MiscViews.h +++ b/UI/MiscViews.h @@ -78,4 +78,17 @@ private: float scale_ = 1.0f; }; +class GameInfoBGView : public UI::InertView { +public: + GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {} + + void Draw(UIContext &dc) override; + std::string DescribeText() const override { return ""; } + void SetColor(uint32_t c) { color_ = c; } + +protected: + Path gamePath_; + uint32_t color_ = 0xFFC0C0C0; +}; + void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool text); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 6cbc84647e..704a596d8a 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1151,7 +1151,7 @@ void NativeFrame(GraphicsContext *graphicsContext) { g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation); - // All actual rendering happen in here. + // All actual rendering (and also emulation) happens in here. ScreenRenderFlags renderFlags = g_screenManager->render(); if (g_screenManager->getUIContext()->Text()) { g_screenManager->getUIContext()->Text()->OncePerFrame(); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 520e84ce4c..293878d511 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -266,10 +266,11 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const } if (draw) { - draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Headless"); + draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Backbuffer"); // Vulkan may get angry if we don't do a final present. if (gpu) { gpu->SetCurFramebufferDirty(true); + gpu->PrepareCopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape)); gpu->CopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape)); }