From 70f205f2ff11fa350bbb058d5a4574f4d2401ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 19:44:21 +0100 Subject: [PATCH] Take output screenshots after the ImGui debugger has drawn The screenshot readback ran in ScreenManager's post-render callback, and on Vulkan it ends the backbuffer render step. renderImDebugger() draws after that, so taking a screenshot with the debugger open made its first SetViewport() write through a null render step (an assert in debug builds, a Play crash report in release). Do the readback at the end of the frame in NativeFrame instead, which also puts the debugger in the screenshot, and remove the now unused callback. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/UI/ScreenManager.cpp | 5 ----- Common/UI/ScreenManager.h | 10 ---------- UI/NativeApp.cpp | 8 ++------ 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/Common/UI/ScreenManager.cpp b/Common/UI/ScreenManager.cpp index 2ae10e113a..75d42c7ca3 100644 --- a/Common/UI/ScreenManager.cpp +++ b/Common/UI/ScreenManager.cpp @@ -389,11 +389,6 @@ ScreenRenderFlags ScreenManager::Render(std::function afterPreRender) { } getUIContext()->Flush(); - - if (postRenderCb_) { - // Really can't render anything after this! Will crash the screenshot mechanism if we do. - postRenderCb_(getUIContext(), postRenderUserdata_); - } } else { ERROR_LOG(Log::UI, "No current screen!"); } diff --git a/Common/UI/ScreenManager.h b/Common/UI/ScreenManager.h index 9b8e46af69..45edf20bb9 100644 --- a/Common/UI/ScreenManager.h +++ b/Common/UI/ScreenManager.h @@ -29,8 +29,6 @@ enum { LAYER_TRANSPARENT = 2, }; -typedef void (*PostRenderCallback)(UIContext *ui, void *userdata); - class ScreenManager { public: virtual ~ScreenManager(); @@ -45,11 +43,6 @@ public: UIContext *getUIContext() { return uiContext_; } Draw::DrawContext *getDrawContext() { return draw_; } - void setPostRenderCallback(PostRenderCallback cb, void *userdata) { - postRenderCb_ = cb; - postRenderUserdata_ = userdata; - } - ScreenRenderFlags Render(std::function afterPreRender); void resized(); void shutdown(); @@ -110,9 +103,6 @@ private: UIContext *uiContext_ = nullptr; Draw::DrawContext *draw_ = nullptr; - PostRenderCallback postRenderCb_ = nullptr; - void *postRenderUserdata_ = nullptr; - const Screen *dialogFinished_ = nullptr; DialogResult dialogResult_{}; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index af789df11d..0bdce0d71f 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -957,7 +957,6 @@ void NativeClearAchievementsHostOverride() { }); } -void CallbackPostRender(UIContext *dc, void *userdata); bool CreateGlobalPipelines(); // TODO: Add faster special case for channels == 2. @@ -1009,7 +1008,6 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { } g_screenManager->setUIContext(uiContext); - g_screenManager->setPostRenderCallback(&CallbackPostRender, nullptr); // Live before the queue is processed: a removal that comes in while the outputs' swapchains are being // recreated has to wait for the next frame, as its surface may be gone once it returns. SetOutputsLive(true); @@ -1188,10 +1186,6 @@ void NativeShutdownGraphics(GraphicsContext *graphicsContext) { INFO_LOG(Log::System, "NativeShutdownGraphics end"); } -void CallbackPostRender(UIContext *dc, void *userdata) { - ScreenshotNotifyEndOfFrame(dc->GetDrawContext()); -} - static void SendMouseDeltaAxis(); void NativeFrame(GraphicsContext *graphicsContext) { @@ -1405,6 +1399,8 @@ void NativeFrame(GraphicsContext *graphicsContext) { runImDebugger(g_draw); }); renderImDebugger(g_draw); + // Last, since the readback ends the backbuffer pass. Nothing can draw after it. + ScreenshotNotifyEndOfFrame(g_draw); if (g_screenManager->getUIContext()->Text()) { g_screenManager->getUIContext()->Text()->OncePerFrame(); }