diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 34651fb05e..45a1fba82a 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -189,7 +189,7 @@ static const CommandLineParam g_autoParams[] = { {POFF(fullscreen), CmdParamType::BoolInverse, "windowed", '\0', "Force windowed mode", CmdLineMode::Application}, {POFF(startScreen), CmdParamType::String, "start-screen", '\0', "Start on a specific screen (e.g. 'gamesettings', 'touchscreentest')", CmdLineMode::Application}, {POFF(escapeExit), CmdParamType::Bool, "escape-exit", '\0', "Escape key exits the application", CmdLineMode::Application}, - {POFF(gameWindow), CmdParamType::Bool, "game-window", '\0', "Show the game in a second window, for testing multiple outputs (SDL, Metal only)", CmdLineMode::Application}, + {POFF(gameWindow), CmdParamType::Bool, "game-window", '\0', "Show the game in a second window, for testing multiple outputs (Vulkan, or Metal on Mac)", CmdLineMode::Application}, {POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Exit the application when the game ends, instead of returning to the menu", CmdLineMode::Application}, {POFF(appendConfig), CmdParamType::String, "appendconfig", '\0', "Merge config FILE into the current configuration"}, {POFF(root), CmdParamType::String, "root", 'r', "Mount directory as the root of host0:/."}, diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 594fcd34e0..a808a98ef3 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -1442,7 +1442,9 @@ void UpdateCursor() { SDL_SetCursor(g_builtinCursors[(int)curCursor]); } -// --game-window: a second window showing just the game, for testing multiple outputs. +// --game-window: a second window showing just the game. For now it's only a way to test multiple outputs on +// the desktop, standing in for a TV over AirPlay. Later, real multi-window support (like ImGui's multi-viewports) +// will take this over. static SDL_Window *g_gameWindow = nullptr; static int g_gameOutputId = 0; diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 966f84dfec..aa78f1f203 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -61,6 +61,8 @@ #include "Windows/Debugger/Debugger_MemoryDlg.h" #include "Common/GPU/GraphicsContext.h" +#include "Common/GPU/thin3d.h" +#include "Common/SysError.h" #include "Windows/main.h" #include "Windows/DinputDevice.h" @@ -1348,4 +1350,95 @@ namespace MainWindow { PostMessage(hwndMain, WM_USER_RUN_CALLBACK, reinterpret_cast(callback), reinterpret_cast(userdata)); } + // --game-window: a second window showing just the game. For now it's only a way to test multiple outputs on + // the desktop, standing in for a TV over AirPlay. Later, real multi-window support (like ImGui's multi-viewports) + // will take over these pieces: the window class, the input forwarding and the output lifetime. + static HWND hwndGame; + static int gameOutputId; + + static LRESULT CALLBACK GameWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_SIZE: + if (gameOutputId != 0 && wParam != SIZE_MINIMIZED) { + Native_ResizeOutput(gameOutputId, LOWORD(lParam), HIWORD(lParam)); + } + return 0; + + case WM_CLOSE: + // The window has to outlive the swapchain, so it stays until we quit. + Native_RemoveOutput(gameOutputId); + ShowWindow(hWnd, SW_HIDE); + return 0; + + case WM_ACTIVATE: + if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { + WindowsRawInput::GainFocus(); + g_InputManager.GainFocus(); + } else { + WindowsRawInput::LoseFocus(); + g_InputManager.LoseFocus(); + } + return 0; + + case WM_INPUT: + { + // Keys still go to the emulator. The mouse would get main window coordinates, so it's left out. + RAWINPUTHEADER header; + UINT size = sizeof(header); + if (GetRawInputData((HRAWINPUT)lParam, RID_HEADER, &header, &size, sizeof(RAWINPUTHEADER)) != (UINT)-1 && header.dwType == RIM_TYPEKEYBOARD) { + return WindowsRawInput::Process(hWnd, wParam, lParam); + } + return DefWindowProc(hWnd, message, wParam, lParam); + } + + case WM_CHAR: + return WindowsRawInput::ProcessChar(hWnd, wParam, lParam); + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } + + void CreateGameWindow() { + if ((GPUBackend)g_Config.iGPUBackend != GPUBackend::VULKAN) { + ERROR_LOG(Log::System, "--game-window needs Vulkan"); + return; + } + + static constexpr const wchar_t *szGameWindowClass = L"PPSSPPGameWnd"; + WNDCLASSEX wcex{}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = (WNDPROC)GameWndProc; + wcex.hInstance = hInst; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wcex.lpszClassName = szGameWindowClass; + wcex.hIcon = LoadIcon(hInst, (LPCTSTR)IDI_PPSSPP); + wcex.hIconSm = (HICON)LoadImage(hInst, (LPCTSTR)IDI_PPSSPP, IMAGE_ICON, 16, 16, LR_SHARED); + RegisterClassEx(&wcex); + + RECT rc{ 0, 0, 960, 544 }; + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); + // No owner: an owned window is destroyed along with the main window, which can be before the swapchain is. + hwndGame = CreateWindowEx(0, szGameWindowClass, L"PPSSPP game", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInst, NULL); + if (!hwndGame) { + ERROR_LOG(Log::System, "Error creating the game window: %s", GetLastErrorMsg().c_str()); + return; + } + + RECT client; + GetClientRect(hwndGame, &client); + Draw::SwapchainDesc desc{}; + desc.winsys = WINDOWSYSTEM_WIN32; + desc.data1 = hInst; + desc.data2 = hwndGame; + desc.width = client.right - client.left; + desc.height = client.bottom - client.top; + desc.tag = "GameWindow"; + gameOutputId = Native_AddOutput(OutputRole::Game, desc); + + ShowWindow(hwndGame, SW_SHOWNOACTIVATE); + } + } // namespace MainWindow diff --git a/Windows/MainWindow.h b/Windows/MainWindow.h index d48b0a3724..31035e13a2 100644 --- a/Windows/MainWindow.h +++ b/Windows/MainWindow.h @@ -80,6 +80,7 @@ namespace MainWindow void SetWindowSize(int zoom); void RunCallbackInWndProc(void (*callback)(void *window, void *userdata), void *userdata); void SetKeepScreenBright(bool keepBright); + void CreateGameWindow(); bool ConfirmAction(HWND hWnd, bool actionIsReset); } diff --git a/Windows/main.cpp b/Windows/main.cpp index 6ddb0441d3..91284155e5 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -1264,6 +1264,10 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin HWND hwndMain = MainWindow::GetHWND(); Achievements::InitializeRAIntegration(hwndMain); + if (cmdLineOptions.gameWindow.value_or(false)) { + MainWindow::CreateGameWindow(); + } + //initialize custom controls CtrlDisAsmView::init(); CtrlMemView::init();