diff --git a/CMakeLists.txt b/CMakeLists.txt index 21442f5016..b91acbe094 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1049,6 +1049,7 @@ else() SDL/SDLJoystick.h SDL/SDLJoystick.cpp SDL/SDLMain.cpp + SDL/SDLUtil.cpp SDL/SDLGLGraphicsContext.cpp ) if(SDL_TTF_LIB_TARGET) @@ -1329,13 +1330,22 @@ if(HEADLESS) headless/Headless.cpp headless/Compare.cpp headless/Compare.h - headless/SDLHeadlessGLGraphicsContext.cpp - headless/SDLHeadlessGLGraphicsContext.h ) + if(SDL_LIB_TARGET) + list(APPEND HeadlessSource + headless/SDLHeadlessGLGraphicsContext.cpp + headless/SDLHeadlessGLGraphicsContext.h + # TODO: Break these out into a library instead of cross linking like this. + SDL/SDLUtil.cpp + SDL/SDLUtil.h + ) + endif() if(APPLE) list(APPEND HeadlessSource Common/Render/Text/draw_text_cocoa.mm Common/Render/Text/draw_text_cocoa.h + SDL/SDLCocoaMetalLayer.h + SDL/SDLCocoaMetalLayer.mm ) set_source_files_properties(Common/Render/Text/draw_text_cocoa.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 7be171f1d3..96c23080d6 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -91,6 +91,23 @@ const char *VulkanImageLayoutToString(VkImageLayout imageLayout) { } } +const char *WindowSystemToString(WindowSystem winsys) { + switch (winsys) { + case WINDOWSYSTEM_UNINITIALIZED: return "UNINITIALIZED"; + case WINDOWSYSTEM_WIN32: return "WIN32"; + case WINDOWSYSTEM_ANDROID: return "ANDROID"; + case WINDOWSYSTEM_METAL_EXT: return "METAL_EXT"; + case WINDOWSYSTEM_XLIB: return "XLIB"; + case WINDOWSYSTEM_XCB: return "XCB"; + case WINDOWSYSTEM_WAYLAND: return "WAYLAND"; + case WINDOWSYSTEM_DISPLAY: return "DISPLAY"; + case WINDOWSYSTEM_SDL: return "SDL"; + case WINDOWSYSTEM_NONE: return "NONE"; + default: + return "UNKNOWN"; + } +} + VulkanContext::VulkanContext() { // Do nothing here. } @@ -970,9 +987,9 @@ VkResult VulkanContext::ReinitSurface() { surface_ = VK_NULL_HANDLE; } - INFO_LOG(Log::G3D, "Creating Vulkan surface for window (data1=%p data2=%p)", winsysData1_, winsysData2_); + INFO_LOG(Log::G3D, "Creating Vulkan surface for window (winsys=%s data1=%p data2=%p)", WindowSystemToString(winsys_), winsysData1_, winsysData2_); - VkResult retval = VK_SUCCESS; + VkResult retval = VK_SUCCESS; switch (winsys_) { #ifdef _WIN32 @@ -1233,7 +1250,7 @@ VkResult VulkanContext::ReinitSurface() { #endif default: - _assert_msg_(false, "Vulkan support for chosen window system not implemented"); + _assert_msg_(false, "Vulkan support for chosen window system (%s) not implemented", WindowSystemToString(winsys_)); return VK_ERROR_INITIALIZATION_FAILED; } diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 85a3c3a989..30a40ae277 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -67,6 +67,7 @@ SDLJoystick *joystick = NULL; #include "Core/Config.h" #include "Core/ConfigValues.h" #include "SDLGLGraphicsContext.h" +#include "SDLUtil.h" #include @@ -1726,58 +1727,6 @@ void UpdateSDLCursor() { #endif } -bool DetermineVulkanWindowSystem(SDL_Window *window, WindowSystem *windowSystem, void **data1, void **data2, std::string *errorMessage) { - _dbg_assert_(window); - SDL_PropertiesID windowProps = SDL_GetWindowProperties(window); - void *x11Display = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr); - if (x11Display != nullptr) { - intptr_t x11Window = (intptr_t)SDL_GetNumberProperty(windowProps, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); -#if defined(VK_USE_PLATFORM_XLIB_KHR) - *windowSystem = WINDOWSYSTEM_XLIB; - *data1 = x11Display; - *data2 = (void *)x11Window; -#elif defined(VK_USE_PLATFORM_XCB_KHR) - *windowSystem = WINDOWSYSTEM_XCB; - *data1 = (void *)XGetXCBConnection((Display *)x11Display); - *data2 = (void *)x11Window; -#endif - return true; - } -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - void *waylandDisplay = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr); - void *waylandSurface = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); - if (waylandDisplay != nullptr && waylandSurface != nullptr) { - *windowSystem = WINDOWSYSTEM_WAYLAND; - *data1 = waylandDisplay; - *data2 = waylandSurface; - return true; - } -#elif defined(VK_USE_PLATFORM_METAL_EXT) -#if PPSSPP_PLATFORM(MAC) - void *cocoaWindow = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); - if (cocoaWindow != nullptr) { - *windowSystem = WINDOWSYSTEM_METAL_EXT; - *data1 = makeWindowMetalCompatible(cocoaWindow); - *data2 = nullptr; - return true; - } -#else - // This path is currently not used as we do not use SDL on iOS, but it is here for completeness. - void *uikitWindow = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, nullptr); - if (uikitWindow != nullptr) { - *windowSystem = WINDOWSYSTEM_METAL_EXT; - *data1 = makeWindowMetalCompatible(uikitWindow); - *data2 = nullptr; - return true; - } -#endif -#endif // VK_USE_PLATFORM_METAL_EXT - if (errorMessage) { - *errorMessage = "Unable to determine Vulkan window system from SDL3 window properties"; - } - return false; -} - #ifdef _WIN32 #undef main #endif @@ -2007,19 +1956,16 @@ int main(int argc, char *argv[]) { } SDL_Window *window = nullptr; + WindowDesc windowDesc; auto initializeBackend = [&](GPUBackend backend, GraphicsContext **graphicsContext, std::string *errorMessage) -> bool { - // Surface init params. - WindowSystem windowSystem = WINDOWSYSTEM_NONE; - void *data1 = nullptr; - void *data2 = nullptr; - GraphicsContext *ctx = nullptr; if (backend == GPUBackend::OPENGL) { SDL_GLContext glContext = nullptr; window = CreateSDLGLWindowAndContext(x, y, w, h, mode, cmdLineOptions.force_gl_version, &glContext, errorMessage); - data1 = (void *)window; - data2 = (void *)glContext; + windowDesc.winsys = WINDOWSYSTEM_SDL; + windowDesc.data1 = (void *)window; + windowDesc.data2 = (void *)glContext; ctx = new SDLGLGraphicsContext(); } else { @@ -2038,7 +1984,7 @@ int main(int argc, char *argv[]) { } // Overwrite the surface init params with what we need for Vulkan.. - if (!DetermineVulkanWindowSystem(window, &windowSystem, &data1, &data2, errorMessage)) { + if (!DetermineVulkanWindowSystem(window, &windowDesc, errorMessage)) { return false; } // NOTE : This should match the lines below in the Vulkan case. @@ -2061,7 +2007,7 @@ int main(int argc, char *argv[]) { }); } - if (!ctx->InitSurface(windowSystem, data1, data2, errorMessage)) { + if (!ctx->InitSurface(windowDesc.winsys, windowDesc.data1, windowDesc.data2, errorMessage)) { fprintf(stderr, "Surface creation failed: %s\n", errorMessage->c_str()); return false; } diff --git a/SDL/SDLUtil.cpp b/SDL/SDLUtil.cpp new file mode 100644 index 0000000000..d363f0790b --- /dev/null +++ b/SDL/SDLUtil.cpp @@ -0,0 +1,66 @@ +#include + +#include "ppsspp_config.h" + +#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) +#include "SDL/SDLCocoaMetalLayer.h" +#endif + +#include "Common/GPU/MiscTypes.h" +#include "Common/GPU/Vulkan/VulkanLoader.h" +#include "Common/GPU/Vulkan/VulkanContext.h" +#include "Common/GPU/GraphicsContext.h" + +#include "SDL/SDLUtil.h" + +bool DetermineVulkanWindowSystem(SDL_Window *window, WindowDesc *desc, std::string *errorMessage) { + _dbg_assert_(window); + SDL_PropertiesID windowProps = SDL_GetWindowProperties(window); + void *x11Display = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr); + if (x11Display != nullptr) { + intptr_t x11Window = (intptr_t)SDL_GetNumberProperty(windowProps, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); +#if defined(VK_USE_PLATFORM_XLIB_KHR) + desc->winsys = WINDOWSYSTEM_XLIB; + desc->data1 = x11Display; + desc->data2 = (void *)x11Window; +#elif defined(VK_USE_PLATFORM_XCB_KHR) + desc->winsys = WINDOWSYSTEM_XCB; + desc->data1 = (void *)XGetXCBConnection((Display *)x11Display); + desc->data2 = (void *)x11Window; +#endif + return true; + } +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + void *waylandDisplay = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr); + void *waylandSurface = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); + if (waylandDisplay != nullptr && waylandSurface != nullptr) { + desc->winsys = WINDOWSYSTEM_WAYLAND; + desc->data1 = waylandDisplay; + desc->data2 = waylandSurface; + return true; + } +#elif defined(VK_USE_PLATFORM_METAL_EXT) +#if PPSSPP_PLATFORM(MAC) + void *cocoaWindow = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); + if (cocoaWindow != nullptr) { + desc->winsys = WINDOWSYSTEM_METAL_EXT; + desc->data1 = makeWindowMetalCompatible(cocoaWindow); + desc->data2 = nullptr; + return true; + } +#else + // This path is currently not used as we do not use SDL on iOS, but it is here for completeness. + void *uikitWindow = SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, nullptr); + if (uikitWindow != nullptr) { + desc->winsys = WINDOWSYSTEM_METAL_EXT; + desc->data1 = makeWindowMetalCompatible(uikitWindow); + desc->data2 = nullptr; + return true; + } +#endif +#endif // VK_USE_PLATFORM_METAL_EXT + if (errorMessage) { + *errorMessage = "Unable to determine Vulkan window system from SDL3 window properties"; + } + return false; +} diff --git a/SDL/SDLUtil.h b/SDL/SDLUtil.h new file mode 100644 index 0000000000..7300d4bcce --- /dev/null +++ b/SDL/SDLUtil.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "Common/GPU/MiscTypes.h" +#include + +struct WindowDesc; + +bool DetermineVulkanWindowSystem(SDL_Window *window, WindowDesc *desc, std::string *errorMessage); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 01ea6f6f49..a449d6b7b7 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -228,7 +228,15 @@ void System_SendDebugScreenshot(const uint8_t *data, int width, int height) { static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **deviceSetting) { #ifdef SDL *deviceSetting = nullptr; - return new SDLHeadlessGLGraphicsContext(); + switch (gpuCore) { + case GPUCORE_GLES: + return new SDLHeadlessGLGraphicsContext(); + case GPUCORE_VULKAN: + *deviceSetting = &g_Config.sVulkanDevice; + return new VulkanGraphicsContext(); + default: + return nullptr; + } #elif PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) switch (gpuCore) { #if PPSSPP_API(ANY_GL) @@ -244,8 +252,7 @@ static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **dev return new VulkanGraphicsContext(); case GPUCORE_SOFTWARE: default: - _assert_(false); - break; + return nullptr; } #elif PPSSPP_ARCH(LOONGARCH64) // The loongarch64 cross-compilation toolchain has no SDL3 packages available (see the @@ -260,8 +267,8 @@ static GraphicsContext *CreateGraphicsContext(GPUCore gpuCore, std::string **dev return nullptr; #else #error The Headless build is not supported on this platform. Please use SDL (Mac/Linux) or Windows (non-UWP). -#endif return nullptr; +#endif } struct AutoTestOptions { @@ -675,6 +682,7 @@ int main(int argc, const char* argv[]) { // Time to set up graphics GraphicsContext *graphicsContext = nullptr; std::string *deviceSetting = nullptr; + void *window = nullptr; WindowDesc windowDesc; if (g_Config.bSoftwareRendering) { // For software rendering, we just create a dummy graphics context (to share as much code as possible). @@ -686,7 +694,7 @@ int main(int argc, const char* argv[]) { return 1; #else // TODO: Will we need a larger window for higher resolutions? Well, not if we use buffered rendering. - windowDesc = CreateHiddenWindow(480, 272, cmdLineOptions.gpuBackend.value()); + window = CreateHiddenWindow(480, 272, cmdLineOptions.gpuBackend.value(), &windowDesc); if (!windowDesc.Valid()) { fprintf(stderr, "Failed to create a window for graphics context"); return 1; @@ -811,8 +819,8 @@ int main(int argc, const char* argv[]) { #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_ARCH(LOONGARCH64) // ... see above #else - if (windowDesc.Valid()) { - DestroyHiddenWindow(windowDesc); + if (window) { + DestroyHiddenWindow(window, windowDesc); } #endif diff --git a/headless/Headless.vcxproj b/headless/Headless.vcxproj index 5c242f2e47..4b5c3c192b 100644 --- a/headless/Headless.vcxproj +++ b/headless/Headless.vcxproj @@ -338,6 +338,14 @@ true true + + true + true + true + true + true + true + @@ -399,8 +407,23 @@ + + true + true + true + true + true + true + - + + true + true + true + true + true + true + diff --git a/headless/Headless.vcxproj.filters b/headless/Headless.vcxproj.filters index fab3a363c6..473d7eb70b 100644 --- a/headless/Headless.vcxproj.filters +++ b/headless/Headless.vcxproj.filters @@ -25,6 +25,9 @@ Windows + + Other Platforms + @@ -41,6 +44,9 @@ Windows + + Other Platforms + diff --git a/headless/SDLHeadlessGLGraphicsContext.cpp b/headless/SDLHeadlessGLGraphicsContext.cpp index 40e5aab5d2..3e81455d0c 100644 --- a/headless/SDLHeadlessGLGraphicsContext.cpp +++ b/headless/SDLHeadlessGLGraphicsContext.cpp @@ -34,10 +34,11 @@ #include "Core/Config.h" #include "Core/System.h" #include "GPU/GPUState.h" +#include "SDL/SDLUtil.h" const bool WINDOW_VISIBLE = false; -WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend) { +void *CreateHiddenWindow(int w, int h, GPUBackend backend, WindowDesc *desc) { Uint32 flags = SDL_WINDOW_BORDERLESS; if (backend == GPUBackend::OPENGL) { flags |= SDL_WINDOW_OPENGL; @@ -47,20 +48,33 @@ WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend) { 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) { + + SDL_Window *window = SDL_CreateWindow("PPSSPPHeadless", w, h, flags); + if (!window) { const char *err = SDL_GetError(); - printf("Failed to create offscreen window: %s\n", err ? err : "(unknown error)"); - return {}; + fprintf(stderr, "Failed to create offscreen window: %s\n", err ? err : "(unknown error)"); + return nullptr; } - return desc; + + if (backend == GPUBackend::VULKAN) { + // Overwrite the surface init params with what we need for Vulkan.. + std::string errorMessage; + if (!DetermineVulkanWindowSystem(window, desc, &errorMessage)) { + fprintf(stderr, "Failed to determine Vulkan window system: %s\n", errorMessage.c_str()); + SDL_DestroyWindow(window); + return nullptr; + } + } else { + desc->winsys = WindowSystem::WINDOWSYSTEM_SDL; + // For OpenGL, we just need the SDL_Window pointer. + desc->data2 = window; + } + return window; } -void DestroyHiddenWindow(WindowDesc window) { - if (window.data2) { - SDL_DestroyWindow(static_cast(window.data2)); +void DestroyHiddenWindow(void *window, WindowDesc desc) { + if (window) { + SDL_DestroyWindow(static_cast(window)); SDL_Quit(); } } diff --git a/headless/SDLHeadlessGLGraphicsContext.h b/headless/SDLHeadlessGLGraphicsContext.h index 95345c3fc9..e8fa3764af 100644 --- a/headless/SDLHeadlessGLGraphicsContext.h +++ b/headless/SDLHeadlessGLGraphicsContext.h @@ -67,7 +67,7 @@ private: SDL_GLContext glContext_; }; -WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend); -void DestroyHiddenWindow(WindowDesc window); +void *CreateHiddenWindow(int w, int h, GPUBackend backend, WindowDesc *desc); +void DestroyHiddenWindow(void *window, WindowDesc desc); #endif diff --git a/headless/WindowsHeadlessHost.cpp b/headless/WindowsHeadlessHost.cpp index bddc9a126b..43ef4cb803 100644 --- a/headless/WindowsHeadlessHost.cpp +++ b/headless/WindowsHeadlessHost.cpp @@ -23,7 +23,7 @@ const bool WINDOW_VISIBLE = false; -WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend) { +void *CreateHiddenWindow(int w, int h, GPUBackend backend, WindowDesc *desc) { static WNDCLASSEX wndClass = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_OWNDC, @@ -47,15 +47,15 @@ WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend) { ShowWindow(wnd, TRUE); SetFocus(wnd); } - WindowDesc desc; - desc.data1 = GetModuleHandle(NULL); - desc.data2 = wnd; - desc.winsys = WindowSystem::WINDOWSYSTEM_WIN32; - return desc; + desc->data1 = GetModuleHandle(NULL); // easy way to get the HINSTANCE. + desc->data2 = wnd; + desc->winsys = WindowSystem::WINDOWSYSTEM_WIN32; + return static_cast(wnd); } -void DestroyHiddenWindow(WindowDesc window) { - if (window.data2) { - DestroyWindow(static_cast(window.data2)); +void DestroyHiddenWindow(void *window, WindowDesc desc) { + if (window) { + DestroyWindow(static_cast(window)); + UnregisterClass(L"PPSSPPHeadless", static_cast(desc.data1)); } } diff --git a/headless/WindowsHeadlessHost.h b/headless/WindowsHeadlessHost.h index 3a108a148f..37b2bda757 100644 --- a/headless/WindowsHeadlessHost.h +++ b/headless/WindowsHeadlessHost.h @@ -22,5 +22,5 @@ struct WindowDesc; // Same API as for SDL -WindowDesc CreateHiddenWindow(int w, int h, GPUBackend backend); -void DestroyHiddenWindow(WindowDesc window); +void *CreateHiddenWindow(int w, int h, GPUBackend backend, WindowDesc *desc); +void DestroyHiddenWindow(void *window, WindowDesc desc);