From f257ad4f7f1842c2f09f4a9606b779f12c363c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Feb 2026 12:13:24 +0100 Subject: [PATCH 1/6] VFS: Remove some code duplication --- Common/File/VFS/VFS.cpp | 106 +++++++++++++++------------------------- Common/File/VFS/VFS.h | 4 +- 2 files changed, 42 insertions(+), 68 deletions(-) diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index 26167efc86..7196a39e66 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -20,7 +20,7 @@ void VFS::Register(std::string_view prefix, VFSBackend *reader) { void VFS::Clear() { for (auto &entry : entries_) { - delete entry.reader; + delete entry.backend; } entries_.clear(); } @@ -37,6 +37,21 @@ static bool IsLocalAbsolutePath(std::string_view path) { return isUnixLocal || isWindowsLocal || isContentURI; } +bool VFS::MapPath(std::string_view path, VFSBackend **backend, std::string_view *relativePath) { + int fn_len = (int)path.length(); + for (const auto &entry : entries_) { + int prefix_len = (int)entry.prefix.length(); + if (prefix_len >= fn_len) continue; + if (0 == memcmp(path.data(), entry.prefix.data(), prefix_len)) { + *backend = entry.backend; + *relativePath = path.substr(prefix_len); + return true; + } + } + ERROR_LOG(Log::IO, "VFS: '%.*s' has an unknown filesystem prefix.", STR_VIEW(path)); + return false; +} + // The returned data should be free'd with delete[]. uint8_t *VFS::ReadFile(std::string_view filename, size_t *size) { if (IsLocalAbsolutePath(filename)) { @@ -45,26 +60,13 @@ uint8_t *VFS::ReadFile(std::string_view filename, size_t *size) { return File::ReadLocalFile(Path(filename), size); } - const int fn_len = (int)filename.length(); - bool fileSystemFound = false; - for (const auto &entry : entries_) { - int prefix_len = (int)entry.prefix.length(); - if (prefix_len >= fn_len) continue; - if (0 == memcmp(filename.data(), entry.prefix.data(), prefix_len)) { - fileSystemFound = true; - // INFO_LOG(Log::IO, "Prefix match: %s (%s) -> %s", entries[i].prefix, filename, filename + prefix_len); - uint8_t *data = entry.reader->ReadFile(filename.substr(prefix_len), size); - if (data) - return data; - else - continue; - // Else try the other registered file systems. - } + VFSBackend *backend = nullptr; + std::string_view relativePath; + if (!MapPath(filename, &backend, &relativePath)) { + return nullptr; } - if (!fileSystemFound) { - ERROR_LOG(Log::IO, "Missing filesystem for '%.*s'", STR_VIEW(filename)); - } // Otherwise, the file was just missing. No need to log. - return nullptr; + + return backend->ReadFile(relativePath, size); } bool VFS::GetFileListing(std::string_view path, std::vector *listing, const char *filter) { @@ -75,23 +77,13 @@ bool VFS::GetFileListing(std::string_view path, std::vector *lis return true; } - int fn_len = (int)path.length(); - bool fileSystemFound = false; - for (const auto &entry : entries_) { - int prefix_len = (int)entry.prefix.length(); - if (prefix_len >= fn_len) continue; - if (0 == memcmp(path.data(), entry.prefix.data(), prefix_len)) { - fileSystemFound = true; - if (entry.reader->GetFileListing(path.substr(prefix_len), listing, filter)) { - return true; - } - } + VFSBackend *backend = nullptr; + std::string_view relativePath; + if (!MapPath(path, &backend, &relativePath)) { + return false; } - if (!fileSystemFound) { - ERROR_LOG(Log::IO, "Missing filesystem for %.*s", STR_VIEW(path)); - } // Otherwise, the file was just missing. No need to log. - return false; + return backend->GetFileListing(relativePath, listing, filter); } bool VFS::GetFileInfo(std::string_view path, File::FileInfo *info) { @@ -101,23 +93,13 @@ bool VFS::GetFileInfo(std::string_view path, File::FileInfo *info) { return File::GetFileInfo(Path(path), info); } - bool fileSystemFound = false; - int fn_len = (int)path.length(); - for (const auto &entry : entries_) { - int prefix_len = (int)entry.prefix.length(); - if (prefix_len >= fn_len) continue; - if (0 == memcmp(path.data(), entry.prefix.data(), prefix_len)) { - fileSystemFound = true; - if (entry.reader->GetFileInfo(path.substr(prefix_len), info)) - return true; - else - continue; - } + VFSBackend *backend = nullptr; + std::string_view relativePath; + if (!MapPath(path, &backend, &relativePath)) { + return false; } - if (!fileSystemFound) { - ERROR_LOG(Log::IO, "Missing filesystem for '%.*s'", STR_VIEW(path)); - } // Otherwise, the file was just missing. No need to log. - return false; + + return backend->GetFileInfo(relativePath, info); } bool VFS::Exists(std::string_view path) { @@ -127,21 +109,11 @@ bool VFS::Exists(std::string_view path) { return File::Exists(Path(path)); } - bool fileSystemFound = false; - int fn_len = (int)path.length(); - for (const auto &entry : entries_) { - int prefix_len = (int)entry.prefix.length(); - if (prefix_len >= fn_len) continue; - if (0 == memcmp(path.data(), entry.prefix.data(), prefix_len)) { - fileSystemFound = true; - if (entry.reader->Exists(path.substr(prefix_len))) - return true; - else - continue; - } + VFSBackend *backend = nullptr; + std::string_view relativePath; + if (!MapPath(path, &backend, &relativePath)) { + return false; } - if (!fileSystemFound) { - ERROR_LOG(Log::IO, "Missing filesystem for '%.*s'", STR_VIEW(path)); - } // Otherwise, the file was just missing. No need to log. - return false; + + return backend->Exists(relativePath); } diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index c339328356..badb77174d 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -81,9 +81,11 @@ public: bool Exists(std::string_view path); private: + bool MapPath(std::string_view filename, VFSBackend **backend, std::string_view *relativePath); + struct VFSEntry { std::string_view prefix; - VFSBackend *reader; + VFSBackend *backend; }; std::vector entries_; }; From a342b53d6d5f938b8a41b22c9c15db565e84fbc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Feb 2026 12:34:03 +0100 Subject: [PATCH 2/6] Correct look of the screen rotation picker on the System tab in settings --- UI/GameSettingsScreen.cpp | 1 + UI/MiscViews.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 8bab7c46f0..30e26644b7 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1405,6 +1405,7 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { auto co = GetI18NCategory(I18NCat::CONTROLS); + // Display rotation AddRotationPicker(screenManager(), systemSettings, true); if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) { diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index fa66571c0e..b26a620faa 100644 --- a/UI/MiscViews.cpp +++ b/UI/MiscViews.cpp @@ -245,8 +245,10 @@ void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool auto co = GetI18NCategory(I18NCat::CONTROLS); PopupMultiChoice *rot = parent->Add(new PopupMultiChoice(&g_Config.iScreenRotation, text ? co->T("Screen Rotation") : "", screenRotation, 0, ARRAY_SIZE(screenRotation), I18NCat::CONTROLS, screenManager, text ? nullptr : new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT))); - rot->SetHideTitle(true); - rot->SetIconOnly(true); + if (!text) { + rot->SetHideTitle(true); + rot->SetIconOnly(true); + } rot->SetChoiceIcons(screenRotationIcons); // Portrait Reversed is not recommended on iPhone (and we also ban it in the plist). From 1078c27bb730420b9107928abd0528f8b8a7d928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Feb 2026 13:43:25 +0100 Subject: [PATCH 3/6] Prepare renderpass refactor --- UI/EmuScreen.cpp | 37 +++++++++++++++++++++++-------------- UI/EmuScreen.h | 1 + 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 9ee376485a..b0247fb39e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1634,13 +1634,12 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { // If a boot is in progress, update it. ProcessGameBoot(gamePath_); - ScreenRenderFlags flags = ScreenRenderFlags::NONE; - Draw::Viewport viewport{ 0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f }; + 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(); if (!draw) { - return flags; // shouldn't really happen but I've seen a suspicious stack trace.. + return ScreenRenderFlags::NONE; // shouldn't really happen but I've seen a suspicious stack trace.. } ProcessQueuedVKeys(); @@ -1649,8 +1648,6 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { bool framebufferBound = false; - const DeviceOrientation orientation = GetDeviceOrientation(); - if (mode & ScreenRenderMode::FIRST) { // Actually, always gonna be first when it exists (?) @@ -1664,9 +1661,9 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { 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"); + 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->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000}, "EmuScreen_BackBuffer"); } draw->SetViewport(viewport); @@ -1681,6 +1678,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { g_OSD.NudgeIngameNotifications(); + const DeviceOrientation orientation = GetDeviceOrientation(); const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation); __DisplaySetDisplayLayoutConfig(displayLayoutConfig); @@ -1699,7 +1697,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { framebufferBound = true; } if (!framebufferBound) { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_Behind"); + draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR,}, "EmuScreen_Behind"); } Draw::BackendState state = draw->GetCurrentBackendState(); @@ -1708,7 +1706,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { // _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"); + draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR,}, "EmuScreen_SafeFallback"); } } @@ -1717,7 +1715,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { draw->SetViewport(viewport); draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); darken(); - return flags; + return ScreenRenderFlags::NONE; } if (!PSP_IsInited() || readyToFinishBoot_) { @@ -1726,13 +1724,13 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { if (mode & ScreenRenderMode::TOP) { checkPowerDown(); } - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_Invalid"); + 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 flags; + return ScreenRenderFlags::NONE; } // Freeze-frame functionality (loads a savestate on every frame). @@ -1748,13 +1746,24 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { } } - PSP_UpdateDebugStats((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS || g_Config.bLogFrameDrops); - // 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); +} + +ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebufferBound, bool skipBufferEffects) { + using namespace Draw; + ScreenRenderFlags flags = ScreenRenderFlags::NONE; + + const DeviceOrientation orientation = GetDeviceOrientation(); + const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation); + 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}; + + PSP_UpdateDebugStats((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS || g_Config.bLogFrameDrops); bool blockedExecution = Achievements::IsBlockingExecution(); uint32_t clearColor = 0; if (!blockedExecution) { diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 122ffa4a8e..93bce1a0f1 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -73,6 +73,7 @@ protected: private: void CreateViews() override; + ScreenRenderFlags RunEmulation(ScreenRenderMode mode, bool framebufferBound, bool skipBufferEffects); void OnDevTools(UI::EventParams ¶ms); void OnChat(UI::EventParams ¶ms); From f01aaeafb179fa4a74dae46906cc4219600ddf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Feb 2026 18:16:54 +0100 Subject: [PATCH 4/6] Split CopyDisplayToOutput in preparation for the next step --- Core/HLE/sceDisplay.cpp | 3 ++- Core/HLE/sceNetAdhoc.cpp | 2 +- GPU/GPUCommon.h | 3 ++- GPU/GPUCommonHW.cpp | 5 +++-- GPU/GPUCommonHW.h | 6 ++++-- GPU/Software/SoftGpu.cpp | 2 +- GPU/Software/SoftGpu.h | 3 ++- UI/EmuScreen.cpp | 6 ++++-- headless/Headless.cpp | 6 ++++-- 9 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index b68bb5797c..56669862eb 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -661,7 +661,8 @@ void __DisplayFlip(int cyclesLate) { } } if (nextFrame) { - gpu->CopyDisplayToOutput(g_displayLayoutConfigCached, fbReallyDirty); + gpu->SetCurFramebufferDirty(fbReallyDirty); + gpu->CopyDisplayToOutput(g_displayLayoutConfigCached); if (fbReallyDirty) { DisplayFireActualFlip(); } diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 6e04ddebc6..0e6dfdbe8d 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -795,7 +795,7 @@ static int ptp_send_postoffice(int idx, const void *data, int *len) { } if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { // this is pretty critical - ERROR_LOG(Log::sceNet, "%s: critical: huge client buf %d what is going on please fix", __func__, len); + ERROR_LOG(Log::sceNet, "%s: critical: huge client buf %d what is going on please fix", __func__, len ? *len : 0); } return 0; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 8e999d5f52..49d185caa6 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -124,7 +124,8 @@ public: uint32_t GetAddrTranslation() override; virtual void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) = 0; - virtual void CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) = 0; + virtual void SetCurFramebufferDirty(bool dirty) = 0; + virtual void CopyDisplayToOutput(const DisplayLayoutConfig &config) = 0; virtual bool PresentedThisFrame() const = 0; // Invalidate any cached content sourced from the specified range. diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index b07273fc59..f2f1c199d1 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, bool reallyDirty) { +void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) { drawEngineCommon_->FlushQueuedDepth(); // Flush anything left over. drawEngineCommon_->Flush(); @@ -538,7 +538,8 @@ void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config, bool re shaderManager_->DirtyLastShader(); // after this, render pass is active. - framebufferManager_->CopyDisplayToOutput(config, reallyDirty); + framebufferManager_->CopyDisplayToOutput(config, curFramebufferDirty_); + curFramebufferDirty_ = false; gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 5e62ffc826..f2179e6b9a 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -7,10 +7,11 @@ class GPUCommonHW : public GPUCommon { public: GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw); - ~GPUCommonHW(); + ~GPUCommonHW() override; // This can fail, and if so no render pass is active. - void CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) override; + void SetCurFramebufferDirty(bool dirty) override { curFramebufferDirty_ = dirty; } + void CopyDisplayToOutput(const DisplayLayoutConfig &config) override; void DoState(PointerWrap &p) override; void DeviceLost() override; void DeviceRestore(Draw::DrawContext *draw) override; @@ -105,4 +106,5 @@ protected: int msaaLevel_ = 0; bool sawExactEqualDepth_ = false; ShaderManagerCommon *shaderManager_ = nullptr; + bool curFramebufferDirty_ = false; }; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index f4b79a2b0e..17b3e2058f 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -646,7 +646,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(const DisplayLayoutConfig &config, presentation_->CopyToOutput(config, outputFlags, config.iInternalScreenRotation, u0, v0, u1, v1); } -void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) { +void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) { drawEngine_->transformUnit.Flush(this, "output"); // The display always shows 480x272. CopyToCurrentFboFromDisplayRam(config, FB_WIDTH, FB_HEIGHT); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 434caef499..8c276d304c 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -137,7 +137,8 @@ public: void UpdateCmdInfo() override {} void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; - void CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) override; + void SetCurFramebufferDirty(bool dirty) override {} + void CopyDisplayToOutput(const DisplayLayoutConfig &config) override; void GetStats(char *buffer, size_t bufsize) override; std::vector GetFramebufferList() const override { return std::vector(); } void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index b0247fb39e..00a225e528 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1690,7 +1690,8 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { if (PSP_IsInited() && !skipBufferEffects) { _dbg_assert_(gpu); gpu->BeginHostFrame(displayLayoutConfig); - gpu->CopyDisplayToOutput(displayLayoutConfig, true); + gpu->SetCurFramebufferDirty(true); + gpu->CopyDisplayToOutput(displayLayoutConfig); gpu->EndHostFrame(); } if (gpu && gpu->PresentedThisFrame()) { @@ -1803,7 +1804,8 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe // 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"); - gpu->CopyDisplayToOutput(displayLayoutConfig, true); + gpu->SetCurFramebufferDirty(true); + gpu->CopyDisplayToOutput(displayLayoutConfig); framebufferBound = true; } } diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 139a26d18b..520e84ce4c 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -268,8 +268,10 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const if (draw) { draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Headless"); // Vulkan may get angry if we don't do a final present. - if (gpu) - gpu->CopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape), true); + if (gpu) { + gpu->SetCurFramebufferDirty(true); + gpu->CopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape)); + } draw->EndFrame(); } From c139d4aaff09717f3fb061022f58e1bddcd31966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Feb 2026 19:18:45 +0100 Subject: [PATCH 5/6] Split up Presentation::CopyToOutput into passes and output. --- GPU/Common/FramebufferManagerCommon.cpp | 13 +++-- GPU/Common/PresentationCommon.cpp | 70 +++++++++++++++---------- GPU/Common/PresentationCommon.h | 8 ++- GPU/Software/SoftGpu.cpp | 3 +- 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index f5d009a8c2..85f81c0a1d 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1536,12 +1536,11 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G return tex; } +// This is called from CopyFramebufferToOutput. bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig &config, const u8 *srcPixels, int srcStride, GEBufferFormat srcPixelFormat) { textureCache_->ForgetLastTexture(); shaderManager_->DirtyLastShader(); - float u0 = 0.0f, u1 = 480.0f / 512.0f; - float v0 = 0.0f, v1 = 1.0f; Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, 512, 272); if (!pixelsTex) return false; @@ -1556,16 +1555,19 @@ bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig flags |= OutputFlags::POSITION_FLIPPED; } + 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_->CopyToOutput(config, flags, uvRotation, u0, v0, u1, v1); + presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1); + presentation_->CopyToOutput(config, flags); // PresentationCommon sets all kinds of state, we can't rely on anything. gstate_c.Dirty(DIRTY_ALL); DiscardFramebufferCopy(); currentRenderVfb_ = nullptr; - return true; } @@ -1727,7 +1729,8 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co int actualHeight = (vfb->bufferHeight * vfb->renderHeight) / vfb->height; presentation_->UpdateUniforms(textureCache_->VideoIsPlaying()); presentation_->SourceFramebuffer(vfb->fbo, actualWidth, actualHeight); - presentation_->CopyToOutput(config, flags, uvRotation, u0, v0, u1, v1); + presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1); + presentation_->CopyToOutput(config, flags); } else if (useBufferedRendering_) { WARN_LOG(Log::FrameBuf, "Using buffered rendering, and current VFB lacks an FBO: %08x", vfb->fb_address); } else { diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 49f60db226..6c5a4b7a8f 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -651,9 +651,11 @@ void PresentationCommon::UpdateUniforms(bool hasVideo) { hasVideo_ = hasVideo; } -void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1) { +void PresentationCommon::RunPostshaderPasses(const DisplayLayoutConfig &config, OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1) { draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); + postShaderOutput_ = nullptr; + // TODO: If shader objects have been created by now, we might have received errors. // GLES can have the shader fail later, shader->failed / shader->error. // This should auto-disable usePostShader_ and call ShowPostShaderError(). @@ -663,7 +665,6 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF const bool usePostShader = usePostShader_ && !useStereo && !(flags & OutputFlags::RB_SWIZZLE); const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size(); - Draw::Framebuffer *postShaderOutput = nullptr; int lastWidth = srcWidth_; int lastHeight = srcHeight_; @@ -678,8 +679,7 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF frame.w /= 2.0; pixelWidth /= 2; } - FRect rc; - CalculateDisplayOutputRect(config, &rc, 480.0f, 272.0f, frame, uvRotation); + CalculateDisplayOutputRect(config, &rc_, 480.0f, 272.0f, frame, uvRotation); // To make buffer updates easier, we use one array of verts. int postVertsOffset = (int)sizeof(Vertex) * 4; @@ -699,10 +699,10 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF // 4-7: Post-processing, other passes // 8-11: Post-processing, first pass (needs to handle cropping the input image, if wrong dimensions) Vertex verts[12] = { - { rc.x, rc.y, 0, finalU0, finalV0, 0xFFFFFFFF }, // TL - { rc.x + rc.w, rc.y, 0, finalU1, finalV0, 0xFFFFFFFF }, // TR - { rc.x, rc.y + rc.h, 0, finalU0, finalV1, 0xFFFFFFFF }, // BL - { rc.x + rc.w, rc.y + rc.h, 0, finalU1, finalV1, 0xFFFFFFFF }, // BR + { rc_.x, rc_.y, 0, finalU0, finalV0, 0xFFFFFFFF }, // TL + { rc_.x + rc_.w, rc_.y, 0, finalU1, finalV0, 0xFFFFFFFF }, // TR + { rc_.x, rc_.y + rc_.h, 0, finalU0, finalV1, 0xFFFFFFFF }, // BL + { rc_.x + rc_.w, rc_.y + rc_.h, 0, finalU1, finalV1, 0xFFFFFFFF }, // BR }; // Rescale X, Y to normalized coordinate system. @@ -732,7 +732,7 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF rotation ^= 2; } - static int rotLookup[4] = { 0, 1, 3, 2 }; + static int rotLookup[4] = {0, 1, 3, 2}; for (int i = 0; i < 4; i++) { int otherI = rotLookup[(rotLookup[i] + rotation) & 3]; @@ -775,10 +775,9 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF // Grab the previous framebuffer early so we can change previousIndex_ when we want. Draw::Framebuffer *previousFramebuffer = previousFramebuffers_.empty() ? nullptr : previousFramebuffers_[previousIndex_]; - PostShaderUniforms uniforms; const auto performShaderPass = [&](const ShaderInfo *shaderInfo, Draw::Framebuffer *postShaderFramebuffer, Draw::Pipeline *postShaderPipeline, int vertsOffset) { - if (postShaderOutput) { - draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::Aspect::COLOR_BIT, 0); + if (postShaderOutput_) { + draw_->BindFramebufferAsTexture(postShaderOutput_, 0, Draw::Aspect::COLOR_BIT, 0); } else { BindSource(0, false); } @@ -788,10 +787,11 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF int nextWidth, nextHeight; draw_->GetFramebufferDimensions(postShaderFramebuffer, &nextWidth, &nextHeight); - Draw::Viewport viewport{ 0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f }; + Draw::Viewport viewport{0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f}; draw_->SetViewport(viewport); draw_->SetScissorRect(0, 0, nextWidth, nextHeight); + PostShaderUniforms uniforms; CalculatePostShaderUniforms(lastWidth, lastHeight, nextWidth, nextHeight, shaderInfo, &uniforms); draw_->BindPipeline(postShaderPipeline); @@ -806,7 +806,7 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF draw_->BindVertexBuffer(vdata_, vertsOffset); draw_->Draw(4, 0); - postShaderOutput = postShaderFramebuffer; + postShaderOutput_ = postShaderFramebuffer; lastWidth = nextWidth; lastHeight = nextHeight; }; @@ -818,16 +818,16 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF bool flipped = flags & OutputFlags::POSITION_FLIPPED; float y0 = flipped ? 1.0f : -1.0f; float y1 = flipped ? -1.0f : 1.0f; - verts[4] = { -1.0f, y0, 0.0f, 0.0f, 0.0f, 0xFFFFFFFF }; // TL - verts[5] = { 1.0f, y0, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF }; // TR - verts[6] = { -1.0f, y1, 0.0f, 0.0f, 1.0f, 0xFFFFFFFF }; // BL - verts[7] = { 1.0f, y1, 0.0f, 1.0f, 1.0f, 0xFFFFFFFF }; // BR + verts[4] = {-1.0f, y0, 0.0f, 0.0f, 0.0f, 0xFFFFFFFF}; // TL + verts[5] = {1.0f, y0, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF}; // TR + verts[6] = {-1.0f, y1, 0.0f, 0.0f, 1.0f, 0xFFFFFFFF}; // BL + verts[7] = {1.0f, y1, 0.0f, 1.0f, 1.0f, 0xFFFFFFFF}; // BR // Now, adjust for the desired input rectangle. - verts[8] = { -1.0f, y0, 0.0f, u0, v0, 0xFFFFFFFF }; // TL - verts[9] = { 1.0f, y0, 0.0f, u1, v0, 0xFFFFFFFF }; // TR - verts[10] = { -1.0f, y1, 0.0f, u0, v1, 0xFFFFFFFF }; // BL - verts[11] = { 1.0f, y1, 0.0f, u1, v1, 0xFFFFFFFF }; // BR + verts[8] = {-1.0f, y0, 0.0f, u0, v0, 0xFFFFFFFF}; // TL + verts[9] = {1.0f, y0, 0.0f, u1, v0, 0xFFFFFFFF}; // TR + verts[10] = {-1.0f, y1, 0.0f, u0, v1, 0xFFFFFFFF}; // BL + verts[11] = {1.0f, y1, 0.0f, u1, v1, 0xFFFFFFFF}; // BR draw_->UpdateBuffer(vdata_, (const uint8_t *)verts, 0, sizeof(verts), Draw::UPDATE_DISCARD); @@ -844,7 +844,7 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF postShaderFramebuffer = previousFramebuffers_[previousIndex_]; } - draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "PostShader"); + draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, {Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE}, "PostShader"); // Pick vertices 8-11 for the first pass. int vertOffset = i == 0 ? (int)sizeof(Vertex) * 8 : (int)sizeof(Vertex) * 4; @@ -869,9 +869,19 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF previousIndex_ = 0; Draw::Framebuffer *postShaderFramebuffer = previousFramebuffers_[previousIndex_]; - draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "InterFrameBlit"); + draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, {Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE}, "InterFrameBlit"); performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline, postVertsOffset); } +} + +void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputFlags flags) { + bool useNearest = flags & OutputFlags::NEAREST; + bool useStereo = gstate_c.Use(GPU_USE_SIMPLE_STEREO_PERSPECTIVE) && stereoPipeline_ != nullptr; // TODO: Also check that the backend has support for it. + + const bool usePostShader = usePostShader_ && !useStereo && !(flags & OutputFlags::RB_SWIZZLE); + const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size(); + 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_); @@ -891,20 +901,23 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF } draw_->BindPipeline(pipeline); - if (postShaderOutput) { - draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::Aspect::COLOR_BIT, 0); + if (postShaderOutput_) { + draw_->BindFramebufferAsTexture(postShaderOutput_, 0, Draw::Aspect::COLOR_BIT, 0); } else { BindSource(0, false); } } BindSource(1, false); + PostShaderUniforms uniforms; if (isFinalAtOutputResolution && previousFramebuffers_.empty()) { - CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, &postShaderInfo_.back(), &uniforms); + CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc_.w, (int)rc_.h, &postShaderInfo_.back(), &uniforms); draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms)); + previousUniforms_ = uniforms; } else if (useStereo) { - CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, stereoShaderInfo_, &uniforms); + CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc_.w, (int)rc_.h, stereoShaderInfo_, &uniforms); draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms)); + previousUniforms_ = uniforms; } else { Draw::VsTexColUB ub{}; memcpy(ub.WorldViewProj, g_display.rot_matrix.m, sizeof(float) * 16); @@ -945,7 +958,6 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF // Unbinds all textures and samplers too, needed since sometimes a MakePixelTexture is deleted etc. draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); - previousUniforms_ = uniforms; presentedThisFrame_ = true; } diff --git a/GPU/Common/PresentationCommon.h b/GPU/Common/PresentationCommon.h index f4aab245d0..a4c3f29022 100644 --- a/GPU/Common/PresentationCommon.h +++ b/GPU/Common/PresentationCommon.h @@ -125,7 +125,9 @@ public: void UpdateUniforms(bool hasVideo); void SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight); void SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight); - void CopyToOutput(const DisplayLayoutConfig &config, OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1); + + void RunPostshaderPasses(const DisplayLayoutConfig &config, OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1); + void CopyToOutput(const DisplayLayoutConfig &config, OutputFlags flags); void CalculateRenderResolution(const DisplayLayoutConfig &config, int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa) const; @@ -189,4 +191,8 @@ protected: int h; }; std::vector postShaderFBOUsage_; + + // Carry over info between RunPostShaderPasses and CopyToOutput. + Draw::Framebuffer *postShaderOutput_ = nullptr; + FRect rc_; }; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 17b3e2058f..232d9db5d0 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -643,7 +643,8 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(const DisplayLayoutConfig &config, } presentation_->SourceTexture(fbTex, desc.width, desc.height); - presentation_->CopyToOutput(config, outputFlags, config.iInternalScreenRotation, u0, v0, u1, v1); + presentation_->RunPostshaderPasses(config, outputFlags, config.iInternalScreenRotation, u0, v0, u1, v1); + presentation_->CopyToOutput(config, outputFlags); } void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) { From 814713e93287d880eab0861367e9a20c7ca52927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 7 Feb 2026 19:48:22 +0100 Subject: [PATCH 6/6] More refactor --- GPU/Common/FramebufferManagerCommon.cpp | 16 ++++++++++------ GPU/Common/PresentationCommon.cpp | 9 +++++---- GPU/Common/PresentationCommon.h | 4 +++- GPU/Software/SoftGpu.cpp | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 85f81c0a1d..b430409319 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1536,14 +1536,15 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G return tex; } -// This is called from CopyFramebufferToOutput. +// This is internal, called from CopyDisplayToOutput. bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig &config, const u8 *srcPixels, int srcStride, GEBufferFormat srcPixelFormat) { textureCache_->ForgetLastTexture(); shaderManager_->DirtyLastShader(); Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, 512, 272); - if (!pixelsTex) + if (!pixelsTex) { return false; + } int uvRotation = useBufferedRendering_ ? config.iInternalScreenRotation : ROTATION_LOCKED_HORIZONTAL; OutputFlags flags = config.iDisplayFilter == SCALE_LINEAR ? OutputFlags::LINEAR : OutputFlags::NEAREST; @@ -1561,7 +1562,6 @@ bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig presentation_->UpdateUniforms(textureCache_->VideoIsPlaying()); presentation_->SourceTexture(pixelsTex, 512, 272); presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1); - presentation_->CopyToOutput(config, flags); // PresentationCommon sets all kinds of state, we can't rely on anything. gstate_c.Dirty(DIRTY_ALL); @@ -1651,7 +1651,9 @@ 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_)) { + 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"); @@ -1730,7 +1732,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, flags); + presentation_->CopyToOutput(config); } else if (useBufferedRendering_) { WARN_LOG(Log::FrameBuf, "Using buffered rendering, and current VFB lacks an FBO: %08x", vfb->fb_address); } else { @@ -2802,7 +2804,9 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS if (isPrevDisplayBuffer || isDisplayBuffer) { FlushBeforeCopy(); // HACK - DrawFramebufferToOutput(displayLayoutConfigCopy_, Memory::GetPointerUnchecked(dstBasePtr), dstStride, displayFormat_); + if (DrawFramebufferToOutput(displayLayoutConfigCopy_, Memory::GetPointerUnchecked(dstBasePtr), dstStride, displayFormat_)) { + presentation_->CopyToOutput(displayLayoutConfigCopy_); + } return; } } diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 6c5a4b7a8f..6842c49cc2 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -655,6 +655,7 @@ void PresentationCommon::RunPostshaderPasses(const DisplayLayoutConfig &config, draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); postShaderOutput_ = nullptr; + outputFlags_ = flags; // TODO: If shader objects have been created by now, we might have received errors. // GLES can have the shader fail later, shader->failed / shader->error. @@ -874,11 +875,11 @@ void PresentationCommon::RunPostshaderPasses(const DisplayLayoutConfig &config, } } -void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputFlags flags) { - bool useNearest = flags & OutputFlags::NEAREST; +void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config) { + bool useNearest = outputFlags_ & OutputFlags::NEAREST; bool useStereo = gstate_c.Use(GPU_USE_SIMPLE_STEREO_PERSPECTIVE) && stereoPipeline_ != nullptr; // TODO: Also check that the backend has support for it. - const bool usePostShader = usePostShader_ && !useStereo && !(flags & OutputFlags::RB_SWIZZLE); + const bool usePostShader = usePostShader_ && !useStereo && !(outputFlags_ & OutputFlags::RB_SWIZZLE); const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size(); int lastWidth = srcWidth_; int lastHeight = srcHeight_; @@ -886,7 +887,7 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config, OutputF draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "FinalBlit"); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); - Draw::Pipeline *pipeline = (flags & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_; + Draw::Pipeline *pipeline = (outputFlags_ & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_; if (useStereo) { draw_->BindPipeline(stereoPipeline_); diff --git a/GPU/Common/PresentationCommon.h b/GPU/Common/PresentationCommon.h index a4c3f29022..d3cc1dad47 100644 --- a/GPU/Common/PresentationCommon.h +++ b/GPU/Common/PresentationCommon.h @@ -69,6 +69,7 @@ struct ShaderInfo; class TextureCacheCommon; enum class OutputFlags { + DEFAULT = 0, LINEAR = 0x0000, NEAREST = 0x0001, RB_SWIZZLE = 0x0002, @@ -127,7 +128,7 @@ public: void SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight); void RunPostshaderPasses(const DisplayLayoutConfig &config, OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1); - void CopyToOutput(const DisplayLayoutConfig &config, OutputFlags flags); + void CopyToOutput(const DisplayLayoutConfig &config); void CalculateRenderResolution(const DisplayLayoutConfig &config, int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa) const; @@ -195,4 +196,5 @@ protected: // Carry over info between RunPostShaderPasses and CopyToOutput. Draw::Framebuffer *postShaderOutput_ = nullptr; FRect rc_; + OutputFlags outputFlags_ = OutputFlags::DEFAULT; }; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 232d9db5d0..f8f9c9b7a2 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -644,7 +644,7 @@ 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, outputFlags); + presentation_->CopyToOutput(config); } void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) {