From ecae62e737cb4d2a8daae7795c348cad97847742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 4 Aug 2026 23:46:40 +0200 Subject: [PATCH] Fix headless with OpenGL for SDL --- CMakeLists.txt | 2 -- Core/EmuThread.cpp | 5 +---- Core/EmuThread.h | 2 ++ headless/Headless.cpp | 15 +++++++++++---- headless/SDLHeadlessGLGraphicsContext.cpp | 23 +++++++++++++++-------- headless/SDLHeadlessGLGraphicsContext.h | 19 +++++++++++++------ headless/WindowsHeadlessHost.h | 4 +++- 7 files changed, 45 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cdbe0c4216..21442f5016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1327,8 +1327,6 @@ file(GLOB UIImages CONFIGURE_DEPENDS if(HEADLESS) set(HeadlessSource headless/Headless.cpp - headless/HeadlessHost.cpp - headless/HeadlessHost.h headless/Compare.cpp headless/Compare.h headless/SDLHeadlessGLGraphicsContext.cpp diff --git a/Core/EmuThread.cpp b/Core/EmuThread.cpp index 6e5fc238eb..b176d93f74 100644 --- a/Core/EmuThread.cpp +++ b/Core/EmuThread.cpp @@ -102,10 +102,7 @@ bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std g_inLoop = true; - while (frame(graphicsContext)) { - // We're here again, so the game quit. Restart Run() which controls the UI. - // This way they can load a new game. - } + while (frame(graphicsContext)) {} // NOTE: Don't call stuff like Core_Stop here. On Android, we fully shut down graphics when you switch away from the app, // then boot it up again when returning. That means stopping this thread and restarting it. diff --git a/Core/EmuThread.h b/Core/EmuThread.h index d138af5a13..9da6977169 100644 --- a/Core/EmuThread.h +++ b/Core/EmuThread.h @@ -16,7 +16,9 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #pragma once + #include +#include #include "Common/System/Application.h" diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 669c3ae5f2..c274bfa7e7 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -238,7 +238,6 @@ static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **dev *deviceSetting = nullptr; return new SDLHeadlessGLGraphicsContext(); #elif PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) - GraphicsContext *graphicsContext = nullptr; switch (gpuCore) { case GPUCORE_GLES: *deviceSetting = nullptr; @@ -257,7 +256,7 @@ static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **dev #else #error The Headless build is not supported on this platform. Please use SDL (Mac/Linux) or Windows (non-UWP). #endif - return graphicsContext; + return nullptr; } struct AutoTestOptions { @@ -672,7 +671,11 @@ int main(int argc, const char* argv[]) { cmdLineOptions.ApplyToConfig(); // Translate backend to core. We probably should consider merging these enums. - if (!g_Config.bSoftwareRendering && cmdLineOptions.gpuBackend.has_value()) { + if (!g_Config.bSoftwareRendering) { + if (!cmdLineOptions.gpuBackend.has_value()) { + fprintf(stderr, "No graphics backend specified, but software rendering is disabled. Use --graphics=software, gles, directx11, or vulkan.\n"); + return 1; + } switch (cmdLineOptions.gpuBackend.value()) { case GPUBackend::OPENGL: gpuCore = GPUCORE_GLES; @@ -696,7 +699,11 @@ int main(int argc, const char* argv[]) { graphicsContext = new NullGraphicsContext(); } else { // TODO: Will we need a larger window for higher resolutions? Well, not if we use buffered rendering. - windowDesc = CreateHiddenWindow(480, 272); + windowDesc = CreateHiddenWindow(480, 272, cmdLineOptions.gpuBackend.value()); + if (!windowDesc.Valid()) { + fprintf(stderr, "Failed to create a window for graphics context"); + return 1; + } graphicsContext = CreateGraphicsContext(gpuCore, &deviceSetting); if (!graphicsContext) { // If we don't get the desired context, we DO NOT fall back. diff --git a/headless/SDLHeadlessGLGraphicsContext.cpp b/headless/SDLHeadlessGLGraphicsContext.cpp index eea1966610..40e5aab5d2 100644 --- a/headless/SDLHeadlessGLGraphicsContext.cpp +++ b/headless/SDLHeadlessGLGraphicsContext.cpp @@ -37,14 +37,24 @@ const bool WINDOW_VISIBLE = false; -WindowDesc CreateHiddenWindow(int w, int h) { - Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS; +WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend) { + Uint32 flags = SDL_WINDOW_BORDERLESS; + if (backend == GPUBackend::OPENGL) { + flags |= SDL_WINDOW_OPENGL; + } else if (backend == GPUBackend::VULKAN) { + flags |= SDL_WINDOW_VULKAN; + } if (!WINDOW_VISIBLE) { flags |= SDL_WINDOW_HIDDEN; } WindowDesc desc; desc.data2 = SDL_CreateWindow("PPSSPPHeadless", w, h, flags); desc.winsys = WindowSystem::WINDOWSYSTEM_SDL; + if (!desc.data2) { + const char *err = SDL_GetError(); + printf("Failed to create offscreen window: %s\n", err ? err : "(unknown error)"); + return {}; + } return desc; } @@ -83,12 +93,9 @@ bool SDLHeadlessGLGraphicsContext::InitAPI(void *wnd, std::string *deviceName, s SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - screen_ = CreateHiddenWindow(); - if (!screen_) { - const char *err = SDL_GetError(); - printf("Failed to create offscreen window: %s\n", err ? err : "(unknown error)"); - return false; - } + screen_ = (SDL_Window *)wnd; + _dbg_assert_(screen_); + glContext_ = SDL_GL_CreateContext(screen_); if (!glContext_) { const char *err = SDL_GetError(); diff --git a/headless/SDLHeadlessGLGraphicsContext.h b/headless/SDLHeadlessGLGraphicsContext.h index 2e1aed38c5..95345c3fc9 100644 --- a/headless/SDLHeadlessGLGraphicsContext.h +++ b/headless/SDLHeadlessGLGraphicsContext.h @@ -21,9 +21,9 @@ #include -#include "headless/HeadlessHost.h" - +#include "Common/GPU/GraphicsContext.h" #include "Common/GPU/OpenGL/GLRenderManager.h" +#include "Core/ConfigValues.h" class SDLHeadlessGLGraphicsContext : public GraphicsContext { public: @@ -35,6 +35,8 @@ public: void ShutdownSurface() override; + bool NeedsSeparateEmuThread() const override { return true; } + Draw::DrawContext *GetDrawContext() override { return draw_; } @@ -43,8 +45,8 @@ public: renderManager_->ThreadStart(draw_); } - bool ThreadFrame(bool waitIfEmpty) override { - return renderManager_->ThreadFrame(waitIfEmpty); + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); } void ThreadEnd() override { @@ -53,6 +55,11 @@ public: void Resize() override {} + // Call from emu thread + void NotifyEmuThreadExit() override { + renderManager_->NotifyEmuThreadExit(); + } + private: Draw::DrawContext *draw_ = nullptr; GLRenderManager *renderManager_ = nullptr; @@ -60,7 +67,7 @@ private: SDL_GLContext glContext_; }; -void *CreateHiddenWindow(int w, int h); -void DestroyHiddenWindow(void *window); +WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend); +void DestroyHiddenWindow(WindowDesc window); #endif diff --git a/headless/WindowsHeadlessHost.h b/headless/WindowsHeadlessHost.h index 9b380d870a..3a108a148f 100644 --- a/headless/WindowsHeadlessHost.h +++ b/headless/WindowsHeadlessHost.h @@ -17,8 +17,10 @@ #pragma once +#include "Core/ConfigValues.h" + struct WindowDesc; // Same API as for SDL -WindowDesc CreateHiddenWindow(int w, int h); +WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend); void DestroyHiddenWindow(WindowDesc window);