mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Headless: Make all GPU backends work correctly on Windows
This commit is contained in:
1 parent
25cf52176f
commit
551e4cd0ab
11 files changed
+85
-62
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-3
@@ -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 {
|
||||
|
||||
+2
-8
@@ -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<bool(GraphicsContext *)> frame) {
|
||||
bool MainThreadFunc(GraphicsContext *graphicsContext, Application *application, const WindowDesc &windowDesc, std::function<bool(GraphicsContext *)> 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();
|
||||
|
||||
+2
-1
@@ -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<bool(GraphicsContext *)> frame);
|
||||
bool MainThreadFunc(GraphicsContext * graphicsContext, Application *application, const WindowDesc &windowDesc, std::function<bool(GraphicsContext *)> 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
|
||||
|
||||
+7
-2
@@ -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;
|
||||
|
||||
+35
-30
@@ -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<void()> 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();
|
||||
|
||||
@@ -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<SDL_Window *>(window));
|
||||
void DestroyHiddenWindow(WindowDesc window) {
|
||||
if (window.data2) {
|
||||
SDL_DestroyWindow(static_cast<SDL_Window *>(window.data2));
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void *>(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<HWND>(window));
|
||||
void DestroyHiddenWindow(WindowDesc window) {
|
||||
if (window.data2) {
|
||||
DestroyWindow(static_cast<HWND>(window.data2));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user