diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 3d6a3e9a25..5a0734b436 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -668,7 +668,10 @@ bool CreateDir(const Path &path, bool quiet) { return false; } - DEBUG_LOG(Log::IO, "CreateDir('%s')", path.c_str()); + if (!quiet) { + DEBUG_LOG(Log::IO, "CreateDir('%s')", path.c_str()); + } + #ifdef HAVE_LIBRETRO_VFS switch (LibretroMkdir(path.ToString().c_str())) { case -2: diff --git a/Common/GPU/GraphicsContext.h b/Common/GPU/GraphicsContext.h index 044bc784b0..a4b2667a41 100644 --- a/Common/GPU/GraphicsContext.h +++ b/Common/GPU/GraphicsContext.h @@ -6,6 +6,16 @@ #include "Common/GPU/thin3d.h" #include "Common/TimeUtil.h" +struct WindowDesc { + WindowSystem winsys{}; + void *data1 = nullptr; + void *data2 = nullptr; + + bool Valid() const { + return winsys != WindowSystem::WINDOWSYSTEM_UNINITIALIZED && data2; // Not all platforms use data1, so don't check it. + } +}; + // Init is done differently on each platform, and done close to the creation, so it's // expected to be implemented by subclasses. class GraphicsContext { diff --git a/Common/System/Application.h b/Common/System/Application.h index 511b45fd6e..3516ff07b5 100644 --- a/Common/System/Application.h +++ b/Common/System/Application.h @@ -6,11 +6,6 @@ class GraphicsContext; class Application { public: - WindowSystem windowSystem; - void *windowData1 = nullptr; - void *windowData2 = nullptr; // This should be the window, if there is one. - - void *Window() const { return windowData2; } virtual ~Application() = default; virtual bool InitGraphics(GraphicsContext *graphicsContext) = 0; virtual void ShutdownGraphics(GraphicsContext *graphicsContext) = 0; diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 0a8a876406..e7e04d344d 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -396,10 +396,10 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C gpuBackend = GPUBackend::OPENGL; softwareRendering = true; } else if (sscanf(restOfOption.c_str(), "gles%lg", &glVersionTemp) == 1 || sscanf(restOfOption.c_str(), "opengl%lg", &glVersionTemp) == 1) { - g_Config.iGPUBackend = (int)GPUBackend::OPENGL; - g_Config.bSoftwareRendering = false; + gpuBackend = GPUBackend::OPENGL; + softwareRendering = false; force_gl_version = int(10.0 * glVersionTemp + 0.5); - } else if (restOfOption == "gles") { + } else if (restOfOption == "gles" || restOfOption == "opengl") { gpuBackend = GPUBackend::OPENGL; softwareRendering = false; } else { diff --git a/Core/EmuThread.cpp b/Core/EmuThread.cpp index d3652fead9..6e5fc238eb 100644 --- a/Core/EmuThread.cpp +++ b/Core/EmuThread.cpp @@ -100,8 +100,6 @@ bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std application->InitGraphics(graphicsContext); - DEBUG_LOG(Log::Boot, "Done."); - g_inLoop = true; while (frame(graphicsContext)) { @@ -114,7 +112,6 @@ bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std // Process the shutdown. Without this, non-GL delays 800ms on shutdown. TODO: is this still an issue? Core_StateProcessed(); - frame(graphicsContext); g_inLoop = false; @@ -124,20 +121,17 @@ bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std } // Call InitAPI and ShutdownAPI outside this! -bool MainThreadFunc(GraphicsContext *graphicsContext, Application *application, WindowSystem windowSystem, void *windowData1, void *windowData2, std::function frame) { +bool MainThreadFunc(GraphicsContext *graphicsContext, Application *application, const WindowDesc &windowDesc, std::function frame) { // This is now the render thread, and will spawn the emu thread below. std::string error_string; - bool success = graphicsContext->InitSurface(windowSystem, windowData1, windowData2, &error_string); + bool success = graphicsContext->InitSurface(windowDesc.winsys, windowDesc.data1, windowDesc.data2, &error_string); if (!success) { return false; } - std::string errorMessage; if (graphicsContext->NeedsSeparateEmuThread()) { SetCurrentThreadName("RenderThread"); - DEBUG_LOG(Log::Boot, "Done."); - g_inLoop = true; std::thread emuThread = EmuThread_Start(graphicsContext, application, frame); graphicsContext->ThreadStart(); diff --git a/Core/EmuThread.h b/Core/EmuThread.h index 51cbb14fe2..d138af5a13 100644 --- a/Core/EmuThread.h +++ b/Core/EmuThread.h @@ -26,12 +26,13 @@ bool MainThread_Ready(); class GraphicsContext; +struct WindowDesc; // Doesn't take ownership of the graphicsContext, you have to delete it. // This should be used by platforms that launch a separate thread and doesn't // need to run a polling loop in it. // NOTE: Does take ownership over Application (which is just a wrapper for NativeInitGraphics/NativeShutdownGraphics/NativeFrame). -bool MainThreadFunc(GraphicsContext * graphicsContext, Application *application, WindowSystem windowSystem, void *windowData1, void *windowData2, std::function frame); +bool MainThreadFunc(GraphicsContext * graphicsContext, Application *application, const WindowDesc &windowDesc, std::function frame); // If you're not using MainThreadFunc, you can at least use these to manage a spinning EmuThread (that calls NativeFrame), // whether your graphics context requires multithreading or not. Then use RunMainLoop to implement your main loop for diff --git a/Windows/main.cpp b/Windows/main.cpp index 370c53f191..a02613cc2f 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -1236,7 +1236,12 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin System_SetWindowTitle(""); System_Notify(SystemNotification::UI); - std::thread mainThread = std::thread([]() { + WindowDesc desc; + desc.winsys = WINDOWSYSTEM_WIN32; + desc.data1 = MainWindow::GetHInstance(); + desc.data2 = MainWindow::GetHWND(); + + std::thread mainThread = std::thread([desc]() { // TODO: We can really merge all of this into MainThreadFunc std::string errorMessage; std::string *deviceNameSetting; @@ -1245,7 +1250,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin HandleGraphicsFailure(errorMessage); return; } - if (!MainThreadFunc(graphicsContext.get(), new NativeApplication(), WINDOWSYSTEM_WIN32, MainWindow::GetHInstance(), MainWindow::GetHWND(), + if (!MainThreadFunc(graphicsContext.get(), new NativeApplication(), desc, [](GraphicsContext *graphicsContext) { NativeFrame(graphicsContext); return GetUIState() != UISTATE_EXIT; diff --git a/headless/Headless.cpp b/headless/Headless.cpp index eee49a3ef9..669c3ae5f2 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -233,14 +233,12 @@ void System_SendDebugScreenshot(const uint8_t *data, int width, int height) { } } -static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **deviceSetting, WindowSystem *winsys) { +static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **deviceSetting) { #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; @@ -353,12 +351,11 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar } if (draw) { - draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "BackBuffer"); // Vulkan may get angry if we don't do a final present. if (gpu) { gpu->SetCurFramebufferDirty(true); gpu->PrepareCopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape)); - draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer"); + draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE}, "BackBuffer"); gpu->CopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape)); } @@ -497,8 +494,6 @@ public: void ShutdownGraphics(GraphicsContext *graphicsContext) override { graphicsContext->NotifyEmuThreadExit(); } -private: - std::function frameCallback_; }; int main(int argc, const char* argv[]) { @@ -656,7 +651,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 = gpuCore == GPUCORE_SOFTWARE; + g_Config.bSoftwareRendering = cmdLineOptions.softwareRendering.value_or(false); g_Config.bSoftwareRenderingJit = true; g_Config.iSplineBezierQuality = 2; g_Config.bHighQualityDepth = true; @@ -676,19 +671,33 @@ 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; + // Translate backend to core. We probably should consider merging these enums. + if (!g_Config.bSoftwareRendering && cmdLineOptions.gpuBackend.has_value()) { + switch (cmdLineOptions.gpuBackend.value()) { + case GPUBackend::OPENGL: + gpuCore = GPUCORE_GLES; + break; + case GPUBackend::DIRECT3D11: + gpuCore = GPUCORE_DIRECTX11; + break; + case GPUBackend::VULKAN: + gpuCore = GPUCORE_VULKAN; + break; + } + } // Time to set up graphics GraphicsContext *graphicsContext = nullptr; std::string *deviceSetting = nullptr; - WindowSystem winsys = WindowSystem::WINDOWSYSTEM_NONE; + WindowDesc windowDesc; if (g_Config.bSoftwareRendering) { // For software rendering, we just create a dummy graphics context (to share as much code as possible). + // We don't bother with a window. graphicsContext = new NullGraphicsContext(); } else { - window = CreateHiddenWindow(480, 272); - graphicsContext = CreateGraphicsContext(gpuCore, &deviceSetting, &winsys); + // TODO: Will we need a larger window for higher resolutions? Well, not if we use buffered rendering. + windowDesc = CreateHiddenWindow(480, 272); + graphicsContext = CreateGraphicsContext(gpuCore, &deviceSetting); 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"); @@ -785,31 +794,27 @@ 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(); + std::string errorMessage; + if (!graphicsContext->InitAPI(windowDesc.data2, deviceSetting, &errorMessage)) { + // No fallbacks in headless - if we can't run it, we can't. Let's not get confusing. + fprintf(stderr, "Failed to initialize graphics API: %s\n", errorMessage.c_str()); + return 1; } + int retval = 0; + MainThreadFunc(graphicsContext, new HeadlessApplication(), windowDesc, [&retval, &coreParameter, &testOptions, &testFilenames](GraphicsContext *graphicsContext) { + retval = RunTests(graphicsContext, coreParameter, testOptions, testFilenames); + return false; + }); - // Run the tests (or frame dumps), one after another. - const int retval = RunTests(graphicsContext, coreParameter, testOptions, testFilenames); + graphicsContext->ShutdownAPI(); if (cmdLineOptions.debuggerPort.has_value()) { ShutdownWebServer(); } - if (window) { - DestroyHiddenWindow(window); + if (windowDesc.Valid()) { + DestroyHiddenWindow(windowDesc); } g_VFS.Clear(); diff --git a/headless/SDLHeadlessGLGraphicsContext.cpp b/headless/SDLHeadlessGLGraphicsContext.cpp index e376780dec..eea1966610 100644 --- a/headless/SDLHeadlessGLGraphicsContext.cpp +++ b/headless/SDLHeadlessGLGraphicsContext.cpp @@ -37,17 +37,20 @@ const bool WINDOW_VISIBLE = false; -void *CreateHiddenWindow(int w, int h) { +WindowDesc CreateHiddenWindow(int w, int h) { Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS; if (!WINDOW_VISIBLE) { flags |= SDL_WINDOW_HIDDEN; } - return SDL_CreateWindow("PPSSPPHeadless", w, h, flags); + WindowDesc desc; + desc.data2 = SDL_CreateWindow("PPSSPPHeadless", w, h, flags); + desc.winsys = WindowSystem::WINDOWSYSTEM_SDL; + return desc; } -void DestroyHiddenWindow(void *window) { - if (window) { - SDL_DestroyWindow(static_cast(window)); +void DestroyHiddenWindow(WindowDesc window) { + if (window.data2) { + SDL_DestroyWindow(static_cast(window.data2)); SDL_Quit(); } } diff --git a/headless/WindowsHeadlessHost.cpp b/headless/WindowsHeadlessHost.cpp index cf08e7f9ee..81973668d3 100644 --- a/headless/WindowsHeadlessHost.cpp +++ b/headless/WindowsHeadlessHost.cpp @@ -19,10 +19,11 @@ #include "headless/WindowsHeadlessHost.h" #include "Common/CommonWindows.h" +#include "Common/GPU/GraphicsContext.h" const bool WINDOW_VISIBLE = false; -void *CreateHiddenWindow(int w, int h) { +WindowDesc CreateHiddenWindow(int w, int h) { static WNDCLASSEX wndClass = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_OWNDC, @@ -46,11 +47,15 @@ void *CreateHiddenWindow(int w, int h) { ShowWindow(wnd, TRUE); SetFocus(wnd); } - return static_cast(wnd); + WindowDesc desc; + desc.data1 = GetModuleHandle(NULL); + desc.data2 = wnd; + desc.winsys = WindowSystem::WINDOWSYSTEM_WIN32; + return desc; } -void DestroyHiddenWindow(void *window) { - if (window) { - DestroyWindow(static_cast(window)); +void DestroyHiddenWindow(WindowDesc window) { + if (window.data2) { + DestroyWindow(static_cast(window.data2)); } } diff --git a/headless/WindowsHeadlessHost.h b/headless/WindowsHeadlessHost.h index e32e78fede..9b380d870a 100644 --- a/headless/WindowsHeadlessHost.h +++ b/headless/WindowsHeadlessHost.h @@ -17,6 +17,8 @@ #pragma once +struct WindowDesc; + // Same API as for SDL -void *CreateHiddenWindow(int w, int h); -void DestroyHiddenWindow(void *window); +WindowDesc CreateHiddenWindow(int w, int h); +void DestroyHiddenWindow(WindowDesc window);