From c9cddb99e92ff631e13677d68b63d576ff2d6fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 09:28:20 -0600 Subject: [PATCH] Headless: Present OpenGL frames too OpenGL's render thread only finishes a frame when it's presented, so the emu thread eventually waited forever in BeginFrame for a free frame, with the render thread waiting for work. GPU tests and games hung, silently, as a blocked host thread also defeats the timeouts. Co-Authored-By: Claude Opus 5.5 (1M context) --- headless/Headless.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 2b96974088..e33f85ea2f 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -330,12 +330,14 @@ struct AutoTestOptions { int requiredDisableHLE; }; -// Ends a frame of the draw context the way the app does, presenting it. With Vulkan, presenting is -// also what returns a frame's image, and a frame that isn't presented would wait forever for its -// next one. The other backends present to a hidden window, which could wait for vsync. +// Ends a frame of the draw context the way the app does, presenting it. Unpresented frames never +// finish: with Vulkan, presenting is what returns a frame's image, and OpenGL's render thread only +// finishes a frame when it's presented, so either would eventually wait forever. Neither waits for +// vsync here (Vulkan and, on macOS, OpenGL render offscreen, and the hidden-window OpenGL context +// swaps with interval 0). D3D11 presents to a hidden window, which could. static void EndDrawFrame(Draw::DrawContext *draw) { draw->EndFrame(); - if (GetGPUBackend() == GPUBackend::VULKAN) { + if (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::OPENGL) { draw->Present(Draw::PresentMode::FIFO); } }