From 25cf52176f7b52671a5e398df1962ddaa951db47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 4 Aug 2026 22:24:26 +0200 Subject: [PATCH] Misc headless work --- CMakeLists.txt | 4 +- Common/GPU/GraphicsContext.h | 10 + Core/EmuThread.cpp | 3 +- Windows/main.cpp | 4 + headless/Headless.cpp | 243 +++++++++++------- headless/Headless.vcxproj | 7 +- headless/Headless.vcxproj.filters | 7 +- headless/HeadlessHost.cpp | 26 -- headless/HeadlessHost.h | 36 --- ...t.cpp => SDLHeadlessGLGraphicsContext.cpp} | 118 ++------- headless/SDLHeadlessGLGraphicsContext.h | 66 +++++ headless/SDLHeadlessHost.h | 52 ---- headless/WindowsHeadlessHost.cpp | 130 +--------- headless/WindowsHeadlessHost.h | 36 +-- 14 files changed, 271 insertions(+), 471 deletions(-) delete mode 100644 headless/HeadlessHost.cpp delete mode 100644 headless/HeadlessHost.h rename headless/{SDLHeadlessHost.cpp => SDLHeadlessGLGraphicsContext.cpp} (55%) create mode 100644 headless/SDLHeadlessGLGraphicsContext.h delete mode 100644 headless/SDLHeadlessHost.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c59690f90..cdbe0c4216 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1331,8 +1331,8 @@ if(HEADLESS) headless/HeadlessHost.h headless/Compare.cpp headless/Compare.h - headless/SDLHeadlessHost.cpp - headless/SDLHeadlessHost.h + headless/SDLHeadlessGLGraphicsContext.cpp + headless/SDLHeadlessGLGraphicsContext.h ) if(APPLE) list(APPEND HeadlessSource diff --git a/Common/GPU/GraphicsContext.h b/Common/GPU/GraphicsContext.h index 4b6b7ac12b..044bc784b0 100644 --- a/Common/GPU/GraphicsContext.h +++ b/Common/GPU/GraphicsContext.h @@ -51,3 +51,13 @@ public: // TODO: Store in a protected variable? virtual Draw::DrawContext *GetDrawContext() = 0; }; + +// This is used by the headless build, for the case of software rendering where we really don't need any context, +// unlike the standalone build where we do need a context anyway for UI and stuff. +class NullGraphicsContext : public GraphicsContext { +public: + NullGraphicsContext() {} + Draw::DrawContext *GetDrawContext() override { return nullptr; } + void Resize() override {} + bool NeedsSeparateEmuThread() const override { return false; } +}; diff --git a/Core/EmuThread.cpp b/Core/EmuThread.cpp index 5b51dae28c..d3652fead9 100644 --- a/Core/EmuThread.cpp +++ b/Core/EmuThread.cpp @@ -95,11 +95,10 @@ void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread) { } bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std::function frame) { - // This is the main thread. the graphics contexts will spawn and handle its own threads if needed. + // This is the main loop for graphics context that handle their own threading. // InitFromRenderThread/ShutdownFromRenderThread are not used. application->InitGraphics(graphicsContext); - // NativeResized(); DEBUG_LOG(Log::Boot, "Done."); diff --git a/Windows/main.cpp b/Windows/main.cpp index c8347891bc..370c53f191 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -1035,16 +1035,20 @@ static GraphicsContext *CreateGraphicsContext(GPUBackend backend, std::string ** *deviceName = nullptr; break; #endif +#if PPSSPP_PLATFORM(WINDOWS) case GPUBackend::DIRECT3D11: graphicsContext = new D3D11Context(); *deviceName = &g_Config.sD3D11Device; break; +#endif +#if !PPSSPP_PLATFORM(UWP) case GPUBackend::VULKAN: default: graphicsContext = new VulkanGraphicsContext(); *deviceName = &g_Config.sVulkanDevice; break; } +#endif return graphicsContext; } diff --git a/headless/Headless.cpp b/headless/Headless.cpp index e20b6de537..eee49a3ef9 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -28,6 +28,10 @@ #include "Common/CommonWindows.h" #if PPSSPP_PLATFORM(WINDOWS) +#if PPSSPP_API(ANY_GL) +#include "Windows/GPU/WindowsGLContext.h" +#endif +#include "Windows/GPU/D3D11Context.h" #else #include #endif @@ -41,11 +45,13 @@ #include "Common/TimeUtil.h" #include "Common/StringUtils.h" #include "Common/Thread/ThreadManager.h" +#include "Common/GPU/Vulkan/VulkanGraphicsContext.h" #include "Core/CmdLine.h" #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/EmuThread.h" #include "Core/System.h" #include "Core/WebServer.h" #include "Core/HLE/sceUtility.h" @@ -56,15 +62,12 @@ #include "Common/Log/LogManager.h" #include "Compare.h" -#include "HeadlessHost.h" #if defined(_WIN32) #include "WindowsHeadlessHost.h" #elif defined(SDL) -#include "SDLHeadlessHost.h" +#include "SDLHeadlessGLGraphicsContext.h" #endif -static HeadlessHost *g_headlessHost; - static Path g_comparisonScreenshot; static Path g_screenshotSavePath; static double g_maxScreenshotError = 0.0; @@ -110,7 +113,6 @@ bool System_GetPropertyBool(SystemProperty prop) { void System_Notify(SystemNotification notification) {} void System_PostUIMessage(UIMessage message, std::string_view param) {} void System_RunOnMainThread(std::function) {} - std::vector System_GetCameraDeviceList() { return std::vector(); } void System_AskForPermission(SystemPermission permission) {} PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; } @@ -139,20 +141,6 @@ int printUsage(const CommandLineOptions &options, const char *progname, const ch return 1; } -static HeadlessHost *getHost(GPUCore gpuCore) { - switch (gpuCore) { - case GPUCORE_SOFTWARE: - return new HeadlessHost(); -#ifdef HEADLESSHOST_CLASS - default: - return new HEADLESSHOST_CLASS(); -#else - default: - return new HeadlessHost(); -#endif - } -} - void FlushDebugOutput() { if (!g_debugOutputBuffer.empty()) { fwrite(g_debugOutputBuffer.data(), sizeof(char), g_debugOutputBuffer.length(), stdout); @@ -245,6 +233,35 @@ void System_SendDebugScreenshot(const uint8_t *data, int width, int height) { } } +static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **deviceSetting, WindowSystem *winsys) { +#ifdef SDL + *deviceSetting = nullptr; + *winsys = WindowSystem::WINDOWSYSTEM_SDL; + return new SDLHeadlessGLGraphicsContext(); +#elif PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) + GraphicsContext *graphicsContext = nullptr; + *winsys = WINDOWSYSTEM_WIN32; + switch (gpuCore) { + case GPUCORE_GLES: + *deviceSetting = nullptr; + return new WindowsGLContext(); + case GPUCORE_DIRECTX11: + *deviceSetting = &g_Config.sD3D11Device; + return new D3D11Context(); + case GPUCORE_VULKAN: + *deviceSetting = &g_Config.sVulkanDevice; + return new VulkanGraphicsContext(); + case GPUCORE_SOFTWARE: + default: + _assert_(false); + break; + } +#else +#error The Headless build is not supported on this platform. Please use SDL (Mac/Linux) or Windows (non-UWP). +#endif + return graphicsContext; +} + struct AutoTestOptions { double timeout; double maxScreenshotError; @@ -254,7 +271,7 @@ struct AutoTestOptions { bool printEqualLines; }; -static bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) { +static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &coreParameter, const AutoTestOptions &opt) { using namespace Draw; // Kinda ugly, trying to guesstimate the test name from filename... @@ -309,7 +326,6 @@ static bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter if (coreState == CORE_NEXTFRAME) { // INFO_LOG(Log::System, "(frame)"); coreState = CORE_RUNNING_CPU; - headlessHost->SwapBuffers(); } if (coreState == CORE_STEPPING_CPU && !coreParameter.startBreak) { break; @@ -414,6 +430,77 @@ static void AddToTestsByPath(std::vector *tests, std::string_view p } } +// Returns the retval that will be returned from main. +int RunTests(GraphicsContext *graphicsContext, CoreParameter &coreParameter, const AutoTestOptions &testOptions, const std::vector &testFilenames) { + std::vector failedTests; + std::vector passedTests; + std::vector missingTests; + + for (size_t i = 0; i < testFilenames.size(); ++i) { + coreParameter.fileToStart = Path(testFilenames[i]); + if (!File::Exists(coreParameter.fileToStart)) { + fprintf(stderr, "File not found: %s\n", coreParameter.fileToStart.c_str()); + missingTests.push_back(testFilenames[i]); + continue; + } + if (testOptions.compare) { + printf("%s:\n", coreParameter.fileToStart.c_str()); + } + const bool passed = RunAutoTest(graphicsContext, coreParameter, testOptions); + if (testOptions.bench) { + double st = time_now_d(); + double deadline = st + testOptions.timeout; + double runs = 0.0; + for (int i = 0; i < 100; ++i) { + RunAutoTest(graphicsContext, coreParameter, testOptions); + runs++; + if (time_now_d() > deadline) { + break; + } + } + double et = time_now_d(); + + std::string testName = GetTestName(coreParameter.fileToStart); + printf(" %s - %f seconds average\n", testName.c_str(), (et - st) / runs); + } + if (testOptions.compare) { + std::string testName = GetTestName(coreParameter.fileToStart); + if (passed) { + passedTests.push_back(testName); + printf(" %s - passed!\n", testName.c_str()); + } else { + failedTests.push_back(testName); + } + } + } + + if (testOptions.compare) { + printf("%d tests passed, %d tests failed, %d tests missing.\n", (int)passedTests.size(), (int)failedTests.size(), (int)missingTests.size()); + if (!failedTests.empty()) { + printf("Failed tests:\n"); + for (size_t i = 0; i < failedTests.size(); ++i) { + printf(" %s\n", failedTests[i].c_str()); + } + return 1; + } + } + + return 0; +} + +class HeadlessApplication : public Application { +public: + bool InitGraphics(GraphicsContext *graphicsContext) override { + Core_SetGraphicsContext(graphicsContext); + return true; + } + void ShutdownGraphics(GraphicsContext *graphicsContext) override { + graphicsContext->NotifyEmuThreadExit(); + } +private: + std::function frameCallback_; +}; + int main(int argc, const char* argv[]) { PROFILE_INIT(); TimeInit(); @@ -534,12 +621,7 @@ int main(int argc, const char* argv[]) { // Needs to be after log so we don't interfere with test output. g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count); - HeadlessHost *headlessHost = getHost(gpuCore); - g_headlessHost = headlessHost; - std::string error_string; - GraphicsContext *graphicsContext = nullptr; - bool glWorking = headlessHost->InitGraphics(&error_string, &graphicsContext, gpuCore); // Force known values for deterministic test execution. This happens before // ApplyToConfig() below, so a matching command line flag can still override any of it - @@ -574,7 +656,7 @@ int main(int argc, const char* argv[]) { g_Config.iInternalResolution = cmdLineOptions.resolutionScale.value_or(1); g_Config.bEnableLogging = (fullLog || outputDebugStringLog); g_Config.bVertexDecoderJit = true; - g_Config.bSoftwareRendering = (glWorking ? gpuCore : GPUCORE_SOFTWARE) == GPUCORE_SOFTWARE; + g_Config.bSoftwareRendering = gpuCore == GPUCORE_SOFTWARE; g_Config.bSoftwareRenderingJit = true; g_Config.iSplineBezierQuality = 2; g_Config.bHighQualityDepth = true; @@ -594,11 +676,31 @@ int main(int argc, const char* argv[]) { // overrides above, so a matching command line flag always wins. cmdLineOptions.ApplyToConfig(); + // TODO: Will we need a larger window for higher resolutions? Well, not if we use buffered rendering. + void *window = nullptr; + + // Time to set up graphics + GraphicsContext *graphicsContext = nullptr; + std::string *deviceSetting = nullptr; + WindowSystem winsys = WindowSystem::WINDOWSYSTEM_NONE; + if (g_Config.bSoftwareRendering) { + // For software rendering, we just create a dummy graphics context (to share as much code as possible). + graphicsContext = new NullGraphicsContext(); + } else { + window = CreateHiddenWindow(480, 272); + graphicsContext = CreateGraphicsContext(gpuCore, &deviceSetting, &winsys); + if (!graphicsContext) { + // If we don't get the desired context, we DO NOT fall back. + fprintf(stderr, "Failed to create a graphics context for GPU core"); + return 1; + } + } + // TODO: This whole function should be refactored to set up CoreParameter in one place, // but not now. CoreParameter coreParameter; coreParameter.cpuCore = cpuCore; // apprently this gets overwritten somehow by g_Config above. - coreParameter.gpuCore = glWorking ? gpuCore : GPUCORE_SOFTWARE; + coreParameter.gpuCore = gpuCore; coreParameter.graphicsContext = graphicsContext; coreParameter.enableSound = false; coreParameter.mountIso = mountIso.empty() ? Path() : Path(mountIso); @@ -641,10 +743,12 @@ int main(int argc, const char* argv[]) { nextPath = nextPath.NavigateUp(); } - if (cmdLineOptions.screenshotFilename.has_value()) + if (cmdLineOptions.screenshotFilename.has_value()) { SetComparisonScreenshot(Path(std::string(cmdLineOptions.screenshotFilename.value())), testOptions.maxScreenshotError); - if (cmdLineOptions.screenshotFilenameSave.has_value()) + } + if (cmdLineOptions.screenshotFilenameSave.has_value()) { SetScreenshotSavePath(Path(std::string(cmdLineOptions.screenshotFilenameSave.value()))); + } SetWriteFailureScreenshot(!getenv("GITHUB_ACTIONS") && !testOptions.bench); SetWriteDebugOutput(!testOptions.compare && !testOptions.bench); @@ -681,67 +785,32 @@ int main(int argc, const char* argv[]) { SaveState::Load(Path(stateToLoad), -1); } + + Application *app = new HeadlessApplication([&]() { + // This is called from the main thread, so we can run the emulation here if we don't need a separate thread. + if (!graphicsContext->NeedsSeparateEmuThread()) { + RunTests(graphicsContext, coreParameter, testOptions, testFilenames); + } + }); + + if (graphicsContext->NeedsSeparateEmuThread()) { + std::thread emuThread = EmuThread_Start(graphicsContext, app, nullptr); + + + emuThread.join(); + } + + // Run the tests (or frame dumps), one after another. - - std::vector failedTests; - std::vector passedTests; - std::vector missingTests; - - for (size_t i = 0; i < testFilenames.size(); ++i) { - coreParameter.fileToStart = Path(testFilenames[i]); - if (!File::Exists(coreParameter.fileToStart)) { - fprintf(stderr, "File not found: %s\n", coreParameter.fileToStart.c_str()); - missingTests.push_back(testFilenames[i]); - continue; - } - if (testOptions.compare) - printf("%s:\n", coreParameter.fileToStart.c_str()); - bool passed = RunAutoTest(headlessHost, coreParameter, testOptions); - if (testOptions.bench) { - double st = time_now_d(); - double deadline = st + testOptions.timeout; - double runs = 0.0; - for (int i = 0; i < 100; ++i) { - RunAutoTest(headlessHost, coreParameter, testOptions); - runs++; - if (time_now_d() > deadline) { - break; - } - } - double et = time_now_d(); - - std::string testName = GetTestName(coreParameter.fileToStart); - printf(" %s - %f seconds average\n", testName.c_str(), (et - st) / runs); - } - if (testOptions.compare) { - std::string testName = GetTestName(coreParameter.fileToStart); - if (passed) { - passedTests.push_back(testName); - printf(" %s - passed!\n", testName.c_str()); - } else { - failedTests.push_back(testName); - } - } - } - - if (testOptions.compare) { - printf("%d tests passed, %d tests failed, %d tests missing.\n", (int)passedTests.size(), (int)failedTests.size(), (int)missingTests.size()); - if (!failedTests.empty()) { - printf("Failed tests:\n"); - for (size_t i = 0; i < failedTests.size(); ++i) { - printf(" %s\n", failedTests[i].c_str()); - } - } - } + const int retval = RunTests(graphicsContext, coreParameter, testOptions, testFilenames); if (cmdLineOptions.debuggerPort.has_value()) { ShutdownWebServer(); } - headlessHost->ShutdownGraphics(); - delete headlessHost; - headlessHost = nullptr; - g_headlessHost = nullptr; + if (window) { + DestroyHiddenWindow(window); + } g_VFS.Clear(); g_logManager.Shutdown(); @@ -752,7 +821,5 @@ int main(int argc, const char* argv[]) { g_threadManager.Teardown(); - if (!failedTests.empty()) - return 1; - return 0; + return retval; } diff --git a/headless/Headless.vcxproj b/headless/Headless.vcxproj index 9fbd83bc75..5c242f2e47 100644 --- a/headless/Headless.vcxproj +++ b/headless/Headless.vcxproj @@ -354,7 +354,7 @@ NotUsing NotUsing - + true true true @@ -362,12 +362,10 @@ true true - - @@ -402,8 +400,7 @@ - - + diff --git a/headless/Headless.vcxproj.filters b/headless/Headless.vcxproj.filters index b4c275e974..fab3a363c6 100644 --- a/headless/Headless.vcxproj.filters +++ b/headless/Headless.vcxproj.filters @@ -16,19 +16,17 @@ Windows - + Other Platforms Windows - Windows - @@ -37,10 +35,9 @@ Windows - + Other Platforms - Windows diff --git a/headless/HeadlessHost.cpp b/headless/HeadlessHost.cpp deleted file mode 100644 index ddd3e4008d..0000000000 --- a/headless/HeadlessHost.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2017- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -#include "Common/File/FileUtil.h" -#include "Common/Log.h" -#include "Common/StringUtils.h" -#include "Core/CoreParameter.h" -#include "Core/System.h" -#include "GPU/GPUCommon.h" -#include "headless/Compare.h" -#include "headless/HeadlessHost.h" - diff --git a/headless/HeadlessHost.h b/headless/HeadlessHost.h deleted file mode 100644 index fd17dfb829..0000000000 --- a/headless/HeadlessHost.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -#pragma once - -#include "Common/CommonTypes.h" -#include "Common/File/Path.h" - -#include "Core/CoreParameter.h" - -class HeadlessHost { -public: - virtual ~HeadlessHost() {} - virtual bool InitGraphics(std::string *error_message, GraphicsContext **ctx, GPUCore core) {return false;} - virtual void ShutdownGraphics() {} - - virtual void SwapBuffers() {} - -protected: - GPUCore gpuCore_; - GraphicsContext *gfx_ = nullptr; -}; diff --git a/headless/SDLHeadlessHost.cpp b/headless/SDLHeadlessGLGraphicsContext.cpp similarity index 55% rename from headless/SDLHeadlessHost.cpp rename to headless/SDLHeadlessGLGraphicsContext.cpp index 294dfdadea..e376780dec 100644 --- a/headless/SDLHeadlessHost.cpp +++ b/headless/SDLHeadlessGLGraphicsContext.cpp @@ -22,11 +22,10 @@ #include "ppsspp_config.h" #include -#include "headless/SDLHeadlessHost.h" +#include "headless/SDLHeadlessGLGraphicsContext.h" #include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/OpenGL/GLFeatures.h" #include "Common/GPU/thin3d_create.h" -#include "Common/GPU/OpenGL/GLRenderManager.h" #include "Common/File/VFS/VFS.h" #include "Common/File/VFS/DirectoryReader.h" #include "Common/GPU/GraphicsContext.h" @@ -37,64 +36,36 @@ #include "GPU/GPUState.h" const bool WINDOW_VISIBLE = false; -const int WINDOW_WIDTH = 480; -const int WINDOW_HEIGHT = 272; -SDL_Window *CreateHiddenWindow() { +void *CreateHiddenWindow(int w, int h) { Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS; if (!WINDOW_VISIBLE) { flags |= SDL_WINDOW_HIDDEN; } - return SDL_CreateWindow("PPSSPPHeadless", WINDOW_WIDTH, WINDOW_HEIGHT, flags); + return SDL_CreateWindow("PPSSPPHeadless", w, h, flags); } -class GLDummyGraphicsContext : public GraphicsContext { -public: - GLDummyGraphicsContext() {} - ~GLDummyGraphicsContext() { delete draw_; } - - bool InitAPI(void *wnd, std::string *deviceNameSetting, std::string *errorMessage) override; - bool InitSurface(WindowSystem winsys, void *data1, void *data2, std::string *errorMessage) override; - - - void ShutdownSurface() override { - delete draw_; - draw_ = nullptr; - - SDL_GL_DestroyContext(glContext_); - glContext_ = nullptr; - SDL_DestroyWindow(screen_); - screen_ = nullptr; - +void DestroyHiddenWindow(void *window) { + if (window) { + SDL_DestroyWindow(static_cast(window)); SDL_Quit(); } +} - Draw::DrawContext *GetDrawContext() override { - return draw_; - } +void SDLHeadlessGLGraphicsContext::ShutdownSurface() { + delete draw_; + draw_ = nullptr; - void ThreadStart() override { - renderManager_->ThreadStart(draw_); - } + SDL_GL_DestroyContext(glContext_); + glContext_ = nullptr; +} - bool ThreadFrame() override { - return renderManager_->ThreadFrame(); - } +bool SDLHeadlessGLGraphicsContext::InitSurface(WindowSystem winsys, void *data1, void *data2, std::string *errorMessage) { + // Not used in this context. + return true; +} - void ThreadEnd() override { - renderManager_->ThreadEnd(); - } - - void Resize() override {} - -private: - Draw::DrawContext *draw_ = nullptr; - GLRenderManager *renderManager_ = nullptr; - SDL_Window *screen_; - SDL_GLContext glContext_; -}; - -bool GLDummyGraphicsContext::InitAPI(void *wnd, std::string *deviceName, std::string *errorMessage) { +bool SDLHeadlessGLGraphicsContext::InitAPI(void *wnd, std::string *deviceName, std::string *errorMessage) { SDL_Init(SDL_INIT_VIDEO); // TODO @@ -159,57 +130,4 @@ bool GLDummyGraphicsContext::InitAPI(void *wnd, std::string *deviceName, std::st return success; } -bool GLDummyGraphicsContext::InitSurface(WindowSystem winsys, void *data1, void *data2, std::string *errorMessage) { - // Not used in this context. - return true; -} - -bool SDLHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext **ctx, GPUCore core) { - GraphicsContext *graphicsContext = new GLDummyGraphicsContext(); - *ctx = graphicsContext; - gfx_ = graphicsContext; - - std::thread th([&]{ - SetCurrentThreadName("SDL-RenderThread"); - std::string errorMessage; - gfx_->InitAPI(nullptr, nullptr, &errorMessage); - while (threadState_ == RenderThreadState::IDLE) - sleep_ms(1, "sdl-idle-poll"); - threadState_ = RenderThreadState::STARTING; - - std::string err; - if (!gfx_->InitSurface(WINDOWSYSTEM_NONE, nullptr, nullptr, &err)) { - threadState_ = RenderThreadState::START_FAILED; - return; - } - gfx_->ThreadStart(); - threadState_ = RenderThreadState::STARTED; - - while (gfx_->ThreadFrame()) {} - - threadState_ = RenderThreadState::STOPPING; - gfx_->ThreadEnd(); - gfx_->ShutdownSurface(); - threadState_ = RenderThreadState::STOPPED; - }); - th.detach(); - - threadState_ = RenderThreadState::START_REQUESTED; - while (threadState_ == RenderThreadState::START_REQUESTED || threadState_ == RenderThreadState::STARTING) - sleep_ms(1, "sdl-start-poll"); - - return threadState_ == RenderThreadState::STARTED; -} - -void SDLHeadlessHost::ShutdownGraphics() { - gfx_->NotifyEmuThreadExit(); - - gfx_->ShutdownAPI(); - delete gfx_; - gfx_ = nullptr; -} - -void SDLHeadlessHost::SwapBuffers() { -} - #endif diff --git a/headless/SDLHeadlessGLGraphicsContext.h b/headless/SDLHeadlessGLGraphicsContext.h new file mode 100644 index 0000000000..2e1aed38c5 --- /dev/null +++ b/headless/SDLHeadlessGLGraphicsContext.h @@ -0,0 +1,66 @@ +// Copyright (c) 2017- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#ifdef SDL + +#include + +#include "headless/HeadlessHost.h" + +#include "Common/GPU/OpenGL/GLRenderManager.h" + +class SDLHeadlessGLGraphicsContext : public GraphicsContext { +public: + SDLHeadlessGLGraphicsContext() {} + ~SDLHeadlessGLGraphicsContext() { delete draw_; } + + bool InitAPI(void *wnd, std::string *deviceNameSetting, std::string *errorMessage) override; + bool InitSurface(WindowSystem winsys, void *data1, void *data2, std::string *errorMessage) override; + + void ShutdownSurface() override; + + Draw::DrawContext *GetDrawContext() override { + return draw_; + } + + void ThreadStart() override { + renderManager_->ThreadStart(draw_); + } + + bool ThreadFrame(bool waitIfEmpty) override { + return renderManager_->ThreadFrame(waitIfEmpty); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } + + void Resize() override {} + +private: + Draw::DrawContext *draw_ = nullptr; + GLRenderManager *renderManager_ = nullptr; + SDL_Window *screen_; + SDL_GLContext glContext_; +}; + +void *CreateHiddenWindow(int w, int h); +void DestroyHiddenWindow(void *window); + +#endif diff --git a/headless/SDLHeadlessHost.h b/headless/SDLHeadlessHost.h deleted file mode 100644 index 0b4d662fde..0000000000 --- a/headless/SDLHeadlessHost.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2017- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -#pragma once - -#ifdef SDL - -#include "headless/HeadlessHost.h" - -#undef HEADLESSHOST_CLASS -#define HEADLESSHOST_CLASS SDLHeadlessHost - -#include - -class SDLHeadlessHost : public HeadlessHost -{ -public: - bool InitGraphics(std::string *error_message, GraphicsContext **ctx, GPUCore core) override; - void ShutdownGraphics() override; - - void SwapBuffers() override; - -protected: - enum class RenderThreadState { - IDLE, - START_REQUESTED, - STARTING, - START_FAILED, - STARTED, - STOP_REQUESTED, - STOPPING, - STOPPED, - }; - - volatile RenderThreadState threadState_ = RenderThreadState::IDLE; -}; - -#endif diff --git a/headless/WindowsHeadlessHost.cpp b/headless/WindowsHeadlessHost.cpp index 5e08b06f8b..cf08e7f9ee 100644 --- a/headless/WindowsHeadlessHost.cpp +++ b/headless/WindowsHeadlessHost.cpp @@ -16,36 +16,13 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "ppsspp_config.h" -#include - #include "headless/WindowsHeadlessHost.h" -#include "Common/GPU/OpenGL/GLCommon.h" -#include "Common/GPU/OpenGL/GLFeatures.h" -#include "Common/File/VFS/VFS.h" -#include "Common/File/VFS/DirectoryReader.h" - #include "Common/CommonWindows.h" -#include "Common/Log.h" -#include "Common/File/FileUtil.h" -#include "Common/TimeUtil.h" -#include "Common/Thread/ThreadUtil.h" - -#include "Core/CoreParameter.h" -#include "Core/System.h" -#include "GPU/GPUCommon.h" -#include "GPU/GPUState.h" -#if PPSSPP_API(ANY_GL) -#include "Windows/GPU/WindowsGLContext.h" -#endif -#include "Windows/GPU/D3D11Context.h" -#include "Common/GPU/Vulkan/VulkanGraphicsContext.h" const bool WINDOW_VISIBLE = false; -const int WINDOW_WIDTH = 480; -const int WINDOW_HEIGHT = 272; -HWND CreateHiddenWindow() { +void *CreateHiddenWindow(int w, int h) { static WNDCLASSEX wndClass = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_OWNDC, @@ -63,108 +40,17 @@ HWND CreateHiddenWindow() { RegisterClassEx(&wndClass); DWORD style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP; - return CreateWindowEx(0, L"PPSSPPHeadless", L"PPSSPPHeadless", style, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, NULL, NULL); -} - -bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext **ctx, GPUCore core) { - hWnd = CreateHiddenWindow(); - gpuCore_ = core; + HWND wnd = CreateWindowEx(0, L"PPSSPPHeadless", L"PPSSPPHeadless", style, CW_USEDEFAULT, CW_USEDEFAULT, w, h, NULL, NULL, NULL, NULL); if (WINDOW_VISIBLE) { - ShowWindow(hWnd, TRUE); - SetFocus(hWnd); + ShowWindow(wnd, TRUE); + SetFocus(wnd); } - - GraphicsContext *graphicsContext = nullptr; - switch (gpuCore_) { - case GPUCORE_GLES: -#if PPSSPP_API(ANY_GL) - case GPUCORE_SOFTWARE: - graphicsContext = new WindowsGLContext(); - break; -#endif - case GPUCORE_DIRECTX11: - graphicsContext = new D3D11Context(); - break; - - case GPUCORE_VULKAN: - graphicsContext = new VulkanGraphicsContext(); - break; - default: - _assert_(false); - break; - } - - if (graphicsContext->InitAPI(hWnd, nullptr, error_message)) { - // Success - } else { - delete graphicsContext; - *ctx = nullptr; - gfx_ = nullptr; - return false; - } - - bool needRenderThread = gpuCore_ == GPUCORE_GLES; - - if (needRenderThread) { - renderThread_ = std::thread([this]{ - SetCurrentThreadName("RenderThread"); - while (threadState_ == RenderThreadState::IDLE) - sleep_ms(1, "render-thread-idle-poll"); - threadState_ = RenderThreadState::STARTING; - - std::string err; - if (!gfx_->InitSurface(WINDOWSYSTEM_WIN32, nullptr, hWnd, &err)) { - threadState_ = RenderThreadState::START_FAILED; - return; - } - gfx_->ThreadStart(); - threadState_ = RenderThreadState::STARTED; - - while (gfx_->ThreadFrame()) {}; - - threadState_ = RenderThreadState::STOPPING; - gfx_->ThreadEnd(); - gfx_->ShutdownSurface(); - threadState_ = RenderThreadState::STOPPED; - }); - } else { - if (graphicsContext->InitSurface(WINDOWSYSTEM_WIN32, NULL, hWnd, error_message)) { - *ctx = graphicsContext; - gfx_ = graphicsContext; - } else { - delete graphicsContext; - *ctx = nullptr; - gfx_ = nullptr; - return false; - } - } - - if (needRenderThread) { - threadState_ = RenderThreadState::START_REQUESTED; - while (threadState_ == RenderThreadState::START_REQUESTED || threadState_ == RenderThreadState::STARTING) - sleep_ms(1, "render-thread-start-poll"); - - return threadState_ == RenderThreadState::STARTED; - } - - return true; + return static_cast(wnd); } -void WindowsHeadlessHost::ShutdownGraphics() { - if (renderThread_.joinable()) { - gfx_->NotifyEmuThreadExit(); - renderThread_.join(); - } else { - gfx_->ShutdownSurface(); +void DestroyHiddenWindow(void *window) { + if (window) { + DestroyWindow(static_cast(window)); } - - gfx_->ShutdownAPI(); - - delete gfx_; - gfx_ = nullptr; - DestroyWindow(hWnd); - hWnd = NULL; } - -void WindowsHeadlessHost::SwapBuffers() {} diff --git a/headless/WindowsHeadlessHost.h b/headless/WindowsHeadlessHost.h index 7b966506dc..e32e78fede 100644 --- a/headless/WindowsHeadlessHost.h +++ b/headless/WindowsHeadlessHost.h @@ -17,36 +17,6 @@ #pragma once -#include "headless/HeadlessHost.h" -#include - -#undef HEADLESSHOST_CLASS -#define HEADLESSHOST_CLASS WindowsHeadlessHost - -#include "Common/CommonWindows.h" - -class WindowsHeadlessHost : public HeadlessHost { -public: - bool InitGraphics(std::string *error_message, GraphicsContext **ctx, GPUCore core) override; - void ShutdownGraphics() override; - - void SwapBuffers() override; - -protected: - enum class RenderThreadState { - IDLE, - START_REQUESTED, - STARTING, - START_FAILED, - STARTED, - STOP_REQUESTED, - STOPPING, - STOPPED, - }; - - HWND hWnd; - HDC hDC; - HGLRC hRC; - volatile RenderThreadState threadState_ = RenderThreadState::IDLE; - std::thread renderThread_; -}; +// Same API as for SDL +void *CreateHiddenWindow(int w, int h); +void DestroyHiddenWindow(void *window);