From f68c77d785c8bbd4451e2a355be396cba05cf132 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 23 Sep 2026 15:22:15 +0200 Subject: [PATCH] SDL3 update (#787) * Update to SDL3 * Add XTest development dependency for SDL3 * Preserve native entry points with SDL3 * Document SDL3 XTest dependency --- .github/workflows/build.yml | 3 +- .gitmodules | 6 +- 3rdparty/CMakeLists.txt | 2 +- 3rdparty/SDL2 | 1 - 3rdparty/SDL3 | 1 + CMakeLists.txt | 5 +- README.md | 8 +- nix/devshell.nix | 5 +- src/graphics/presentation/systemOverlay.cpp | 137 +++--- src/graphics/presentation/systemOverlay.h | 3 +- .../presentation/window/hostInput.cpp | 131 +++--- src/graphics/presentation/window/hostInput.h | 4 +- .../presentation/window/swapchain.cpp | 4 +- .../presentation/window/vulkanWindow.cpp | 52 +-- src/graphics/presentation/window/window.cpp | 391 +++++++----------- .../presentation/window/windowInternal.h | 14 +- src/launcher/CMakeLists.txt | 2 +- src/launcher/src/configurationEditDialog.cpp | 11 +- src/libs/audio.cpp | 197 ++++----- src/libs/controller.cpp | 36 +- src/libs/libCes.cpp | 2 +- tests/KernelFileSystemTests.cpp | 8 +- 22 files changed, 469 insertions(+), 554 deletions(-) delete mode 160000 3rdparty/SDL2 create mode 160000 3rdparty/SDL3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa3013ff..a843b572 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -314,6 +314,7 @@ jobs: libxkbcommon-dev \ libxrandr-dev \ libxss-dev \ + libxtst-dev \ lld \ pkg-config \ wayland-protocols @@ -359,7 +360,7 @@ jobs: -DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log exit "${PIPESTATUS[0]}" - - name: Verify SDL2 backends + - name: Verify SDL3 backends shell: bash run: | status=0 diff --git a/.gitmodules b/.gitmodules index f7d27d54..4cdeb2df 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ -[submodule "3rdparty/SDL2"] - path = 3rdparty/SDL2 +[submodule "3rdparty/SDL3"] + path = 3rdparty/SDL3 url = https://github.com/libsdl-org/SDL.git - branch = SDL2 + branch = release-3.4.x [submodule "3rdparty/magic_enum"] path = 3rdparty/magic_enum url = https://github.com/Neargye/magic_enum.git diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 28578aab..19533be1 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -29,7 +29,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(xbyak zydis) set(SDL_SHARED OFF CACHE BOOL "" FORCE) -add_subdirectory(SDL2) +add_subdirectory(SDL3) add_library(Vulkan-Headers INTERFACE) add_library(Vulkan::Headers ALIAS Vulkan-Headers) diff --git a/3rdparty/SDL2 b/3rdparty/SDL2 deleted file mode 160000 index 4b69833b..00000000 --- a/3rdparty/SDL2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b69833bc54abf3dd3288d4aa7afbba527775e5b diff --git a/3rdparty/SDL3 b/3rdparty/SDL3 new file mode 160000 index 00000000..fa2c02bb --- /dev/null +++ b/3rdparty/SDL3 @@ -0,0 +1 @@ +Subproject commit fa2c02bb6e21974a89ea9824bc53c9932abe5f9c diff --git a/CMakeLists.txt b/CMakeLists.txt index fdb7a2e5..b2b95028 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12...4.3) +cmake_minimum_required(VERSION 3.16...4.3) if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0) set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies") @@ -331,7 +331,7 @@ if (KYTY_CLANG_CL) set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED") endif() -set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak) +set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak) # Linux system libraries. if(LINUX) @@ -341,7 +341,6 @@ endif() set(inc_headers ${KYTY_SOURCE_DIR} - ${KYTY_THIRD_PARTY_DIR}/SDL2/include ${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include ${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include ${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include diff --git a/README.md b/README.md index cea72a96..a485db37 100644 --- a/README.md +++ b/README.md @@ -166,15 +166,15 @@ The finished application and its runtime dependencies will be placed in ### Building on Linux -Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and -udev development packages SDL2 quietly configures itself without those backends, and the resulting +Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland and +udev development packages SDL3 quietly configures itself without those backends, and the resulting build has no working sound and no gamepad hotplug: ```bash sudo apt-get install --no-install-recommends \ clang lld ninja-build cmake git glslang-tools pkg-config \ libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \ - libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \ + libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \ libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols ``` @@ -204,7 +204,7 @@ The CMake source root is the repository root. ### Building on NixOS -A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL2 backend +A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL3 backend libraries. Enter it and configure exactly as on other Linux distributions; the shell exports `CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not needed: diff --git a/nix/devshell.nix b/nix/devshell.nix index bdfcb653..6e66557d 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,8 +1,7 @@ { pkgs }: let - # Build-time dependencies. Every -dev package the bundled SDL2 probes for must - # be listed here, otherwise SDL2 quietly drops the audio/Wayland/udev backends. + # Build-time dependencies for the bundled SDL3 audio, video and input backends. buildDeps = with pkgs; [ qt6.qtbase # Concurrent, Network, Widgets (launcher) vulkan-headers @@ -18,6 +17,7 @@ let libXi libXrandr libXScrnSaver + libXtst libxkbcommon # Wayland wayland @@ -46,6 +46,7 @@ let libXi libXrandr libXScrnSaver + libXtst libxkbcommon wayland libdecor diff --git a/src/graphics/presentation/systemOverlay.cpp b/src/graphics/presentation/systemOverlay.cpp index a2631a97..e3ceca84 100644 --- a/src/graphics/presentation/systemOverlay.cpp +++ b/src/graphics/presentation/systemOverlay.cpp @@ -1,6 +1,7 @@ #include "graphics/presentation/systemOverlay.h" -#include "SDL.h" +#include + #include "common/assert.h" #include "common/stringUtils.h" #include "graphics/host_gpu/graphicContext.h" @@ -176,6 +177,7 @@ bool g_input_multiline = false; bool g_input_lifecycle_active = false; bool g_controller_captured = false; OverlaySession g_session; +SDL_Window* g_input_window = nullptr; void ClearInputEvents() { std::scoped_lock lock(g_input_mutex); @@ -212,7 +214,7 @@ void RetryVisibilityWakeup() { } SDL_Event event {}; event.type = type; - if (SDL_PushEvent(&event) > 0) { + if (SDL_PushEvent(&event)) { g_missing_visibility_wakeups--; } } @@ -252,7 +254,7 @@ void RefreshVisibility() { std::scoped_lock input_lock(g_input_mutex); g_visibility_updates.push_back({snapshot.session, visible, capture_controller, capture_keyboard, text_input, multiline}); - if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) { + if (g_missing_visibility_wakeups != 0 || !SDL_PushEvent(&event)) { g_missing_visibility_wakeups++; } } @@ -268,16 +270,16 @@ void OnDialogVisibilityChanged(bool, uint64_t) { uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) { uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0); - if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100; - if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200; - if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400; - if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800; - if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000; - if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000; - if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000; - if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000; - if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000; - if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000; + if ((modifiers & SDL_KMOD_LCTRL) != 0) status |= 0x00000100; + if ((modifiers & SDL_KMOD_LSHIFT) != 0) status |= 0x00000200; + if ((modifiers & SDL_KMOD_LALT) != 0) status |= 0x00000400; + if ((modifiers & SDL_KMOD_LGUI) != 0) status |= 0x00000800; + if ((modifiers & SDL_KMOD_RCTRL) != 0) status |= 0x00001000; + if ((modifiers & SDL_KMOD_RSHIFT) != 0) status |= 0x00002000; + if ((modifiers & SDL_KMOD_RALT) != 0) status |= 0x00004000; + if ((modifiers & SDL_KMOD_RGUI) != 0) status |= 0x00008000; + if ((modifiers & SDL_KMOD_NUM) != 0) status |= 0x00010000; + if ((modifiers & SDL_KMOD_CAPS) != 0) status |= 0x00020000; return status; } @@ -392,14 +394,14 @@ float AlignmentPivot(Ime::Alignment alignment) { ImGuiKey ControllerButtonToKey(int button) { switch (button) { - case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown; - case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight; - case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft; - case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight; - case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown; + case SDL_GAMEPAD_BUTTON_SOUTH: return ImGuiKey_GamepadFaceDown; + case SDL_GAMEPAD_BUTTON_EAST: return ImGuiKey_GamepadFaceRight; + case SDL_GAMEPAD_BUTTON_WEST: return ImGuiKey_GamepadFaceLeft; + case SDL_GAMEPAD_BUTTON_NORTH: return ImGuiKey_GamepadFaceUp; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight; + case SDL_GAMEPAD_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown; default: return ImGuiKey_None; } } @@ -415,9 +417,11 @@ void CheckVulkanResult(VkResult result) { } // namespace -void InitializeSystemOverlayInput() { +void InitializeSystemOverlayInput(SDL_Window* window) { + EXIT_IF(window == nullptr); const Uint32 type = SDL_RegisterEvents(1); EXIT_IF(type == static_cast(-1)); + g_input_window = window; { std::scoped_lock lock(g_visibility_mutex); g_input_lifecycle_active = true; @@ -453,9 +457,10 @@ void ShutdownSystemOverlayInput() { g_input_controller = false; g_input_keyboard = false; g_input_multiline = false; - if (SDL_IsTextInputActive() == SDL_TRUE) { - SDL_StopTextInput(); + if (g_input_window != nullptr && SDL_TextInputActive(g_input_window)) { + SDL_StopTextInput(g_input_window); } + g_input_window = nullptr; } SystemOverlayVisualState GetSystemOverlayVisualState() noexcept { @@ -487,13 +492,13 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) { g_input_keyboard = update.capture_keyboard; g_input_multiline = update.multiline; if (update.text_input) { - SDL_StartTextInput(); - } else if (SDL_IsTextInputActive() == SDL_TRUE) { - SDL_StopTextInput(); + SDL_StartTextInput(g_input_window); + } else if (SDL_TextInputActive(g_input_window)) { + SDL_StopTextInput(g_input_window); } return true; } - if (event.type == SDL_CONTROLLERDEVICEREMOVED) { + if (event.type == SDL_EVENT_GAMEPAD_REMOVED) { if (g_input_active && g_input_controller) { QueueInput({InputKind::ResetController, g_input_session, 0, 0.0f, 0.0f}); } @@ -505,26 +510,26 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) { const auto session = g_input_session; const uint64_t generation = session.generation; - const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN || - event.type == SDL_CONTROLLERBUTTONUP || - event.type == SDL_CONTROLLERAXISMOTION; + const bool controller_event = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || + event.type == SDL_EVENT_GAMEPAD_BUTTON_UP || + event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION; if (controller_event && !g_input_controller) { return false; } - const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING || - event.type == SDL_KEYDOWN || event.type == SDL_KEYUP; + const bool keyboard_event = event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_TEXT_EDITING || + event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP; if (keyboard_event && !g_input_keyboard) { return false; } if (keyboard_event && session.kind == OverlayKind::Error) { - if (event.type == SDL_KEYDOWN && event.key.repeat == 0 && - (event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER)) { + if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && + (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) { ErrorDialog::HostAccept(generation); } return true; } switch (event.type) { - case SDL_TEXTINPUT: { + case SDL_EVENT_TEXT_INPUT: { const auto text = Utf8ToUtf16(event.text.text); if (!text.empty()) { auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode, @@ -535,26 +540,24 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) { } return true; } - case SDL_TEXTEDITING: return true; - case SDL_KEYDOWN: { - g_last_external_keycode = static_cast(event.key.keysym.scancode); - g_last_external_status = - ExternalKeyStatus(static_cast(event.key.keysym.mod), false); + case SDL_EVENT_TEXT_EDITING: return true; + case SDL_EVENT_KEY_DOWN: { + g_last_external_keycode = static_cast(event.key.scancode); + g_last_external_status = ExternalKeyStatus(event.key.mod, false); auto action = Ime::ExternalAction::Text; bool queue = true; - if (event.key.keysym.sym == SDLK_BACKSPACE) { + if (event.key.key == SDLK_BACKSPACE) { action = Ime::ExternalAction::Backspace; - } else if (event.key.keysym.sym == SDLK_LEFT) { + } else if (event.key.key == SDLK_LEFT) { action = Ime::ExternalAction::MoveLeft; - } else if (event.key.keysym.sym == SDLK_RIGHT) { + } else if (event.key.key == SDLK_RIGHT) { action = Ime::ExternalAction::MoveRight; - } else if (event.key.keysym.sym == SDLK_ESCAPE) { + } else if (event.key.key == SDLK_ESCAPE) { action = Ime::ExternalAction::Cancel; - } else if (event.key.keysym.sym == SDLK_RETURN || - event.key.keysym.sym == SDLK_KP_ENTER) { + } else if (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER) { action = g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept; - } else if (event.key.keysym.sym == SDLK_TAB) { + } else if (event.key.key == SDLK_TAB) { action = Ime::ExternalAction::None; } else { queue = false; @@ -566,28 +569,28 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) { } return true; } - case SDL_KEYUP: return true; - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - QueueInput({InputKind::Button, session, event.cbutton.button, - event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f}); + case SDL_EVENT_KEY_UP: return true; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + QueueInput({InputKind::Button, session, event.gbutton.button, + event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f}); return true; - case SDL_CONTROLLERAXISMOTION: - QueueInput({InputKind::Axis, session, event.caxis.axis, - static_cast(event.caxis.value), 0.0f}); + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + QueueInput({InputKind::Axis, session, event.gaxis.axis, + static_cast(event.gaxis.value), 0.0f}); return true; - case SDL_MOUSEMOTION: + case SDL_EVENT_MOUSE_MOTION: QueueInput({InputKind::MousePosition, session, 0, static_cast(event.motion.x), static_cast(event.motion.y)}); return true; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: QueueInput({InputKind::MousePosition, session, 0, static_cast(event.button.x), static_cast(event.button.y)}); QueueInput({InputKind::MouseButton, session, event.button.button, - event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f}); + event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f}); return true; - case SDL_MOUSEWHEEL: + case SDL_EVENT_MOUSE_WHEEL: QueueInput({InputKind::MouseWheel, session, 0, static_cast(event.wheel.x), static_cast(event.wheel.y)}); return true; @@ -681,9 +684,9 @@ struct SystemOverlay::Impl { case InputKind::Button: { const bool down = event.x != 0.0f; if (session.kind == OverlayKind::Ime && down) { - if (event.id == SDL_CONTROLLER_BUTTON_B) { + if (event.id == SDL_GAMEPAD_BUTTON_EAST) { Ime::HostCancel(session.generation); - } else if (event.id == SDL_CONTROLLER_BUTTON_Y) { + } else if (event.id == SDL_GAMEPAD_BUTTON_NORTH) { Ime::HostBackspace(session.generation); } } @@ -695,19 +698,19 @@ struct SystemOverlay::Impl { } case InputKind::Axis: { const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f); - if (event.id == SDL_CONTROLLER_AXIS_LEFTX) { + if (event.id == SDL_GAMEPAD_AXIS_LEFTX) { io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f, std::max(-value, 0.0f)); io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f, std::max(value, 0.0f)); - } else if (event.id == SDL_CONTROLLER_AXIS_LEFTY) { + } else if (event.id == SDL_GAMEPAD_AXIS_LEFTY) { io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f, std::max(-value, 0.0f)); io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f, std::max(value, 0.0f)); - } else if (event.id == SDL_CONTROLLER_AXIS_RIGHTX) { + } else if (event.id == SDL_GAMEPAD_AXIS_RIGHTX) { right_stick.x = std::abs(value) > 0.2f ? value : 0.0f; - } else if (event.id == SDL_CONTROLLER_AXIS_RIGHTY) { + } else if (event.id == SDL_GAMEPAD_AXIS_RIGHTY) { right_stick.y = std::abs(value) > 0.2f ? value : 0.0f; } break; diff --git a/src/graphics/presentation/systemOverlay.h b/src/graphics/presentation/systemOverlay.h index 4c6fe061..7fbb157f 100644 --- a/src/graphics/presentation/systemOverlay.h +++ b/src/graphics/presentation/systemOverlay.h @@ -7,6 +7,7 @@ #include union SDL_Event; +struct SDL_Window; namespace Libs::Graphics { @@ -17,7 +18,7 @@ struct SystemOverlayVisualState { uint64_t revision; }; -void InitializeSystemOverlayInput(); +void InitializeSystemOverlayInput(SDL_Window* window); void ShutdownSystemOverlayInput(); bool ProcessSystemOverlayInput(const SDL_Event& event); SystemOverlayVisualState GetSystemOverlayVisualState() noexcept; diff --git a/src/graphics/presentation/window/hostInput.cpp b/src/graphics/presentation/window/hostInput.cpp index 0fe939e5..c8b37547 100644 --- a/src/graphics/presentation/window/hostInput.cpp +++ b/src/graphics/presentation/window/hostInput.cpp @@ -1,12 +1,7 @@ #include "graphics/presentation/window/hostInput.h" -#include "SDL_error.h" -#include "SDL_events.h" -#include "SDL_keyboard.h" -#include "SDL_keycode.h" -#include "SDL_mouse.h" -#include "SDL_timer.h" -#include "SDL_video.h" +#include + #include "common/assert.h" #include "common/emulatorConfig.h" #include "common/logging/log.h" @@ -80,6 +75,7 @@ struct MouseJoystickState { }; MouseJoystickState g_mouse; +SDL_Window* g_mouse_window = nullptr; std::size_t ControlFromName(std::string_view name) { const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(), @@ -213,14 +209,14 @@ uint32_t DefaultKeyboardButton(int key_code) { case SDLK_LEFT: return Controller::PAD_BUTTON_LEFT; case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN; case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT; - case SDLK_j: return Controller::PAD_BUTTON_CROSS; - case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE; - case SDLK_k: return Controller::PAD_BUTTON_SQUARE; - case SDLK_l: return Controller::PAD_BUTTON_CIRCLE; - case SDLK_q: return Controller::PAD_BUTTON_L1; - case SDLK_e: return Controller::PAD_BUTTON_R1; - case SDLK_z: return Controller::PAD_BUTTON_L2; - case SDLK_c: return Controller::PAD_BUTTON_R2; + case SDLK_J: return Controller::PAD_BUTTON_CROSS; + case SDLK_I: return Controller::PAD_BUTTON_TRIANGLE; + case SDLK_K: return Controller::PAD_BUTTON_SQUARE; + case SDLK_L: return Controller::PAD_BUTTON_CIRCLE; + case SDLK_Q: return Controller::PAD_BUTTON_L1; + case SDLK_E: return Controller::PAD_BUTTON_R1; + case SDLK_Z: return Controller::PAD_BUTTON_L2; + case SDLK_C: return Controller::PAD_BUTTON_R2; case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3; case SDLK_LCTRL: return Controller::PAD_BUTTON_R3; case SDLK_RETURN: @@ -284,35 +280,35 @@ void DefaultKeyboardInput(int key_code, bool down) { switch (NormalizeKey(static_cast(key_code))) { case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return; case SDLK_TAB: SetTouchPad(0.75f, down); return; - case SDLK_a: + case SDLK_A: left.left = down; SetStickAxis(Controller::Axis::LeftX, left.left, left.right); return; - case SDLK_d: + case SDLK_D: left.right = down; SetStickAxis(Controller::Axis::LeftX, left.left, left.right); return; - case SDLK_w: + case SDLK_W: left.up = down; SetStickAxis(Controller::Axis::LeftY, left.up, left.down); return; - case SDLK_s: + case SDLK_S: left.down = down; SetStickAxis(Controller::Axis::LeftY, left.up, left.down); return; - case SDLK_f: + case SDLK_F: right.left = down; SetStickAxis(Controller::Axis::RightX, right.left, right.right); return; - case SDLK_h: + case SDLK_H: right.right = down; SetStickAxis(Controller::Axis::RightX, right.left, right.right); return; - case SDLK_t: + case SDLK_T: right.up = down; SetStickAxis(Controller::Axis::RightY, right.up, right.down); return; - case SDLK_g: + case SDLK_G: right.down = down; SetStickAxis(Controller::Axis::RightY, right.up, right.down); return; @@ -320,11 +316,11 @@ void DefaultKeyboardInput(int key_code, bool down) { } } -void MouseToJoystick(int delta_x, int delta_y) { +void MouseToJoystick(float delta_x, float delta_y) { const double distance = std::hypot(delta_x, delta_y); const double scale = std::clamp(distance * GetInputMap().MouseSensitivity() + 16.0, 64.0, 128.0) / distance; - const auto map_axis = [scale](int delta) { + const auto map_axis = [scale](float delta) { return std::clamp(128 + static_cast(std::lround(delta * scale)), 0, 255); }; Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x), @@ -340,16 +336,45 @@ void CenterMouseStick() { } bool SetRelativeMouseMode(bool enabled) { - if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) == 0) { + if (SDL_SetWindowRelativeMouseMode(g_mouse_window, enabled)) { return true; } LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError()); return false; } + +int PollMouse(uint64_t now_ms) { + if (now_ms < g_mouse.next_poll) { + return static_cast(g_mouse.next_poll - now_ms); + } + g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS; + + float delta_x = 0; + float delta_y = 0; + SDL_GetRelativeMouseState(&delta_x, &delta_y); + if (delta_x == 0 && delta_y == 0) { + CenterMouseStick(); + } else { + MouseToJoystick(delta_x, delta_y); + g_mouse.output = true; + } + return MOUSE_POLL_INTERVAL_MS; +} + } // namespace -void HostInputInit() { +void HostInputInit(SDL_Window* window) { GetInputMap(); + g_mouse_window = window; +} + +void HostInputShutdown() { + if (g_mouse.enabled) { + SetRelativeMouseMode(false); + CenterMouseStick(); + g_mouse = {}; + } + g_mouse_window = nullptr; } void HostInputKey(int key_code, bool down) { @@ -380,51 +405,33 @@ void HostInputToggleMouseToJoystick() { if (!SetRelativeMouseMode(true)) { return; } - int ignored_x = 0; - int ignored_y = 0; - SDL_GetRelativeMouseState(&ignored_x, &ignored_y); - g_mouse.enabled = true; - g_mouse.next_poll = SDL_GetTicks64() + MOUSE_POLL_INTERVAL_MS; + g_mouse.enabled = true; LOGF("Mouse to right stick: enabled (F7 to release)\n"); } -int PollMouse(uint64_t now_ms) { - if (now_ms < g_mouse.next_poll) { - return static_cast(g_mouse.next_poll - now_ms); - } - g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS; - - int delta_x = 0; - int delta_y = 0; - SDL_GetRelativeMouseState(&delta_x, &delta_y); - if (delta_x == 0 && delta_y == 0) { - CenterMouseStick(); - return MOUSE_POLL_INTERVAL_MS; - } - - MouseToJoystick(delta_x, delta_y); - g_mouse.output = true; - return MOUSE_POLL_INTERVAL_MS; -} - bool HostInputWaitEvent(SDL_Event* event) { - if (!g_mouse.enabled || SDL_GetKeyboardFocus() == nullptr) { + bool has_event; + if (!g_mouse.enabled || SDL_GetKeyboardFocus() != g_mouse_window) { + g_mouse.next_poll = 0; CenterMouseStick(); - if (SDL_WaitEvent(event) == 0) { + has_event = SDL_WaitEvent(event); + if (!has_event) { EXIT("%s\n", SDL_GetError()); } - return true; + } else { + if (g_mouse.next_poll == 0) { + SDL_GetRelativeMouseState(nullptr, nullptr); + g_mouse.next_poll = SDL_GetTicks() + MOUSE_POLL_INTERVAL_MS; + } + has_event = SDL_WaitEventTimeout(event, PollMouse(SDL_GetTicks())); } - const int timeout_ms = PollMouse(SDL_GetTicks64()); - SDL_ClearError(); - if (SDL_WaitEventTimeout(event, timeout_ms) != 0) { - return true; + if (has_event && event->type == SDL_EVENT_WINDOW_FOCUS_LOST && + event->window.windowID == SDL_GetWindowID(g_mouse_window)) { + g_mouse.next_poll = 0; + CenterMouseStick(); } - if (SDL_GetError()[0] != '\0') { - EXIT("%s\n", SDL_GetError()); - } - return false; + return has_event; } } // namespace Libs::Graphics diff --git a/src/graphics/presentation/window/hostInput.h b/src/graphics/presentation/window/hostInput.h index e0a788b1..bbf2e7f9 100644 --- a/src/graphics/presentation/window/hostInput.h +++ b/src/graphics/presentation/window/hostInput.h @@ -3,11 +3,13 @@ #include +struct SDL_Window; union SDL_Event; namespace Libs::Graphics { -void HostInputInit(); +void HostInputInit(SDL_Window* window); +void HostInputShutdown(); void HostInputKey(int key_code, bool down); void HostInputMouseButton(uint8_t mouse_button, bool down); void HostInputToggleMouseToJoystick(); diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index cf7ae99f..51de9d4c 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -532,7 +532,9 @@ void Swapchain::Recreate(bool surface_lost) { #if defined(__APPLE__) // Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the // window's view/layer and must run on the main thread on macOS. - m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }); + EXIT_IF(!SDL_RunOnMainThread( + [](void* window) { static_cast(window)->RecreateSurface(); }, + &m_window, true)); #else m_window.RecreateSurface(); #endif diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 04b765c3..9cbcb09d 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -1,20 +1,6 @@ -#include "SDL.h" -#include "SDL_error.h" -#include "SDL_events.h" -#include "SDL_gamecontroller.h" -#include "SDL_hints.h" -#include "SDL_joystick.h" -#include "SDL_keyboard.h" -#include "SDL_keycode.h" -#include "SDL_mouse.h" -#include "SDL_pixels.h" -#include "SDL_rwops.h" -#include "SDL_stdinc.h" -#include "SDL_surface.h" -#include "SDL_thread.h" -#include "SDL_touch.h" -#include "SDL_video.h" -#include "SDL_vulkan.h" +#include +#include + #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" @@ -700,25 +686,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, return device; } -static void VulkanGetExtensions(SDL_Window* window, VulkanExtensions& r) { - EXIT_IF(window == nullptr); - +static void VulkanGetExtensions(VulkanExtensions& r) { uint32_t required_extensions_count = 0; - auto sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, nullptr); - - EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE); + const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count); + EXIT_NOT_IMPLEMENTED(extensions == nullptr); EXIT_NOT_IMPLEMENTED(required_extensions_count == 0); - - r.required_extensions = - std::vector(required_extensions_count); // @suppress("Ambiguous problem") - - sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, - r.required_extensions.data()); - - EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE); - EXIT_NOT_IMPLEMENTED(required_extensions_count == 0); - EXIT_NOT_IMPLEMENTED(required_extensions_count != r.required_extensions.size()); + r.required_extensions.assign(extensions, extensions + required_extensions_count); r.available_extensions = EnumerateVulkan( // @suppress("Ambiguous problem") @@ -899,7 +873,7 @@ void WindowContext::CreateVulkan() { VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr); VulkanExtensions r; - VulkanGetExtensions(window, r); + VulkanGetExtensions(r); VulkanCheckInstanceVersion(); vk::ApplicationInfo app_info {}; @@ -986,8 +960,8 @@ void WindowContext::CreateVulkan() { } vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE; - if (SDL_Vulkan_CreateSurface(window, static_cast(graphic_ctx.instance), - &native_surface) == SDL_FALSE) { + if (!SDL_Vulkan_CreateSurface(window, static_cast(graphic_ctx.instance), + nullptr, &native_surface)) { EXIT("Could not create a Vulkan surface"); } surface = native_surface; @@ -1100,8 +1074,8 @@ void WindowContext::RecreateSurface() { surface = nullptr; } vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE; - if (SDL_Vulkan_CreateSurface(window, static_cast(graphic_ctx.instance), - &native_surface) == SDL_FALSE) { + if (!SDL_Vulkan_CreateSurface(window, static_cast(graphic_ctx.instance), + nullptr, &native_surface)) { EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError()); } surface = native_surface; @@ -1137,7 +1111,7 @@ WindowContext::~WindowContext() { SDL_DestroyWindow(window); window = nullptr; } - SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD); } } // namespace Libs::Graphics diff --git a/src/graphics/presentation/window/window.cpp b/src/graphics/presentation/window/window.cpp index f46bf2cb..ef4e8b2e 100644 --- a/src/graphics/presentation/window/window.cpp +++ b/src/graphics/presentation/window/window.cpp @@ -1,23 +1,7 @@ #include "graphics/presentation/window.h" -#include "SDL.h" -#include "SDL_error.h" -#include "SDL_events.h" -#include "SDL_filesystem.h" -#include "SDL_gamecontroller.h" -#include "SDL_hints.h" -#include "SDL_joystick.h" -#include "SDL_keyboard.h" -#include "SDL_keycode.h" -#include "SDL_mouse.h" -#include "SDL_pixels.h" -#include "SDL_rwops.h" -#include "SDL_stdinc.h" -#include "SDL_surface.h" -#include "SDL_thread.h" -#include "SDL_touch.h" -#include "SDL_video.h" -#include "SDL_vulkan.h" +#include + #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" @@ -72,39 +56,39 @@ struct EventKeyboard { static uint32_t ControllerButtonToPadButton(int button) { switch (button) { - case SDL_CONTROLLER_BUTTON_A: return Controller::PAD_BUTTON_CROSS; - case SDL_CONTROLLER_BUTTON_B: return Controller::PAD_BUTTON_CIRCLE; - case SDL_CONTROLLER_BUTTON_X: return Controller::PAD_BUTTON_SQUARE; - case SDL_CONTROLLER_BUTTON_Y: return Controller::PAD_BUTTON_TRIANGLE; - case SDL_CONTROLLER_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS; - case SDL_CONTROLLER_BUTTON_LEFTSTICK: return Controller::PAD_BUTTON_L3; - case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return Controller::PAD_BUTTON_R3; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return Controller::PAD_BUTTON_L1; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return Controller::PAD_BUTTON_R1; - case SDL_CONTROLLER_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT; - case SDL_CONTROLLER_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD; + case SDL_GAMEPAD_BUTTON_SOUTH: return Controller::PAD_BUTTON_CROSS; + case SDL_GAMEPAD_BUTTON_EAST: return Controller::PAD_BUTTON_CIRCLE; + case SDL_GAMEPAD_BUTTON_WEST: return Controller::PAD_BUTTON_SQUARE; + case SDL_GAMEPAD_BUTTON_NORTH: return Controller::PAD_BUTTON_TRIANGLE; + case SDL_GAMEPAD_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: return Controller::PAD_BUTTON_L3; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return Controller::PAD_BUTTON_R3; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return Controller::PAD_BUTTON_L1; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return Controller::PAD_BUTTON_R1; + case SDL_GAMEPAD_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD; default: return 0; } } static Controller::Axis ControllerAxisFromSdl(int axis_id) { switch (axis_id) { - case SDL_CONTROLLER_AXIS_LEFTX: return Controller::Axis::LeftX; - case SDL_CONTROLLER_AXIS_LEFTY: return Controller::Axis::LeftY; - case SDL_CONTROLLER_AXIS_RIGHTX: return Controller::Axis::RightX; - case SDL_CONTROLLER_AXIS_RIGHTY: return Controller::Axis::RightY; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return Controller::Axis::TriggerLeft; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return Controller::Axis::TriggerRight; + case SDL_GAMEPAD_AXIS_LEFTX: return Controller::Axis::LeftX; + case SDL_GAMEPAD_AXIS_LEFTY: return Controller::Axis::LeftY; + case SDL_GAMEPAD_AXIS_RIGHTX: return Controller::Axis::RightX; + case SDL_GAMEPAD_AXIS_RIGHTY: return Controller::Axis::RightY; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return Controller::Axis::TriggerLeft; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return Controller::Axis::TriggerRight; default: return Controller::Axis::AxisMax; } } static bool ControllerAxisIsTrigger(int axis_id) { - return axis_id == SDL_CONTROLLER_AXIS_TRIGGERLEFT || - axis_id == SDL_CONTROLLER_AXIS_TRIGGERRIGHT; + return axis_id == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || + axis_id == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; } static int ControllerAxisValueFromSdl(int axis_id, int axis_value) { @@ -165,22 +149,6 @@ struct EventController { double timestamp_seconds; }; -enum class DisplayOrientation { - Unknown, /* The display orientation can't be determined */ - Landscape, /* The display is in landscape mode, with the right side up, relative to portrait - mode */ - LandscapeFlipped, /* The display is in landscape mode, with the left side up, relative to - portrait mode */ - Portrait, /* The display is in portrait mode */ - PortraitFlipped, /* The display is in portrait mode, upside down */ - - DisplayEventOrientation = 0xF0 -}; - -struct EventDisplay { - DisplayOrientation orientation; -}; - namespace { std::unique_ptr g_window; @@ -213,11 +181,8 @@ static void ToggleDesktopFullscreen() { } const auto flags = static_cast(SDL_GetWindowFlags(g_window->window)); - const bool fullscreen = - (flags & static_cast(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u; - const auto mode = - fullscreen ? 0u : static_cast(SDL_WINDOW_FULLSCREEN_DESKTOP); - if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) { + const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0u; + if (!SDL_SetWindowFullscreen(g_window->window, !fullscreen)) { LOGF("Toggle fullscreen failed: %s\n", SDL_GetError()); } } @@ -247,7 +212,7 @@ static void GameEventKeyboard(const EventKeyboard& key) { break; case SDLK_RETURN: case SDLK_KP_ENTER: - if (!key.repeat && (key.mod & KMOD_ALT) != 0) { + if (!key.repeat && (key.mod & SDL_KMOD_ALT) != 0) { ToggleDesktopFullscreen(); fullscreen_key = key.key_code; } @@ -340,15 +305,15 @@ static void GameEventController([[maybe_unused]] const EventController& f) { #endif if (f.added) { - auto* pad = SDL_GameControllerOpen(f.id); + auto* pad = SDL_OpenGamepad(f.id); EXIT_NOT_IMPLEMENTED(pad == nullptr); - int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad)); + int id = SDL_GetJoystickID(SDL_GetGamepadJoystick(pad)); Controller::Connect(id); } if (f.removed) { Controller::Disconnect(f.id); - SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f.id)); + SDL_CloseGamepad(SDL_GetGamepadFromID(f.id)); } if (f.down || f.up) { @@ -400,81 +365,73 @@ void WindowContext::Resize(uint32_t new_width, uint32_t new_height) { void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) { const auto& window_event = event; - switch (window_event.event) { - case SDL_WINDOWEVENT_SHOWN: + switch (window_event.type) { + case SDL_EVENT_WINDOW_SHOWN: LOGF("Window %" PRIu32 " shown\n", window_event.windowID); break; - case SDL_WINDOWEVENT_HIDDEN: + case SDL_EVENT_WINDOW_HIDDEN: LOGF("Window %" PRIu32 " hidden\n", window_event.windowID); break; - case SDL_WINDOWEVENT_EXPOSED: + case SDL_EVENT_WINDOW_EXPOSED: LOGF("Window %" PRIu32 " exposed\n", window_event.windowID); break; - case SDL_WINDOWEVENT_MOVED: + case SDL_EVENT_WINDOW_MOVED: LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID, window_event.data1, window_event.data2); break; - case SDL_WINDOWEVENT_RESIZED: + case SDL_EVENT_WINDOW_RESIZED: LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID, window_event.data1, window_event.data2); - - LOGF("m: %d\n", static_cast(SDL_ThreadID())); - Resize(window_event.data1, window_event.data2); - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n", window_event.windowID, window_event.data1, window_event.data2); - LOGF("m: %d\n", static_cast(SDL_ThreadID())); Resize(window_event.data1, window_event.data2); break; - case SDL_WINDOWEVENT_MINIMIZED: + case SDL_EVENT_WINDOW_MINIMIZED: LOGF("Window %" PRIu32 " minimized\n", window_event.windowID); break; - case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: LOGF("Window %" PRIu32 " maximized\n", window_event.windowID); break; - case SDL_WINDOWEVENT_RESTORED: + case SDL_EVENT_WINDOW_RESTORED: LOGF("Window %" PRIu32 " restored\n", window_event.windowID); break; - case SDL_WINDOWEVENT_ENTER: + case SDL_EVENT_WINDOW_MOUSE_ENTER: LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID); break; - case SDL_WINDOWEVENT_LEAVE: + case SDL_EVENT_WINDOW_MOUSE_LEAVE: LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID); break; - case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_GAINED: LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID); break; - case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_EVENT_WINDOW_FOCUS_LOST: LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID); break; - case SDL_WINDOWEVENT_CLOSE: + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: LOGF("Window %" PRIu32 " closed\n", window_event.windowID); break; default: - LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window_event.windowID, - window_event.event); + LOGF("Window %" PRIu32 " got unknown event %" PRIu32 "\n", window_event.windowID, + static_cast(window_event.type)); break; } } void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) { - bool sdl = false; - - switch (display.event) { - case SDL_DISPLAYEVENT_ORIENTATION: sdl = true; [[fallthrough]]; - case static_cast(DisplayOrientation::DisplayEventOrientation): { - LOGF("Display %" PRIu32 "[%s] changed orientation to %d - ", display.display, - sdl ? "SDL" : "Kyty", static_cast(display.data1)); + switch (display.type) { + case SDL_EVENT_DISPLAY_ORIENTATION: { + LOGF("Display %" PRIu32 " changed orientation to %d - ", display.displayID, + static_cast(display.data1)); switch (display.data1) { case SDL_ORIENTATION_UNKNOWN: LOGF("UNKNOWN\n"); break; @@ -488,8 +445,8 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) { break; } default: - LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx8 "\n", display.display, - display.event); + LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx32 "\n", display.displayID, + static_cast(display.type)); break; } } @@ -497,10 +454,9 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) { void WindowContext::ProcessEvent(double time_s) { auto& game = loop; auto* event = &game.event; - EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE); - if ((event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) && - event->key.keysym.sym == SDLK_F7) { - if (event->type == SDL_KEYDOWN && event->key.repeat == 0) { + if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) && + event->key.key == SDLK_F7) { + if (event->type == SDL_EVENT_KEY_DOWN && event->key.repeat == 0) { HostInputToggleMouseToJoystick(); } return; @@ -508,34 +464,42 @@ void WindowContext::ProcessEvent(double time_s) { if (ProcessSystemOverlayInput(*event)) { return; } + if (event->type >= SDL_EVENT_WINDOW_FIRST && event->type <= SDL_EVENT_WINDOW_LAST) { + ProcessWindowEvent(event->window); + return; + } + if (event->type >= SDL_EVENT_DISPLAY_FIRST && event->type <= SDL_EVENT_DISPLAY_LAST) { + ProcessDisplayEvent(event->display); + return; + } switch (event->type) { - case SDL_QUIT: GameEventQuit(game); break; + case SDL_EVENT_QUIT: GameEventQuit(game); break; - case SDL_APP_TERMINATING: GameEventTerminate(game); break; + case SDL_EVENT_TERMINATING: GameEventTerminate(game); break; - case SDL_APP_LOWMEMORY: GameEventLowMemory(); break; + case SDL_EVENT_LOW_MEMORY: GameEventLowMemory(); break; - case SDL_APP_WILLENTERBACKGROUND: GameEventWillEnterBackground(game); break; + case SDL_EVENT_WILL_ENTER_BACKGROUND: GameEventWillEnterBackground(game); break; - case SDL_APP_DIDENTERBACKGROUND: GameEventDidEnterBackground(); break; + case SDL_EVENT_DID_ENTER_BACKGROUND: GameEventDidEnterBackground(); break; - case SDL_APP_WILLENTERFOREGROUND: GameEventWillEnterForeground(); break; + case SDL_EVENT_WILL_ENTER_FOREGROUND: GameEventWillEnterForeground(); break; - case SDL_APP_DIDENTERFOREGROUND: GameEventDidEnterForeground(game); break; + case SDL_EVENT_DID_ENTER_FOREGROUND: GameEventDidEnterForeground(game); break; - case SDL_KEYDOWN: - case SDL_KEYUP: { + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { EventKeyboard key {}; - key.down = (event->type == SDL_KEYDOWN); - key.up = (event->type == SDL_KEYUP); - key.pressed = (event->key.state == SDL_PRESSED); - key.released = (event->key.state == SDL_RELEASED); + key.down = (event->type == SDL_EVENT_KEY_DOWN); + key.up = (event->type == SDL_EVENT_KEY_UP); + key.pressed = event->key.down; + key.released = !event->key.down; key.repeat = (event->key.repeat != 0u); - key.scan_code = event->key.keysym.scancode; - key.key_code = event->key.keysym.sym; - key.mod = event->key.keysym.mod; + key.scan_code = event->key.scancode; + key.key_code = event->key.key; + key.mod = event->key.mod; key.timestamp_seconds = time_s; GameEventKeyboard(key); @@ -543,24 +507,20 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_WINDOWEVENT: ProcessWindowEvent(event->window); break; - - case SDL_DISPLAYEVENT: ProcessDisplayEvent(event->display); break; - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: { + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { EventMouse mb {}; - mb.down = (event->button.type == SDL_MOUSEBUTTONDOWN); - mb.up = (event->button.type == SDL_MOUSEBUTTONUP); + mb.down = (event->button.type == SDL_EVENT_MOUSE_BUTTON_DOWN); + mb.up = (event->button.type == SDL_EVENT_MOUSE_BUTTON_UP); mb.left = (event->button.button == SDL_BUTTON_LEFT); mb.middle = (event->button.button == SDL_BUTTON_MIDDLE); mb.right = (event->button.button == SDL_BUTTON_RIGHT); mb.x1 = (event->button.button == SDL_BUTTON_X1); mb.x2 = (event->button.button == SDL_BUTTON_X2); mb.touch = (event->button.which == SDL_TOUCH_MOUSEID); - mb.pressed = (event->button.state == SDL_PRESSED); - mb.released = (event->button.state == SDL_RELEASED); + mb.pressed = event->button.down; + mb.released = !event->button.down; mb.num_of_clicks = event->button.clicks; mb.wheel = false; mb.x = event->button.x; @@ -575,7 +535,7 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_MOUSEWHEEL: { + case SDL_EVENT_MOUSE_WHEEL: { EventMouse mb {}; mb.down = false; @@ -602,7 +562,7 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_MOUSEMOTION: { + case SDL_EVENT_MOUSE_MOTION: { EventMouse mb {}; mb.down = false; @@ -629,16 +589,16 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_FINGERMOTION: - case SDL_FINGERDOWN: - case SDL_FINGERUP: { + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: { EventFinger f {}; - f.down = (event->tfinger.type == SDL_FINGERDOWN); - f.up = (event->tfinger.type == SDL_FINGERUP); - f.motion = (event->tfinger.type == SDL_FINGERMOTION); - f.finger_id = static_cast(event->tfinger.fingerId); - f.touch_id = static_cast(event->tfinger.touchId); + f.down = (event->tfinger.type == SDL_EVENT_FINGER_DOWN); + f.up = (event->tfinger.type == SDL_EVENT_FINGER_UP); + f.motion = (event->tfinger.type == SDL_EVENT_FINGER_MOTION); + f.finger_id = static_cast(event->tfinger.fingerID); + f.touch_id = static_cast(event->tfinger.touchID); f.x = event->tfinger.x; f.y = event->tfinger.y; f.dx = event->tfinger.dx; @@ -651,13 +611,13 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_CONTROLLERAXISMOTION: { + case SDL_EVENT_GAMEPAD_AXIS_MOTION: { EventController c {}; - c.id = event->caxis.which; - c.button = SDL_CONTROLLER_BUTTON_INVALID; - c.axis_id = event->caxis.axis; - c.axis_value = event->caxis.value; + c.id = event->gaxis.which; + c.button = SDL_GAMEPAD_BUTTON_INVALID; + c.axis_id = event->gaxis.axis; + c.axis_value = event->gaxis.value; c.down = false; c.up = false; c.added = false; @@ -673,22 +633,22 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: { + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: { EventController c {}; - c.id = event->cbutton.which; - c.button = event->cbutton.button; - c.axis_id = SDL_CONTROLLER_AXIS_INVALID; + c.id = event->gbutton.which; + c.button = event->gbutton.button; + c.axis_id = SDL_GAMEPAD_AXIS_INVALID; c.axis_value = 0; - c.down = (event->cbutton.type == SDL_CONTROLLERBUTTONDOWN); - c.up = (event->cbutton.type == SDL_CONTROLLERBUTTONUP); + c.down = event->gbutton.down; + c.up = !event->gbutton.down; c.added = false; c.removed = false; c.remapped = false; c.axis = false; - c.pressed = (event->cbutton.state == SDL_PRESSED); - c.released = (event->cbutton.state == SDL_RELEASED); + c.pressed = event->gbutton.down; + c.released = !event->gbutton.down; c.timestamp_seconds = time_s; GameEventController(c); @@ -696,44 +656,44 @@ void WindowContext::ProcessEvent(double time_s) { break; } - case SDL_CONTROLLERTOUCHPADDOWN: - case SDL_CONTROLLERTOUCHPADMOTION: - case SDL_CONTROLLERTOUCHPADUP: - if (event->ctouchpad.touchpad == 0) { - Controller::SetTouchPad(event->ctouchpad.which, event->ctouchpad.finger, - event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP, - event->ctouchpad.x, event->ctouchpad.y); + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + if (event->gtouchpad.touchpad == 0) { + Controller::SetTouchPad(event->gtouchpad.which, event->gtouchpad.finger, + event->gtouchpad.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, + event->gtouchpad.x, event->gtouchpad.y); } break; - case SDL_CONTROLLERSENSORUPDATE: { - const auto& sensor = event->csensor; + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: { + const auto& sensor = event->gsensor; if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) { Controller::SetSensor(sensor.which, sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel : Controller::Sensor::Gyro, sensor.data, - sensor.timestamp_us != 0 - ? sensor.timestamp_us - : static_cast(sensor.timestamp) * 1000); + sensor.sensor_timestamp != 0 + ? sensor.sensor_timestamp / 1000 + : sensor.timestamp / 1000); } break; } - case SDL_CONTROLLERDEVICEADDED: - case SDL_CONTROLLERDEVICEREMOVED: - case SDL_CONTROLLERDEVICEREMAPPED: { + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EVENT_GAMEPAD_REMAPPED: { EventController c {}; - c.id = event->cdevice.which; - c.button = SDL_CONTROLLER_BUTTON_INVALID; - c.axis_id = SDL_CONTROLLER_AXIS_INVALID; + c.id = event->gdevice.which; + c.button = SDL_GAMEPAD_BUTTON_INVALID; + c.axis_id = SDL_GAMEPAD_AXIS_INVALID; c.axis_value = 0; c.down = false; c.up = false; - c.added = (event->cdevice.type == SDL_CONTROLLERDEVICEADDED); - c.removed = (event->cdevice.type == SDL_CONTROLLERDEVICEREMOVED); - c.remapped = (event->cdevice.type == SDL_CONTROLLERDEVICEREMAPPED); + c.added = (event->gdevice.type == SDL_EVENT_GAMEPAD_ADDED); + c.removed = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMOVED); + c.remapped = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMAPPED); c.axis = false; c.pressed = false; c.released = false; @@ -746,47 +706,6 @@ void WindowContext::ProcessEvent(double time_s) { } } -void WindowContext::RunOnMainThread(std::function task) { - if (Common::Thread::IsMainThread()) { - task(); - return; - } - - uint64_t ticket = 0; - { - Common::LockGuard lock(main_task_mutex); - main_tasks.push_back(std::move(task)); - ticket = ++main_tasks_queued; - } - - // Wake the main loop in case it is blocked in SDL_WaitEvent. - SDL_Event event {}; - event.type = SDL_USEREVENT; - SDL_PushEvent(&event); - - Common::LockGuard lock(main_task_mutex); - while (main_tasks_run < ticket) { - main_task_done.Wait(&main_task_mutex); - } -} - -void WindowContext::DrainMainThreadTasks() { - std::vector> tasks; - { - Common::LockGuard lock(main_task_mutex); - tasks.swap(main_tasks); - } - if (tasks.empty()) { - return; - } - for (auto& task: tasks) { - task(); - } - Common::LockGuard lock(main_task_mutex); - main_tasks_run += tasks.size(); - main_task_done.SignalAll(); -} - void WindowContext::Run() { Common::Timer timer; timer.Start(); @@ -796,7 +715,6 @@ void WindowContext::Run() { loop.paused.store(false, std::memory_order_release); while (!loop.need_exit) { - DrainMainThreadTasks(); if (loop.paused.load(std::memory_order_acquire)) { if (!timer.IsPaused()) { timer.Pause(); @@ -820,17 +738,15 @@ static void WindowCreate(WindowContext& context) { int width = static_cast(context.graphic_ctx.screen_width); int height = static_cast(context.graphic_ctx.screen_height); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0"); +#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX + // RenderDoc can hide Wayland Vulkan surface support; prefer X11 for captures. + if (Config::RenderDocEnabled()) { + SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland"); + } #endif - - if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) { + if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { EXIT("%s\n", SDL_GetError()); } - HostInputInit(); - InitializeSystemOverlayInput(); LOGF("WindowCreate(): width = %d, height = %d\n", width, height); @@ -840,15 +756,14 @@ static void WindowCreate(WindowContext& context) { // MoltenVK loader before calling SDL_CreateWindow. Keep an explicit user // override, and fall back to SDL's normal loader search when no bundle is present. if (std::getenv("SDL_VULKAN_LIBRARY") == nullptr) { - if (char* base_path = SDL_GetBasePath(); base_path != nullptr) { + if (const char* base_path = SDL_GetBasePath(); base_path != nullptr) { const std::string base_path_str = base_path; - SDL_free(base_path); std::string moltenvk_path = base_path_str + "libMoltenVK.dylib"; if (!Common::File::IsFileExisting(moltenvk_path)) { moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib"; } if (Common::File::IsFileExisting(moltenvk_path) && - SDL_setenv("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) { + SDL_setenv_unsafe("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) { LOGF("Vulkan loader: %s\n", moltenvk_path.c_str()); } } @@ -861,21 +776,23 @@ static void WindowCreate(WindowContext& context) { window_flags |= static_cast(SDL_WINDOW_BORDERLESS); } #endif - context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, width, height, window_flags); + context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, width, height, window_flags); if (context.window == nullptr) { EXIT("%s\n", SDL_GetError()); } - SDL_SetWindowResizable(context.window, SDL_FALSE); + SDL_SetWindowPosition(context.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_SetWindowResizable(context.window, false); + HostInputInit(context.window); + InitializeSystemOverlayInput(context.window); context.UpdateIcon(); } uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept { auto flags = static_cast(SDL_WINDOW_VULKAN); if (fullscreen) { - flags |= static_cast(SDL_WINDOW_FULLSCREEN_DESKTOP); + flags |= static_cast(SDL_WINDOW_FULLSCREEN); } return flags; } @@ -906,6 +823,7 @@ void WindowRun() { void WindowShutdown() { if (g_window != nullptr) { + HostInputShutdown(); Controller::EmergencyShutdown(); g_window.reset(); } @@ -941,7 +859,7 @@ struct WindowIcon { void* pixels = nullptr; ~WindowIcon() { - SDL_FreeSurface(surface); + SDL_DestroySurface(surface); stbi_image_free(pixels); } }; @@ -965,8 +883,8 @@ static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* ico EXIT_IF(icon->pixels == nullptr); - icon->surface = SDL_CreateRGBSurfaceWithFormatFrom(icon->pixels, width, height, 32, width * 4, - SDL_PIXELFORMAT_RGBA32); + icon->surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, icon->pixels, + width * 4); EXIT_NOT_IMPLEMENTED(icon->surface == nullptr); } @@ -1028,7 +946,16 @@ void WindowContext::UpdateTitle() { (has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""), device_name, processor_name, frame_num, current_fps); - RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); }); + struct TitleUpdate { + SDL_Window* window; + std::string* text; + } update {window, &text}; + EXIT_IF(!SDL_RunOnMainThread( + [](void* data) { + auto& title = *static_cast(data); + SDL_SetWindowTitle(title.window, title.text->c_str()); + }, + &update, true)); } } // namespace Libs::Graphics diff --git a/src/graphics/presentation/window/windowInternal.h b/src/graphics/presentation/window/windowInternal.h index 66ceb905..a0aa1b59 100644 --- a/src/graphics/presentation/window/windowInternal.h +++ b/src/graphics/presentation/window/windowInternal.h @@ -1,15 +1,14 @@ #ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_ #define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_ -#include "SDL_events.h" -#include "SDL_video.h" +#include + #include "common/threads.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/vulkanCommon.h" #include #include -#include #include #include @@ -48,9 +47,6 @@ struct WindowContext { void ProcessDisplayEvent(const SDL_DisplayEvent& event); void ProcessEvent(double time_seconds); void Run(); - // SDL window operations must complete on the main thread. - void RunOnMainThread(std::function task); - void DrainMainThreadTasks(); GraphicContext graphic_ctx; SDL_Window* window = nullptr; @@ -61,12 +57,6 @@ struct WindowContext { WindowLoopState loop; Common::Mutex mutex; - - Common::Mutex main_task_mutex; - Common::CondVar main_task_done; - std::vector> main_tasks; // guarded by main_task_mutex - uint64_t main_tasks_queued = 0; // guarded by main_task_mutex - uint64_t main_tasks_run = 0; // guarded by main_task_mutex }; } // namespace Libs::Graphics diff --git a/src/launcher/CMakeLists.txt b/src/launcher/CMakeLists.txt index 01d5ec1f..8a36335d 100644 --- a/src/launcher/CMakeLists.txt +++ b/src/launcher/CMakeLists.txt @@ -20,7 +20,7 @@ add_executable(launcher WIN32 ) -target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL2-static kyty_git_version) +target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static kyty_git_version) if(WIN32) target_link_libraries(launcher setupapi) endif() diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index c97791b5..4db2f6d8 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -3,7 +3,7 @@ #include "common/emulatorConfig.h" #include "configuration.h" #include "mandatoryLineEdit.h" -#include "SDL.h" +#include #include #include @@ -173,17 +173,18 @@ void ConfigurationEditDialog::Init(const Configuration& info) { microphone->clear(); microphone->addItem(tr("None"), QString {}); microphone->setToolTip(tr("Microphone used by games. None supplies silence.")); - SDL_SetMainReady(); - if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) { - const int device_count = SDL_GetNumAudioDevices(SDL_TRUE); + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { + int device_count = 0; + SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count); for (int i = 0; i < device_count; i++) { - if (const auto* device = SDL_GetAudioDeviceName(i, SDL_TRUE); device != nullptr) { + if (const auto* device = SDL_GetAudioDeviceName(devices[i]); device != nullptr) { const auto name = QString::fromUtf8(device); if (microphone->findData(name) < 0) { microphone->addItem(name, name); } } } + SDL_free(devices); SDL_QuitSubSystem(SDL_INIT_AUDIO); } else { microphone->setToolTip(tr("Microphones could not be listed: %1") diff --git a/src/libs/audio.cpp b/src/libs/audio.cpp index cf4ae70c..4f448e9d 100644 --- a/src/libs/audio.cpp +++ b/src/libs/audio.cpp @@ -1,6 +1,6 @@ #include "libs/audio.h" -#include "SDL.h" +#include #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" @@ -115,8 +115,7 @@ private: int channels_num = 0; int volume[12] = {}; - SDL_AudioDeviceID audio_device = 0; - SDL_AudioSpec audio_spec = {}; + SDL_AudioStream* stream = nullptr; }; struct PortIn { @@ -127,7 +126,8 @@ private: uint32_t freq = 0; uint32_t bytes_per_frame = 0; uint64_t last_input_time = 0; - SDL_AudioDeviceID audio_device = 0; + SDL_AudioStream* stream = nullptr; + SDL_AudioDeviceID device = 0; }; PortIn* GetAudioInPort(Id handle); // Caller holds m_mutex. @@ -242,13 +242,13 @@ uint32_t Audio::OutputChannels(const PortOut& port) { } SDL_AudioFormat Audio::SdlFormat(Format format) { - return FormatIsFloat(format) ? AUDIO_F32SYS : AUDIO_S16SYS; + return FormatIsFloat(format) ? SDL_AUDIO_F32 : SDL_AUDIO_S16; } bool Audio::OpenSdlDevice(PortOut* port) { EXIT_IF(port == nullptr); - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOGF("AudioOut: SDL audio init failed: %s\n", SDL_GetError()); return false; } @@ -256,37 +256,34 @@ bool Audio::OpenSdlDevice(PortOut* port) { SDL_AudioSpec desired {}; desired.freq = static_cast(port->freq); desired.format = SdlFormat(port->format); - desired.channels = static_cast(OutputChannels(*port)); - desired.samples = static_cast(port->samples_num); - desired.callback = nullptr; + desired.channels = static_cast(OutputChannels(*port)); - SDL_AudioSpec obtained {}; - - port->audio_device = - SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); - if (port->audio_device == 0) { - LOGF("AudioOut: SDL_OpenAudioDevice failed: %s\n", SDL_GetError()); + port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, nullptr, + nullptr); + if (port->stream == nullptr) { + LOGF("AudioOut: SDL_OpenAudioDeviceStream failed: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + if (!SDL_ResumeAudioStreamDevice(port->stream)) { + LOGF("AudioOut: SDL_ResumeAudioStreamDevice failed: %s\n", SDL_GetError()); + CloseSdlDevice(port); return false; } - port->audio_spec = obtained; - SDL_PauseAudioDevice(port->audio_device, 0); - - LOGF("AudioOut: opened SDL device (%d Hz, %u ch, format 0x%04x)\n", obtained.freq, - obtained.channels, obtained.format); + LOGF("AudioOut: opened SDL stream (%d Hz, %d ch, format 0x%04x)\n", desired.freq, + desired.channels, static_cast(desired.format)); return true; } void Audio::CloseSdlDevice(PortOut* port) { EXIT_IF(port == nullptr); - if (port->audio_device != 0 && SDL_WasInit(SDL_INIT_AUDIO) != 0) { - SDL_ClearQueuedAudio(port->audio_device); - SDL_CloseAudioDevice(port->audio_device); + if (port->stream != nullptr) { + SDL_DestroyAudioStream(port->stream); + SDL_QuitSubSystem(SDL_INIT_AUDIO); } - - port->audio_device = 0; - port->audio_spec = {}; + port->stream = nullptr; } const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data, @@ -364,7 +361,7 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data, bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) { EXIT_IF(port == nullptr); - if (port->audio_device == 0 || data == nullptr) { + if (port->stream == nullptr || data == nullptr) { return false; } @@ -374,56 +371,25 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) { const auto prepared_size = BytesPerSample(port->format) * output_channels * port->samples_num; - std::vector convert_buffer; - const void* queue_data = prepared_data; - uint32_t queue_size = prepared_size; - - SDL_AudioCVT cvt {}; - const int cvt_result = - SDL_BuildAudioCVT(&cvt, SdlFormat(port->format), static_cast(output_channels), - static_cast(port->freq), port->audio_spec.format, - port->audio_spec.channels, port->audio_spec.freq); - - if (cvt_result < 0) { - LOGF("AudioOut: SDL_BuildAudioCVT failed: %s\n", SDL_GetError()); - return false; - } - - if (cvt_result > 0) { - convert_buffer.resize(prepared_size * cvt.len_mult); - std::memcpy(convert_buffer.data(), prepared_data, prepared_size); - - cvt.buf = convert_buffer.data(); - cvt.len = static_cast(prepared_size); - - if (SDL_ConvertAudio(&cvt) < 0) { - LOGF("AudioOut: SDL_ConvertAudio failed: %s\n", SDL_GetError()); - return false; - } - - queue_data = cvt.buf; - queue_size = static_cast(cvt.len_cvt); - } - if (blocking) { constexpr uint64_t target_latency_us = 40000; const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0; const auto buffers = buffer_us != 0 ? static_cast((target_latency_us + buffer_us - 1) / buffer_us) : 2u; - const auto min_queued_size = queue_size * std::clamp(buffers, 2u, 16u); + const auto min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u); const auto wait_start = LibKernel::KernelGetProcessTime(); - while (SDL_GetQueuedAudioSize(port->audio_device) > min_queued_size) { + while (SDL_GetAudioStreamQueued(port->stream) > static_cast(min_queued_size)) { if (LibKernel::KernelGetProcessTime() - wait_start > 200000) { - SDL_ClearQueuedAudio(port->audio_device); + SDL_ClearAudioStream(port->stream); break; } Common::Thread::SleepMicro(1000); } } - if (SDL_QueueAudio(port->audio_device, queue_data, queue_size) < 0) { - LOGF("AudioOut: SDL_QueueAudio failed: %s\n", SDL_GetError()); + if (!SDL_PutAudioStreamData(port->stream, prepared_data, static_cast(prepared_size))) { + LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError()); return false; } @@ -498,7 +464,7 @@ bool Audio::AudioOutHasDevice(Id handle) { Common::LockGuard lock(m_mutex); return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX && - m_out_ports[handle.GetId()].used && m_out_ports[handle.GetId()].audio_device != 0); + m_out_ports[handle.GetId()].used && m_out_ports[handle.GetId()].stream != nullptr); } bool Audio::AudioOutGetStatus(Id handle, int* type, int* channels_num) { @@ -557,7 +523,7 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking bool any_port_has_device = false; for (uint32_t i = 0; i < num; i++) { - if (m_out_ports[params[i].handle.GetId()].audio_device != 0) { + if (m_out_ports[params[i].handle.GetId()].stream != nullptr) { any_port_has_device = true; break; } @@ -583,35 +549,65 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking return first_port.samples_num; } +static bool RecordingDevicePresent(SDL_AudioDeviceID device) { + int count = 0; + SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&count); + bool present = false; + for (int i = 0; i < count; i++) { + if (devices[i] == device) { + present = true; + break; + } + } + SDL_free(devices); + return present; +} + void Audio::OpenSdlDevice(PortIn* port, Format format) { const auto& name = Config::GetAudioInputDevice(); if (name.empty()) { return; } - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError()); return; } + int device_count = 0; + SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count); + SDL_AudioDeviceID device = 0; + for (int i = 0; i < device_count; i++) { + const char* device_name = SDL_GetAudioDeviceName(devices[i]); + if (device_name != nullptr && name == device_name) { + device = devices[i]; + break; + } + } + SDL_free(devices); + if (device == 0) { + LOGF("AudioIn: cannot find '%s'; using silence\n", name.c_str()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return; + } SDL_AudioSpec desired {}; desired.freq = static_cast(port->freq); desired.format = SdlFormat(format); - desired.channels = static_cast(port->bytes_per_frame / BytesPerSample(format)); - desired.samples = static_cast(port->samples_num); - // SDL converts capture data to the guest format when allowed_changes is zero. - port->audio_device = SDL_OpenAudioDevice(name.c_str(), 1, &desired, nullptr, 0); - if (port->audio_device == 0) { + desired.channels = static_cast(port->bytes_per_frame / BytesPerSample(format)); + port->stream = SDL_OpenAudioDeviceStream(device, &desired, nullptr, nullptr); + if (port->stream == nullptr) { LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } - LOGF("AudioIn: opened '%s' (%u Hz, %u ch)\n", name.c_str(), port->freq, desired.channels); + port->device = device; + LOGF("AudioIn: opened '%s' (%u Hz, %d ch)\n", name.c_str(), port->freq, desired.channels); } void Audio::CloseSdlDevice(PortIn* port) { - if (port->audio_device != 0) { - SDL_CloseAudioDevice(port->audio_device); + if (port->stream != nullptr) { + SDL_DestroyAudioStream(port->stream); SDL_QuitSubSystem(SDL_INIT_AUDIO); - port->audio_device = 0; + port->stream = nullptr; + port->device = 0; } } @@ -668,8 +664,7 @@ int Audio::AudioInGetSilentState(Id handle) { if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } - if (port->audio_device == 0 || - SDL_GetAudioDeviceStatus(port->audio_device) == SDL_AUDIO_STOPPED) { + if (port->stream == nullptr || !RecordingDevicePresent(port->device)) { return AUDIO_IN_SILENT_STATE_DEVICE_NONE; } return 0; @@ -689,9 +684,9 @@ int Audio::AudioInInput(Id handle, void* dest) { port->busy = true; snapshot = *port; } - if (snapshot.audio_device != 0 && dest != nullptr) { - SDL_PauseAudioDevice(snapshot.audio_device, 0); - } + bool failed = snapshot.stream != nullptr && dest != nullptr && + (!RecordingDevicePresent(snapshot.device) || + !SDL_ResumeAudioStreamDevice(snapshot.stream)); const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq; uint64_t wait_time = 0; @@ -699,7 +694,7 @@ int Audio::AudioInInput(Id handle, void* dest) { if (dest != nullptr) { wait_time = block_time; } - } else if (snapshot.last_input_time != 0 && (snapshot.audio_device == 0 || dest == nullptr)) { + } else if (snapshot.last_input_time != 0 && (snapshot.stream == nullptr || dest == nullptr)) { const auto now = LibKernel::KernelGetProcessTime(); const auto next = snapshot.last_input_time + block_time; if (next > now) { @@ -708,31 +703,41 @@ int Audio::AudioInInput(Id handle, void* dest) { } Common::Thread::SleepMicro(wait_time); uint32_t frames = snapshot.samples_num; - bool failed = false; + bool timed_out = false; if (dest != nullptr) { - if (snapshot.audio_device != 0) { - while (!snapshot.asynchronous && - SDL_GetQueuedAudioSize(snapshot.audio_device) < - frames * snapshot.bytes_per_frame && - SDL_GetAudioDeviceStatus(snapshot.audio_device) == SDL_AUDIO_PLAYING) { + if (snapshot.stream != nullptr && !failed) { + const int requested = static_cast(frames * snapshot.bytes_per_frame); + const auto deadline = LibKernel::KernelGetProcessTime() + + std::max(20000, block_time * 4); + const auto device = SDL_GetAudioStreamDevice(snapshot.stream); + int available = SDL_GetAudioStreamAvailable(snapshot.stream); + while (!snapshot.asynchronous && available >= 0 && + available < requested && + device != 0 && !SDL_AudioDevicePaused(device) && + LibKernel::KernelGetProcessTime() < deadline) { Common::Thread::SleepMicro(1000); + available = SDL_GetAudioStreamAvailable(snapshot.stream); } - failed = SDL_GetAudioDeviceStatus(snapshot.audio_device) != SDL_AUDIO_PLAYING; - if (!failed) { + failed = available < 0 || device == 0 || SDL_AudioDevicePaused(device); + timed_out = !snapshot.asynchronous && available < requested; + if (!failed && !timed_out) { if (snapshot.asynchronous) { frames = AUDIO_IN_GRAIN_MAX_ASYNC; } - frames = SDL_DequeueAudio(snapshot.audio_device, dest, - frames * snapshot.bytes_per_frame) / - snapshot.bytes_per_frame; + const int bytes = SDL_GetAudioStreamData( + snapshot.stream, dest, static_cast(frames * snapshot.bytes_per_frame)); + failed = bytes < 0; + if (!failed) { + frames = static_cast(bytes) / snapshot.bytes_per_frame; + } } } - if (snapshot.audio_device == 0 || failed) { + if (snapshot.stream == nullptr || failed || timed_out) { std::memset(dest, 0, frames * snapshot.bytes_per_frame); } - } else if (snapshot.audio_device != 0) { - SDL_PauseAudioDevice(snapshot.audio_device, 1); - SDL_ClearQueuedAudio(snapshot.audio_device); + } else if (snapshot.stream != nullptr) { + SDL_PauseAudioStreamDevice(snapshot.stream); + SDL_ClearAudioStream(snapshot.stream); } { Common::LockGuard lock(m_mutex); diff --git a/src/libs/controller.cpp b/src/libs/controller.cpp index f55f97f8..b1c24063 100644 --- a/src/libs/controller.cpp +++ b/src/libs/controller.cpp @@ -1,6 +1,6 @@ #include "libs/controller.h" -#include "SDL.h" +#include #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" @@ -293,11 +293,11 @@ void GameController::Connect(int id) { m_connected_ids.push_back(id); if (id != HOST_INPUT_CONTROLLER_ID) { - if (auto* pad = SDL_GameControllerFromInstanceID(static_cast(id)); + if (auto* pad = SDL_GetGamepadFromID(static_cast(id)); pad != nullptr) { for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) { - if (SDL_GameControllerHasSensor(pad, sensor) && - SDL_GameControllerSetSensorEnabled(pad, sensor, SDL_TRUE) != 0) { + if (SDL_GamepadHasSensor(pad, sensor) && + !SDL_SetGamepadSensorEnabled(pad, sensor, true)) { LOGF("\t enabling controller sensor failed: %s\n", SDL_GetError()); } } @@ -519,22 +519,22 @@ void GameController::ResetInputState() { void GameController::ReleaseHostPads() { Common::LockGuard lock(m_mutex); - std::vector pads; + std::vector pads; for (const auto id: m_connected_ids) { if (id == HOST_INPUT_CONTROLLER_ID) { continue; } - if (auto* pad = SDL_GameControllerFromInstanceID(static_cast(id)); + if (auto* pad = SDL_GetGamepadFromID(static_cast(id)); pad != nullptr) { - if (SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) { + if (SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) { DualSenseEffects effect {}; effect.enable_bits = 0x0c; effect.right_trigger[0] = 0x05; effect.left_trigger[0] = 0x05; - (void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect)); + (void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect)); } - (void)SDL_GameControllerRumble(pad, 0, 0, 0); - (void)SDL_GameControllerSetLED(pad, 0, 0, 0); + (void)SDL_RumbleGamepad(pad, 0, 0, 0); + (void)SDL_SetGamepadLED(pad, 0, 0, 0); pads.push_back(pad); } } @@ -542,7 +542,7 @@ void GameController::ReleaseHostPads() { if (!pads.empty()) { SDL_Delay(RELEASE_FLUSH_MS); for (auto* pad: pads) { - SDL_GameControllerClose(pad); + SDL_CloseGamepad(pad); } } @@ -556,23 +556,23 @@ void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) { return; } - auto* pad = SDL_GameControllerFromInstanceID(static_cast(m_active_id)); + auto* pad = SDL_GetGamepadFromID(static_cast(m_active_id)); if (pad == nullptr) { return; } const auto large = static_cast(large_motor * 0x101U); const auto small = static_cast(small_motor * 0x101U); - if (SDL_GameControllerRumble(pad, large, small, RUMBLE_DURATION_MS) != 0) { + if (!SDL_RumbleGamepad(pad, large, small, RUMBLE_DURATION_MS)) { LOGF("\t rumble failed: %s\n", SDL_GetError()); } } void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) { Common::LockGuard lock(m_mutex); - if (auto* pad = SDL_GameControllerFromInstanceID(static_cast(m_active_id)); + if (auto* pad = SDL_GetGamepadFromID(static_cast(m_active_id)); pad != nullptr) { - (void)SDL_GameControllerSetLED(pad, r, g, b); + (void)SDL_SetGamepadLED(pad, r, g, b); } } @@ -599,9 +599,9 @@ bool GameController::SetTriggerEffect(const PadTriggerEffectParam& param) { } Common::LockGuard lock(m_mutex); - auto* pad = SDL_GameControllerFromInstanceID(static_cast(m_active_id)); - if (pad != nullptr && SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) { - (void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect)); + auto* pad = SDL_GetGamepadFromID(static_cast(m_active_id)); + if (pad != nullptr && SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) { + (void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect)); } return true; } diff --git a/src/libs/libCes.cpp b/src/libs/libCes.cpp index 1e36584c..f7d984fb 100644 --- a/src/libs/libCes.cpp +++ b/src/libs/libCes.cpp @@ -3,7 +3,7 @@ #include "libs/libs.h" #include "loader/symbolDatabase.h" -#include +#include #include #include diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index bcf97112..7d12f869 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -1,4 +1,6 @@ -#include "SDL.h" +#include +#include + #include "common/emulatorConfig.h" #include "common/file.h" #include "common/logging/log.h" @@ -478,11 +480,11 @@ int main() { Config::Load(options); subsystems.Initialize(); - Check(SDL_InitSubSystem(SDL_INIT_VIDEO) == 0, "initialize Vulkan test video"); + Check(SDL_InitSubSystem(SDL_INIT_VIDEO), "initialize Vulkan test video"); auto graphics = std::make_unique(); graphics->graphic_ctx.screen_width = 64; graphics->graphic_ctx.screen_height = 64; - graphics->window = SDL_CreateWindow("KernelFileSystemTests", 0, 0, 64, 64, + graphics->window = SDL_CreateWindow("KernelFileSystemTests", 64, 64, SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN); Check(graphics->window != nullptr, "create hidden Vulkan test window"); graphics->CreateVulkan();