SDL3 update (#787)

* Update to SDL3

* Add XTest development dependency for SDL3

* Preserve native entry points with SDL3

* Document SDL3 XTest dependency
This commit is contained in:
nmzik 2026-09-23 15:22:15 +02:00 committed by GitHub
parent b5ba482fc2
commit f68c77d785
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 469 additions and 554 deletions

View File

@ -314,6 +314,7 @@ jobs:
libxkbcommon-dev \ libxkbcommon-dev \
libxrandr-dev \ libxrandr-dev \
libxss-dev \ libxss-dev \
libxtst-dev \
lld \ lld \
pkg-config \ pkg-config \
wayland-protocols wayland-protocols
@ -359,7 +360,7 @@ jobs:
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log -DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}" exit "${PIPESTATUS[0]}"
- name: Verify SDL2 backends - name: Verify SDL3 backends
shell: bash shell: bash
run: | run: |
status=0 status=0

6
.gitmodules vendored
View File

@ -1,7 +1,7 @@
[submodule "3rdparty/SDL2"] [submodule "3rdparty/SDL3"]
path = 3rdparty/SDL2 path = 3rdparty/SDL3
url = https://github.com/libsdl-org/SDL.git url = https://github.com/libsdl-org/SDL.git
branch = SDL2 branch = release-3.4.x
[submodule "3rdparty/magic_enum"] [submodule "3rdparty/magic_enum"]
path = 3rdparty/magic_enum path = 3rdparty/magic_enum
url = https://github.com/Neargye/magic_enum.git url = https://github.com/Neargye/magic_enum.git

View File

@ -29,7 +29,7 @@ FetchContent_Declare(
FetchContent_MakeAvailable(xbyak zydis) FetchContent_MakeAvailable(xbyak zydis)
set(SDL_SHARED OFF CACHE BOOL "" FORCE) set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(SDL2) add_subdirectory(SDL3)
add_library(Vulkan-Headers INTERFACE) add_library(Vulkan-Headers INTERFACE)
add_library(Vulkan::Headers ALIAS Vulkan-Headers) add_library(Vulkan::Headers ALIAS Vulkan-Headers)

1
3rdparty/SDL2 vendored

@ -1 +0,0 @@
Subproject commit 4b69833bc54abf3dd3288d4aa7afbba527775e5b

1
3rdparty/SDL3 vendored Submodule

@ -0,0 +1 @@
Subproject commit fa2c02bb6e21974a89ea9824bc53c9932abe5f9c

View File

@ -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) if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies") 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") set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
endif() 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. # Linux system libraries.
if(LINUX) if(LINUX)
@ -341,7 +341,6 @@ endif()
set(inc_headers set(inc_headers
${KYTY_SOURCE_DIR} ${KYTY_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include ${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include ${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include ${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include

View File

@ -166,15 +166,15 @@ The finished application and its runtime dependencies will be placed in
### Building on Linux ### Building on Linux
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland and
udev development packages SDL2 quietly configures itself without those backends, and the resulting udev development packages SDL3 quietly configures itself without those backends, and the resulting
build has no working sound and no gamepad hotplug: build has no working sound and no gamepad hotplug:
```bash ```bash
sudo apt-get install --no-install-recommends \ sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools pkg-config \ clang lld ninja-build cmake git glslang-tools pkg-config \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \ 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 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 ### 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 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 `CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
needed: needed:

View File

@ -1,8 +1,7 @@
{ pkgs }: { pkgs }:
let let
# Build-time dependencies. Every -dev package the bundled SDL2 probes for must # Build-time dependencies for the bundled SDL3 audio, video and input backends.
# be listed here, otherwise SDL2 quietly drops the audio/Wayland/udev backends.
buildDeps = with pkgs; [ buildDeps = with pkgs; [
qt6.qtbase # Concurrent, Network, Widgets (launcher) qt6.qtbase # Concurrent, Network, Widgets (launcher)
vulkan-headers vulkan-headers
@ -18,6 +17,7 @@ let
libXi libXi
libXrandr libXrandr
libXScrnSaver libXScrnSaver
libXtst
libxkbcommon libxkbcommon
# Wayland # Wayland
wayland wayland
@ -46,6 +46,7 @@ let
libXi libXi
libXrandr libXrandr
libXScrnSaver libXScrnSaver
libXtst
libxkbcommon libxkbcommon
wayland wayland
libdecor libdecor

View File

@ -1,6 +1,7 @@
#include "graphics/presentation/systemOverlay.h" #include "graphics/presentation/systemOverlay.h"
#include "SDL.h" #include <SDL3/SDL.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
@ -176,6 +177,7 @@ bool g_input_multiline = false;
bool g_input_lifecycle_active = false; bool g_input_lifecycle_active = false;
bool g_controller_captured = false; bool g_controller_captured = false;
OverlaySession g_session; OverlaySession g_session;
SDL_Window* g_input_window = nullptr;
void ClearInputEvents() { void ClearInputEvents() {
std::scoped_lock lock(g_input_mutex); std::scoped_lock lock(g_input_mutex);
@ -212,7 +214,7 @@ void RetryVisibilityWakeup() {
} }
SDL_Event event {}; SDL_Event event {};
event.type = type; event.type = type;
if (SDL_PushEvent(&event) > 0) { if (SDL_PushEvent(&event)) {
g_missing_visibility_wakeups--; g_missing_visibility_wakeups--;
} }
} }
@ -252,7 +254,7 @@ void RefreshVisibility() {
std::scoped_lock input_lock(g_input_mutex); std::scoped_lock input_lock(g_input_mutex);
g_visibility_updates.push_back({snapshot.session, visible, capture_controller, g_visibility_updates.push_back({snapshot.session, visible, capture_controller,
capture_keyboard, text_input, multiline}); 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++; g_missing_visibility_wakeups++;
} }
} }
@ -268,16 +270,16 @@ void OnDialogVisibilityChanged(bool, uint64_t) {
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) { uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0); uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100; if ((modifiers & SDL_KMOD_LCTRL) != 0) status |= 0x00000100;
if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200; if ((modifiers & SDL_KMOD_LSHIFT) != 0) status |= 0x00000200;
if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400; if ((modifiers & SDL_KMOD_LALT) != 0) status |= 0x00000400;
if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800; if ((modifiers & SDL_KMOD_LGUI) != 0) status |= 0x00000800;
if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000; if ((modifiers & SDL_KMOD_RCTRL) != 0) status |= 0x00001000;
if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000; if ((modifiers & SDL_KMOD_RSHIFT) != 0) status |= 0x00002000;
if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000; if ((modifiers & SDL_KMOD_RALT) != 0) status |= 0x00004000;
if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000; if ((modifiers & SDL_KMOD_RGUI) != 0) status |= 0x00008000;
if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000; if ((modifiers & SDL_KMOD_NUM) != 0) status |= 0x00010000;
if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000; if ((modifiers & SDL_KMOD_CAPS) != 0) status |= 0x00020000;
return status; return status;
} }
@ -392,14 +394,14 @@ float AlignmentPivot(Ime::Alignment alignment) {
ImGuiKey ControllerButtonToKey(int button) { ImGuiKey ControllerButtonToKey(int button) {
switch (button) { switch (button) {
case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown; case SDL_GAMEPAD_BUTTON_SOUTH: return ImGuiKey_GamepadFaceDown;
case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight; case SDL_GAMEPAD_BUTTON_EAST: return ImGuiKey_GamepadFaceRight;
case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft; case SDL_GAMEPAD_BUTTON_WEST: return ImGuiKey_GamepadFaceLeft;
case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp; case SDL_GAMEPAD_BUTTON_NORTH: return ImGuiKey_GamepadFaceUp;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft; case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight; case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp; case SDL_GAMEPAD_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown; case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
default: return ImGuiKey_None; default: return ImGuiKey_None;
} }
} }
@ -415,9 +417,11 @@ void CheckVulkanResult(VkResult result) {
} // namespace } // namespace
void InitializeSystemOverlayInput() { void InitializeSystemOverlayInput(SDL_Window* window) {
EXIT_IF(window == nullptr);
const Uint32 type = SDL_RegisterEvents(1); const Uint32 type = SDL_RegisterEvents(1);
EXIT_IF(type == static_cast<Uint32>(-1)); EXIT_IF(type == static_cast<Uint32>(-1));
g_input_window = window;
{ {
std::scoped_lock lock(g_visibility_mutex); std::scoped_lock lock(g_visibility_mutex);
g_input_lifecycle_active = true; g_input_lifecycle_active = true;
@ -453,9 +457,10 @@ void ShutdownSystemOverlayInput() {
g_input_controller = false; g_input_controller = false;
g_input_keyboard = false; g_input_keyboard = false;
g_input_multiline = false; g_input_multiline = false;
if (SDL_IsTextInputActive() == SDL_TRUE) { if (g_input_window != nullptr && SDL_TextInputActive(g_input_window)) {
SDL_StopTextInput(); SDL_StopTextInput(g_input_window);
} }
g_input_window = nullptr;
} }
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept { SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
@ -487,13 +492,13 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
g_input_keyboard = update.capture_keyboard; g_input_keyboard = update.capture_keyboard;
g_input_multiline = update.multiline; g_input_multiline = update.multiline;
if (update.text_input) { if (update.text_input) {
SDL_StartTextInput(); SDL_StartTextInput(g_input_window);
} else if (SDL_IsTextInputActive() == SDL_TRUE) { } else if (SDL_TextInputActive(g_input_window)) {
SDL_StopTextInput(); SDL_StopTextInput(g_input_window);
} }
return true; return true;
} }
if (event.type == SDL_CONTROLLERDEVICEREMOVED) { if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
if (g_input_active && g_input_controller) { if (g_input_active && g_input_controller) {
QueueInput({InputKind::ResetController, g_input_session, 0, 0.0f, 0.0f}); 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 auto session = g_input_session;
const uint64_t generation = session.generation; const uint64_t generation = session.generation;
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN || const bool controller_event = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ||
event.type == SDL_CONTROLLERBUTTONUP || event.type == SDL_EVENT_GAMEPAD_BUTTON_UP ||
event.type == SDL_CONTROLLERAXISMOTION; event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION;
if (controller_event && !g_input_controller) { if (controller_event && !g_input_controller) {
return false; return false;
} }
const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING || const bool keyboard_event = event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_TEXT_EDITING ||
event.type == SDL_KEYDOWN || event.type == SDL_KEYUP; event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP;
if (keyboard_event && !g_input_keyboard) { if (keyboard_event && !g_input_keyboard) {
return false; return false;
} }
if (keyboard_event && session.kind == OverlayKind::Error) { if (keyboard_event && session.kind == OverlayKind::Error) {
if (event.type == SDL_KEYDOWN && event.key.repeat == 0 && if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
(event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER)) { (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) {
ErrorDialog::HostAccept(generation); ErrorDialog::HostAccept(generation);
} }
return true; return true;
} }
switch (event.type) { switch (event.type) {
case SDL_TEXTINPUT: { case SDL_EVENT_TEXT_INPUT: {
const auto text = Utf8ToUtf16(event.text.text); const auto text = Utf8ToUtf16(event.text.text);
if (!text.empty()) { if (!text.empty()) {
auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode, auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode,
@ -535,26 +540,24 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
} }
return true; return true;
} }
case SDL_TEXTEDITING: return true; case SDL_EVENT_TEXT_EDITING: return true;
case SDL_KEYDOWN: { case SDL_EVENT_KEY_DOWN: {
g_last_external_keycode = static_cast<uint16_t>(event.key.keysym.scancode); g_last_external_keycode = static_cast<uint16_t>(event.key.scancode);
g_last_external_status = g_last_external_status = ExternalKeyStatus(event.key.mod, false);
ExternalKeyStatus(static_cast<SDL_Keymod>(event.key.keysym.mod), false);
auto action = Ime::ExternalAction::Text; auto action = Ime::ExternalAction::Text;
bool queue = true; bool queue = true;
if (event.key.keysym.sym == SDLK_BACKSPACE) { if (event.key.key == SDLK_BACKSPACE) {
action = Ime::ExternalAction::Backspace; action = Ime::ExternalAction::Backspace;
} else if (event.key.keysym.sym == SDLK_LEFT) { } else if (event.key.key == SDLK_LEFT) {
action = Ime::ExternalAction::MoveLeft; action = Ime::ExternalAction::MoveLeft;
} else if (event.key.keysym.sym == SDLK_RIGHT) { } else if (event.key.key == SDLK_RIGHT) {
action = Ime::ExternalAction::MoveRight; action = Ime::ExternalAction::MoveRight;
} else if (event.key.keysym.sym == SDLK_ESCAPE) { } else if (event.key.key == SDLK_ESCAPE) {
action = Ime::ExternalAction::Cancel; action = Ime::ExternalAction::Cancel;
} else if (event.key.keysym.sym == SDLK_RETURN || } else if (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER) {
event.key.keysym.sym == SDLK_KP_ENTER) {
action = action =
g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept; 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; action = Ime::ExternalAction::None;
} else { } else {
queue = false; queue = false;
@ -566,28 +569,28 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
} }
return true; return true;
} }
case SDL_KEYUP: return true; case SDL_EVENT_KEY_UP: return true;
case SDL_CONTROLLERBUTTONDOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_CONTROLLERBUTTONUP: case SDL_EVENT_GAMEPAD_BUTTON_UP:
QueueInput({InputKind::Button, session, event.cbutton.button, QueueInput({InputKind::Button, session, event.gbutton.button,
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f}); event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
return true; return true;
case SDL_CONTROLLERAXISMOTION: case SDL_EVENT_GAMEPAD_AXIS_MOTION:
QueueInput({InputKind::Axis, session, event.caxis.axis, QueueInput({InputKind::Axis, session, event.gaxis.axis,
static_cast<float>(event.caxis.value), 0.0f}); static_cast<float>(event.gaxis.value), 0.0f});
return true; return true;
case SDL_MOUSEMOTION: case SDL_EVENT_MOUSE_MOTION:
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.motion.x), QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.motion.x),
static_cast<float>(event.motion.y)}); static_cast<float>(event.motion.y)});
return true; return true;
case SDL_MOUSEBUTTONDOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_MOUSEBUTTONUP: case SDL_EVENT_MOUSE_BUTTON_UP:
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.button.x), QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.button.x),
static_cast<float>(event.button.y)}); static_cast<float>(event.button.y)});
QueueInput({InputKind::MouseButton, session, event.button.button, 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; return true;
case SDL_MOUSEWHEEL: case SDL_EVENT_MOUSE_WHEEL:
QueueInput({InputKind::MouseWheel, session, 0, static_cast<float>(event.wheel.x), QueueInput({InputKind::MouseWheel, session, 0, static_cast<float>(event.wheel.x),
static_cast<float>(event.wheel.y)}); static_cast<float>(event.wheel.y)});
return true; return true;
@ -681,9 +684,9 @@ struct SystemOverlay::Impl {
case InputKind::Button: { case InputKind::Button: {
const bool down = event.x != 0.0f; const bool down = event.x != 0.0f;
if (session.kind == OverlayKind::Ime && down) { if (session.kind == OverlayKind::Ime && down) {
if (event.id == SDL_CONTROLLER_BUTTON_B) { if (event.id == SDL_GAMEPAD_BUTTON_EAST) {
Ime::HostCancel(session.generation); Ime::HostCancel(session.generation);
} else if (event.id == SDL_CONTROLLER_BUTTON_Y) { } else if (event.id == SDL_GAMEPAD_BUTTON_NORTH) {
Ime::HostBackspace(session.generation); Ime::HostBackspace(session.generation);
} }
} }
@ -695,19 +698,19 @@ struct SystemOverlay::Impl {
} }
case InputKind::Axis: { case InputKind::Axis: {
const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f); 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, io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f,
std::max(-value, 0.0f)); std::max(-value, 0.0f));
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f, io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f,
std::max(value, 0.0f)); 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, io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f,
std::max(-value, 0.0f)); std::max(-value, 0.0f));
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f, io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f,
std::max(value, 0.0f)); 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; 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; right_stick.y = std::abs(value) > 0.2f ? value : 0.0f;
} }
break; break;

View File

@ -7,6 +7,7 @@
#include <memory> #include <memory>
union SDL_Event; union SDL_Event;
struct SDL_Window;
namespace Libs::Graphics { namespace Libs::Graphics {
@ -17,7 +18,7 @@ struct SystemOverlayVisualState {
uint64_t revision; uint64_t revision;
}; };
void InitializeSystemOverlayInput(); void InitializeSystemOverlayInput(SDL_Window* window);
void ShutdownSystemOverlayInput(); void ShutdownSystemOverlayInput();
bool ProcessSystemOverlayInput(const SDL_Event& event); bool ProcessSystemOverlayInput(const SDL_Event& event);
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept; SystemOverlayVisualState GetSystemOverlayVisualState() noexcept;

View File

@ -1,12 +1,7 @@
#include "graphics/presentation/window/hostInput.h" #include "graphics/presentation/window/hostInput.h"
#include "SDL_error.h" #include <SDL3/SDL.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 "common/assert.h" #include "common/assert.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -80,6 +75,7 @@ struct MouseJoystickState {
}; };
MouseJoystickState g_mouse; MouseJoystickState g_mouse;
SDL_Window* g_mouse_window = nullptr;
std::size_t ControlFromName(std::string_view name) { std::size_t ControlFromName(std::string_view name) {
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(), 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_LEFT: return Controller::PAD_BUTTON_LEFT;
case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN; case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN;
case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT; case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT;
case SDLK_j: return Controller::PAD_BUTTON_CROSS; case SDLK_J: return Controller::PAD_BUTTON_CROSS;
case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE; case SDLK_I: return Controller::PAD_BUTTON_TRIANGLE;
case SDLK_k: return Controller::PAD_BUTTON_SQUARE; case SDLK_K: return Controller::PAD_BUTTON_SQUARE;
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE; case SDLK_L: return Controller::PAD_BUTTON_CIRCLE;
case SDLK_q: return Controller::PAD_BUTTON_L1; case SDLK_Q: return Controller::PAD_BUTTON_L1;
case SDLK_e: return Controller::PAD_BUTTON_R1; case SDLK_E: return Controller::PAD_BUTTON_R1;
case SDLK_z: return Controller::PAD_BUTTON_L2; case SDLK_Z: return Controller::PAD_BUTTON_L2;
case SDLK_c: return Controller::PAD_BUTTON_R2; case SDLK_C: return Controller::PAD_BUTTON_R2;
case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3; case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3;
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3; case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
case SDLK_RETURN: case SDLK_RETURN:
@ -284,35 +280,35 @@ void DefaultKeyboardInput(int key_code, bool down) {
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) { switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return; case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return;
case SDLK_TAB: SetTouchPad(0.75f, down); return; case SDLK_TAB: SetTouchPad(0.75f, down); return;
case SDLK_a: case SDLK_A:
left.left = down; left.left = down;
SetStickAxis(Controller::Axis::LeftX, left.left, left.right); SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
return; return;
case SDLK_d: case SDLK_D:
left.right = down; left.right = down;
SetStickAxis(Controller::Axis::LeftX, left.left, left.right); SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
return; return;
case SDLK_w: case SDLK_W:
left.up = down; left.up = down;
SetStickAxis(Controller::Axis::LeftY, left.up, left.down); SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
return; return;
case SDLK_s: case SDLK_S:
left.down = down; left.down = down;
SetStickAxis(Controller::Axis::LeftY, left.up, left.down); SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
return; return;
case SDLK_f: case SDLK_F:
right.left = down; right.left = down;
SetStickAxis(Controller::Axis::RightX, right.left, right.right); SetStickAxis(Controller::Axis::RightX, right.left, right.right);
return; return;
case SDLK_h: case SDLK_H:
right.right = down; right.right = down;
SetStickAxis(Controller::Axis::RightX, right.left, right.right); SetStickAxis(Controller::Axis::RightX, right.left, right.right);
return; return;
case SDLK_t: case SDLK_T:
right.up = down; right.up = down;
SetStickAxis(Controller::Axis::RightY, right.up, right.down); SetStickAxis(Controller::Axis::RightY, right.up, right.down);
return; return;
case SDLK_g: case SDLK_G:
right.down = down; right.down = down;
SetStickAxis(Controller::Axis::RightY, right.up, right.down); SetStickAxis(Controller::Axis::RightY, right.up, right.down);
return; 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 distance = std::hypot(delta_x, delta_y);
const double scale = const double scale =
std::clamp(distance * GetInputMap().MouseSensitivity() + 16.0, 64.0, 128.0) / distance; 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<int>(std::lround(delta * scale)), 0, 255); return std::clamp(128 + static_cast<int>(std::lround(delta * scale)), 0, 255);
}; };
Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x), Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
@ -340,16 +336,45 @@ void CenterMouseStick() {
} }
bool SetRelativeMouseMode(bool enabled) { bool SetRelativeMouseMode(bool enabled) {
if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) == 0) { if (SDL_SetWindowRelativeMouseMode(g_mouse_window, enabled)) {
return true; return true;
} }
LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError()); LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError());
return false; return false;
} }
int PollMouse(uint64_t now_ms) {
if (now_ms < g_mouse.next_poll) {
return static_cast<int>(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 } // namespace
void HostInputInit() { void HostInputInit(SDL_Window* window) {
GetInputMap(); 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) { void HostInputKey(int key_code, bool down) {
@ -380,51 +405,33 @@ void HostInputToggleMouseToJoystick() {
if (!SetRelativeMouseMode(true)) { if (!SetRelativeMouseMode(true)) {
return; return;
} }
int ignored_x = 0; g_mouse.enabled = true;
int ignored_y = 0;
SDL_GetRelativeMouseState(&ignored_x, &ignored_y);
g_mouse.enabled = true;
g_mouse.next_poll = SDL_GetTicks64() + MOUSE_POLL_INTERVAL_MS;
LOGF("Mouse to right stick: enabled (F7 to release)\n"); 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<int>(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) { 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(); CenterMouseStick();
if (SDL_WaitEvent(event) == 0) { has_event = SDL_WaitEvent(event);
if (!has_event) {
EXIT("%s\n", SDL_GetError()); 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()); if (has_event && event->type == SDL_EVENT_WINDOW_FOCUS_LOST &&
SDL_ClearError(); event->window.windowID == SDL_GetWindowID(g_mouse_window)) {
if (SDL_WaitEventTimeout(event, timeout_ms) != 0) { g_mouse.next_poll = 0;
return true; CenterMouseStick();
} }
if (SDL_GetError()[0] != '\0') { return has_event;
EXIT("%s\n", SDL_GetError());
}
return false;
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics

View File

@ -3,11 +3,13 @@
#include <cstdint> #include <cstdint>
struct SDL_Window;
union SDL_Event; union SDL_Event;
namespace Libs::Graphics { namespace Libs::Graphics {
void HostInputInit(); void HostInputInit(SDL_Window* window);
void HostInputShutdown();
void HostInputKey(int key_code, bool down); void HostInputKey(int key_code, bool down);
void HostInputMouseButton(uint8_t mouse_button, bool down); void HostInputMouseButton(uint8_t mouse_button, bool down);
void HostInputToggleMouseToJoystick(); void HostInputToggleMouseToJoystick();

View File

@ -532,7 +532,9 @@ void Swapchain::Recreate(bool surface_lost) {
#if defined(__APPLE__) #if defined(__APPLE__)
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the // Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
// window's view/layer and must run on the main thread on macOS. // 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<WindowContext*>(window)->RecreateSurface(); },
&m_window, true));
#else #else
m_window.RecreateSurface(); m_window.RecreateSurface();
#endif #endif

View File

@ -1,20 +1,6 @@
#include "SDL.h" #include <SDL3/SDL.h>
#include "SDL_error.h" #include <SDL3/SDL_vulkan.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 "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
@ -700,25 +686,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
return device; return device;
} }
static void VulkanGetExtensions(SDL_Window* window, VulkanExtensions& r) { static void VulkanGetExtensions(VulkanExtensions& r) {
EXIT_IF(window == nullptr);
uint32_t required_extensions_count = 0; uint32_t required_extensions_count = 0;
auto sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, nullptr); const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
EXIT_NOT_IMPLEMENTED(extensions == nullptr);
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0); EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
r.required_extensions.assign(extensions, extensions + required_extensions_count);
r.required_extensions =
std::vector<const char*>(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.available_extensions = r.available_extensions =
EnumerateVulkan<vk::ExtensionProperties>( // @suppress("Ambiguous problem") EnumerateVulkan<vk::ExtensionProperties>( // @suppress("Ambiguous problem")
@ -899,7 +873,7 @@ void WindowContext::CreateVulkan() {
VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr); VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr);
VulkanExtensions r; VulkanExtensions r;
VulkanGetExtensions(window, r); VulkanGetExtensions(r);
VulkanCheckInstanceVersion(); VulkanCheckInstanceVersion();
vk::ApplicationInfo app_info {}; vk::ApplicationInfo app_info {};
@ -986,8 +960,8 @@ void WindowContext::CreateVulkan() {
} }
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE; vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance), if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
&native_surface) == SDL_FALSE) { nullptr, &native_surface)) {
EXIT("Could not create a Vulkan surface"); EXIT("Could not create a Vulkan surface");
} }
surface = native_surface; surface = native_surface;
@ -1100,8 +1074,8 @@ void WindowContext::RecreateSurface() {
surface = nullptr; surface = nullptr;
} }
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE; vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance), if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
&native_surface) == SDL_FALSE) { nullptr, &native_surface)) {
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError()); EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
} }
surface = native_surface; surface = native_surface;
@ -1137,7 +1111,7 @@ WindowContext::~WindowContext() {
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
window = nullptr; window = nullptr;
} }
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics

View File

@ -1,23 +1,7 @@
#include "graphics/presentation/window.h" #include "graphics/presentation/window.h"
#include "SDL.h" #include <SDL3/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 "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
@ -72,39 +56,39 @@ struct EventKeyboard {
static uint32_t ControllerButtonToPadButton(int button) { static uint32_t ControllerButtonToPadButton(int button) {
switch (button) { switch (button) {
case SDL_CONTROLLER_BUTTON_A: return Controller::PAD_BUTTON_CROSS; case SDL_GAMEPAD_BUTTON_SOUTH: return Controller::PAD_BUTTON_CROSS;
case SDL_CONTROLLER_BUTTON_B: return Controller::PAD_BUTTON_CIRCLE; case SDL_GAMEPAD_BUTTON_EAST: return Controller::PAD_BUTTON_CIRCLE;
case SDL_CONTROLLER_BUTTON_X: return Controller::PAD_BUTTON_SQUARE; case SDL_GAMEPAD_BUTTON_WEST: return Controller::PAD_BUTTON_SQUARE;
case SDL_CONTROLLER_BUTTON_Y: return Controller::PAD_BUTTON_TRIANGLE; case SDL_GAMEPAD_BUTTON_NORTH: return Controller::PAD_BUTTON_TRIANGLE;
case SDL_CONTROLLER_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS; case SDL_GAMEPAD_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return Controller::PAD_BUTTON_L3; case SDL_GAMEPAD_BUTTON_LEFT_STICK: return Controller::PAD_BUTTON_L3;
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return Controller::PAD_BUTTON_R3; case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return Controller::PAD_BUTTON_R3;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return Controller::PAD_BUTTON_L1; case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return Controller::PAD_BUTTON_L1;
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return Controller::PAD_BUTTON_R1; case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return Controller::PAD_BUTTON_R1;
case SDL_CONTROLLER_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP; case SDL_GAMEPAD_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN; case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT; case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT; case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
case SDL_CONTROLLER_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD; case SDL_GAMEPAD_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
default: return 0; default: return 0;
} }
} }
static Controller::Axis ControllerAxisFromSdl(int axis_id) { static Controller::Axis ControllerAxisFromSdl(int axis_id) {
switch (axis_id) { switch (axis_id) {
case SDL_CONTROLLER_AXIS_LEFTX: return Controller::Axis::LeftX; case SDL_GAMEPAD_AXIS_LEFTX: return Controller::Axis::LeftX;
case SDL_CONTROLLER_AXIS_LEFTY: return Controller::Axis::LeftY; case SDL_GAMEPAD_AXIS_LEFTY: return Controller::Axis::LeftY;
case SDL_CONTROLLER_AXIS_RIGHTX: return Controller::Axis::RightX; case SDL_GAMEPAD_AXIS_RIGHTX: return Controller::Axis::RightX;
case SDL_CONTROLLER_AXIS_RIGHTY: return Controller::Axis::RightY; case SDL_GAMEPAD_AXIS_RIGHTY: return Controller::Axis::RightY;
case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return Controller::Axis::TriggerLeft; case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return Controller::Axis::TriggerLeft;
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return Controller::Axis::TriggerRight; case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return Controller::Axis::TriggerRight;
default: return Controller::Axis::AxisMax; default: return Controller::Axis::AxisMax;
} }
} }
static bool ControllerAxisIsTrigger(int axis_id) { static bool ControllerAxisIsTrigger(int axis_id) {
return axis_id == SDL_CONTROLLER_AXIS_TRIGGERLEFT || return axis_id == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
axis_id == SDL_CONTROLLER_AXIS_TRIGGERRIGHT; axis_id == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
} }
static int ControllerAxisValueFromSdl(int axis_id, int axis_value) { static int ControllerAxisValueFromSdl(int axis_id, int axis_value) {
@ -165,22 +149,6 @@ struct EventController {
double timestamp_seconds; 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 { namespace {
std::unique_ptr<WindowContext> g_window; std::unique_ptr<WindowContext> g_window;
@ -213,11 +181,8 @@ static void ToggleDesktopFullscreen() {
} }
const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window)); const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window));
const bool fullscreen = const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0u;
(flags & static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u; if (!SDL_SetWindowFullscreen(g_window->window, !fullscreen)) {
const auto mode =
fullscreen ? 0u : static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) {
LOGF("Toggle fullscreen failed: %s\n", SDL_GetError()); LOGF("Toggle fullscreen failed: %s\n", SDL_GetError());
} }
} }
@ -247,7 +212,7 @@ static void GameEventKeyboard(const EventKeyboard& key) {
break; break;
case SDLK_RETURN: case SDLK_RETURN:
case SDLK_KP_ENTER: case SDLK_KP_ENTER:
if (!key.repeat && (key.mod & KMOD_ALT) != 0) { if (!key.repeat && (key.mod & SDL_KMOD_ALT) != 0) {
ToggleDesktopFullscreen(); ToggleDesktopFullscreen();
fullscreen_key = key.key_code; fullscreen_key = key.key_code;
} }
@ -340,15 +305,15 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
#endif #endif
if (f.added) { if (f.added) {
auto* pad = SDL_GameControllerOpen(f.id); auto* pad = SDL_OpenGamepad(f.id);
EXIT_NOT_IMPLEMENTED(pad == nullptr); EXIT_NOT_IMPLEMENTED(pad == nullptr);
int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad)); int id = SDL_GetJoystickID(SDL_GetGamepadJoystick(pad));
Controller::Connect(id); Controller::Connect(id);
} }
if (f.removed) { if (f.removed) {
Controller::Disconnect(f.id); Controller::Disconnect(f.id);
SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f.id)); SDL_CloseGamepad(SDL_GetGamepadFromID(f.id));
} }
if (f.down || f.up) { 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) { void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
const auto& window_event = event; const auto& window_event = event;
switch (window_event.event) { switch (window_event.type) {
case SDL_WINDOWEVENT_SHOWN: case SDL_EVENT_WINDOW_SHOWN:
LOGF("Window %" PRIu32 " shown\n", window_event.windowID); LOGF("Window %" PRIu32 " shown\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_HIDDEN: case SDL_EVENT_WINDOW_HIDDEN:
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID); LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_EXPOSED: case SDL_EVENT_WINDOW_EXPOSED:
LOGF("Window %" PRIu32 " exposed\n", window_event.windowID); LOGF("Window %" PRIu32 " exposed\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_MOVED: case SDL_EVENT_WINDOW_MOVED:
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID, LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID,
window_event.data1, window_event.data2); window_event.data1, window_event.data2);
break; break;
case SDL_WINDOWEVENT_RESIZED: case SDL_EVENT_WINDOW_RESIZED:
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID, LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID,
window_event.data1, window_event.data2); window_event.data1, window_event.data2);
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
Resize(window_event.data1, window_event.data2);
break; break;
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n", LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n",
window_event.windowID, window_event.data1, window_event.data2); window_event.windowID, window_event.data1, window_event.data2);
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
Resize(window_event.data1, window_event.data2); Resize(window_event.data1, window_event.data2);
break; break;
case SDL_WINDOWEVENT_MINIMIZED: case SDL_EVENT_WINDOW_MINIMIZED:
LOGF("Window %" PRIu32 " minimized\n", window_event.windowID); LOGF("Window %" PRIu32 " minimized\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_MAXIMIZED: case SDL_EVENT_WINDOW_MAXIMIZED:
LOGF("Window %" PRIu32 " maximized\n", window_event.windowID); LOGF("Window %" PRIu32 " maximized\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_RESTORED: case SDL_EVENT_WINDOW_RESTORED:
LOGF("Window %" PRIu32 " restored\n", window_event.windowID); LOGF("Window %" PRIu32 " restored\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_ENTER: case SDL_EVENT_WINDOW_MOUSE_ENTER:
LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID); LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_LEAVE: case SDL_EVENT_WINDOW_MOUSE_LEAVE:
LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID); LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_FOCUS_GAINED: case SDL_EVENT_WINDOW_FOCUS_GAINED:
LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID); LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_FOCUS_LOST: case SDL_EVENT_WINDOW_FOCUS_LOST:
LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID); LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID);
break; break;
case SDL_WINDOWEVENT_CLOSE: case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
LOGF("Window %" PRIu32 " closed\n", window_event.windowID); LOGF("Window %" PRIu32 " closed\n", window_event.windowID);
break; break;
default: default:
LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window_event.windowID, LOGF("Window %" PRIu32 " got unknown event %" PRIu32 "\n", window_event.windowID,
window_event.event); static_cast<uint32_t>(window_event.type));
break; break;
} }
} }
void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) { void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
bool sdl = false; switch (display.type) {
case SDL_EVENT_DISPLAY_ORIENTATION: {
switch (display.event) { LOGF("Display %" PRIu32 " changed orientation to %d - ", display.displayID,
case SDL_DISPLAYEVENT_ORIENTATION: sdl = true; [[fallthrough]]; static_cast<int>(display.data1));
case static_cast<Uint8>(DisplayOrientation::DisplayEventOrientation): {
LOGF("Display %" PRIu32 "[%s] changed orientation to %d - ", display.display,
sdl ? "SDL" : "Kyty", static_cast<int>(display.data1));
switch (display.data1) { switch (display.data1) {
case SDL_ORIENTATION_UNKNOWN: LOGF("UNKNOWN\n"); break; case SDL_ORIENTATION_UNKNOWN: LOGF("UNKNOWN\n"); break;
@ -488,8 +445,8 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
break; break;
} }
default: default:
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx8 "\n", display.display, LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx32 "\n", display.displayID,
display.event); static_cast<uint32_t>(display.type));
break; break;
} }
} }
@ -497,10 +454,9 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
void WindowContext::ProcessEvent(double time_s) { void WindowContext::ProcessEvent(double time_s) {
auto& game = loop; auto& game = loop;
auto* event = &game.event; auto* event = &game.event;
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE); if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) &&
if ((event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) && event->key.key == SDLK_F7) {
event->key.keysym.sym == SDLK_F7) { if (event->type == SDL_EVENT_KEY_DOWN && event->key.repeat == 0) {
if (event->type == SDL_KEYDOWN && event->key.repeat == 0) {
HostInputToggleMouseToJoystick(); HostInputToggleMouseToJoystick();
} }
return; return;
@ -508,34 +464,42 @@ void WindowContext::ProcessEvent(double time_s) {
if (ProcessSystemOverlayInput(*event)) { if (ProcessSystemOverlayInput(*event)) {
return; 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) { 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_EVENT_KEY_DOWN:
case SDL_KEYUP: { case SDL_EVENT_KEY_UP: {
EventKeyboard key {}; EventKeyboard key {};
key.down = (event->type == SDL_KEYDOWN); key.down = (event->type == SDL_EVENT_KEY_DOWN);
key.up = (event->type == SDL_KEYUP); key.up = (event->type == SDL_EVENT_KEY_UP);
key.pressed = (event->key.state == SDL_PRESSED); key.pressed = event->key.down;
key.released = (event->key.state == SDL_RELEASED); key.released = !event->key.down;
key.repeat = (event->key.repeat != 0u); key.repeat = (event->key.repeat != 0u);
key.scan_code = event->key.keysym.scancode; key.scan_code = event->key.scancode;
key.key_code = event->key.keysym.sym; key.key_code = event->key.key;
key.mod = event->key.keysym.mod; key.mod = event->key.mod;
key.timestamp_seconds = time_s; key.timestamp_seconds = time_s;
GameEventKeyboard(key); GameEventKeyboard(key);
@ -543,24 +507,20 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_WINDOWEVENT: ProcessWindowEvent(event->window); break; case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP: {
case SDL_DISPLAYEVENT: ProcessDisplayEvent(event->display); break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
EventMouse mb {}; EventMouse mb {};
mb.down = (event->button.type == SDL_MOUSEBUTTONDOWN); mb.down = (event->button.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
mb.up = (event->button.type == SDL_MOUSEBUTTONUP); mb.up = (event->button.type == SDL_EVENT_MOUSE_BUTTON_UP);
mb.left = (event->button.button == SDL_BUTTON_LEFT); mb.left = (event->button.button == SDL_BUTTON_LEFT);
mb.middle = (event->button.button == SDL_BUTTON_MIDDLE); mb.middle = (event->button.button == SDL_BUTTON_MIDDLE);
mb.right = (event->button.button == SDL_BUTTON_RIGHT); mb.right = (event->button.button == SDL_BUTTON_RIGHT);
mb.x1 = (event->button.button == SDL_BUTTON_X1); mb.x1 = (event->button.button == SDL_BUTTON_X1);
mb.x2 = (event->button.button == SDL_BUTTON_X2); mb.x2 = (event->button.button == SDL_BUTTON_X2);
mb.touch = (event->button.which == SDL_TOUCH_MOUSEID); mb.touch = (event->button.which == SDL_TOUCH_MOUSEID);
mb.pressed = (event->button.state == SDL_PRESSED); mb.pressed = event->button.down;
mb.released = (event->button.state == SDL_RELEASED); mb.released = !event->button.down;
mb.num_of_clicks = event->button.clicks; mb.num_of_clicks = event->button.clicks;
mb.wheel = false; mb.wheel = false;
mb.x = event->button.x; mb.x = event->button.x;
@ -575,7 +535,7 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_MOUSEWHEEL: { case SDL_EVENT_MOUSE_WHEEL: {
EventMouse mb {}; EventMouse mb {};
mb.down = false; mb.down = false;
@ -602,7 +562,7 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_MOUSEMOTION: { case SDL_EVENT_MOUSE_MOTION: {
EventMouse mb {}; EventMouse mb {};
mb.down = false; mb.down = false;
@ -629,16 +589,16 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_FINGERMOTION: case SDL_EVENT_FINGER_MOTION:
case SDL_FINGERDOWN: case SDL_EVENT_FINGER_DOWN:
case SDL_FINGERUP: { case SDL_EVENT_FINGER_UP: {
EventFinger f {}; EventFinger f {};
f.down = (event->tfinger.type == SDL_FINGERDOWN); f.down = (event->tfinger.type == SDL_EVENT_FINGER_DOWN);
f.up = (event->tfinger.type == SDL_FINGERUP); f.up = (event->tfinger.type == SDL_EVENT_FINGER_UP);
f.motion = (event->tfinger.type == SDL_FINGERMOTION); f.motion = (event->tfinger.type == SDL_EVENT_FINGER_MOTION);
f.finger_id = static_cast<int>(event->tfinger.fingerId); f.finger_id = static_cast<int>(event->tfinger.fingerID);
f.touch_id = static_cast<int>(event->tfinger.touchId); f.touch_id = static_cast<int>(event->tfinger.touchID);
f.x = event->tfinger.x; f.x = event->tfinger.x;
f.y = event->tfinger.y; f.y = event->tfinger.y;
f.dx = event->tfinger.dx; f.dx = event->tfinger.dx;
@ -651,13 +611,13 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_CONTROLLERAXISMOTION: { case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
EventController c {}; EventController c {};
c.id = event->caxis.which; c.id = event->gaxis.which;
c.button = SDL_CONTROLLER_BUTTON_INVALID; c.button = SDL_GAMEPAD_BUTTON_INVALID;
c.axis_id = event->caxis.axis; c.axis_id = event->gaxis.axis;
c.axis_value = event->caxis.value; c.axis_value = event->gaxis.value;
c.down = false; c.down = false;
c.up = false; c.up = false;
c.added = false; c.added = false;
@ -673,22 +633,22 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_CONTROLLERBUTTONDOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_CONTROLLERBUTTONUP: { case SDL_EVENT_GAMEPAD_BUTTON_UP: {
EventController c {}; EventController c {};
c.id = event->cbutton.which; c.id = event->gbutton.which;
c.button = event->cbutton.button; c.button = event->gbutton.button;
c.axis_id = SDL_CONTROLLER_AXIS_INVALID; c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
c.axis_value = 0; c.axis_value = 0;
c.down = (event->cbutton.type == SDL_CONTROLLERBUTTONDOWN); c.down = event->gbutton.down;
c.up = (event->cbutton.type == SDL_CONTROLLERBUTTONUP); c.up = !event->gbutton.down;
c.added = false; c.added = false;
c.removed = false; c.removed = false;
c.remapped = false; c.remapped = false;
c.axis = false; c.axis = false;
c.pressed = (event->cbutton.state == SDL_PRESSED); c.pressed = event->gbutton.down;
c.released = (event->cbutton.state == SDL_RELEASED); c.released = !event->gbutton.down;
c.timestamp_seconds = time_s; c.timestamp_seconds = time_s;
GameEventController(c); GameEventController(c);
@ -696,44 +656,44 @@ void WindowContext::ProcessEvent(double time_s) {
break; break;
} }
case SDL_CONTROLLERTOUCHPADDOWN: case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_CONTROLLERTOUCHPADMOTION: case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_CONTROLLERTOUCHPADUP: case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
if (event->ctouchpad.touchpad == 0) { if (event->gtouchpad.touchpad == 0) {
Controller::SetTouchPad(event->ctouchpad.which, event->ctouchpad.finger, Controller::SetTouchPad(event->gtouchpad.which, event->gtouchpad.finger,
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP, event->gtouchpad.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
event->ctouchpad.x, event->ctouchpad.y); event->gtouchpad.x, event->gtouchpad.y);
} }
break; break;
case SDL_CONTROLLERSENSORUPDATE: { case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
const auto& sensor = event->csensor; const auto& sensor = event->gsensor;
if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) { if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) {
Controller::SetSensor(sensor.which, Controller::SetSensor(sensor.which,
sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel
: Controller::Sensor::Gyro, : Controller::Sensor::Gyro,
sensor.data, sensor.data,
sensor.timestamp_us != 0 sensor.sensor_timestamp != 0
? sensor.timestamp_us ? sensor.sensor_timestamp / 1000
: static_cast<uint64_t>(sensor.timestamp) * 1000); : sensor.timestamp / 1000);
} }
break; break;
} }
case SDL_CONTROLLERDEVICEADDED: case SDL_EVENT_GAMEPAD_ADDED:
case SDL_CONTROLLERDEVICEREMOVED: case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_CONTROLLERDEVICEREMAPPED: { case SDL_EVENT_GAMEPAD_REMAPPED: {
EventController c {}; EventController c {};
c.id = event->cdevice.which; c.id = event->gdevice.which;
c.button = SDL_CONTROLLER_BUTTON_INVALID; c.button = SDL_GAMEPAD_BUTTON_INVALID;
c.axis_id = SDL_CONTROLLER_AXIS_INVALID; c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
c.axis_value = 0; c.axis_value = 0;
c.down = false; c.down = false;
c.up = false; c.up = false;
c.added = (event->cdevice.type == SDL_CONTROLLERDEVICEADDED); c.added = (event->gdevice.type == SDL_EVENT_GAMEPAD_ADDED);
c.removed = (event->cdevice.type == SDL_CONTROLLERDEVICEREMOVED); c.removed = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMOVED);
c.remapped = (event->cdevice.type == SDL_CONTROLLERDEVICEREMAPPED); c.remapped = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMAPPED);
c.axis = false; c.axis = false;
c.pressed = false; c.pressed = false;
c.released = false; c.released = false;
@ -746,47 +706,6 @@ void WindowContext::ProcessEvent(double time_s) {
} }
} }
void WindowContext::RunOnMainThread(std::function<void()> 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<std::function<void()>> 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() { void WindowContext::Run() {
Common::Timer timer; Common::Timer timer;
timer.Start(); timer.Start();
@ -796,7 +715,6 @@ void WindowContext::Run() {
loop.paused.store(false, std::memory_order_release); loop.paused.store(false, std::memory_order_release);
while (!loop.need_exit) { while (!loop.need_exit) {
DrainMainThreadTasks();
if (loop.paused.load(std::memory_order_acquire)) { if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) { if (!timer.IsPaused()) {
timer.Pause(); timer.Pause();
@ -820,17 +738,15 @@ static void WindowCreate(WindowContext& context) {
int width = static_cast<int>(context.graphic_ctx.screen_width); int width = static_cast<int>(context.graphic_ctx.screen_width);
int height = static_cast<int>(context.graphic_ctx.screen_height); int height = static_cast<int>(context.graphic_ctx.screen_height);
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); #if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); // RenderDoc can hide Wayland Vulkan surface support; prefer X11 for captures.
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (Config::RenderDocEnabled()) {
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0"); SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland");
}
#endif #endif
if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
EXIT("%s\n", SDL_GetError()); EXIT("%s\n", SDL_GetError());
} }
HostInputInit();
InitializeSystemOverlayInput();
LOGF("WindowCreate(): width = %d, height = %d\n", width, height); 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 // 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. // override, and fall back to SDL's normal loader search when no bundle is present.
if (std::getenv("SDL_VULKAN_LIBRARY") == nullptr) { 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; const std::string base_path_str = base_path;
SDL_free(base_path);
std::string moltenvk_path = base_path_str + "libMoltenVK.dylib"; std::string moltenvk_path = base_path_str + "libMoltenVK.dylib";
if (!Common::File::IsFileExisting(moltenvk_path)) { if (!Common::File::IsFileExisting(moltenvk_path)) {
moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib"; moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib";
} }
if (Common::File::IsFileExisting(moltenvk_path) && 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()); LOGF("Vulkan loader: %s\n", moltenvk_path.c_str());
} }
} }
@ -861,21 +776,23 @@ static void WindowCreate(WindowContext& context) {
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS); window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
} }
#endif #endif
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED, context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, width, height, window_flags);
SDL_WINDOWPOS_CENTERED, width, height, window_flags);
if (context.window == nullptr) { if (context.window == nullptr) {
EXIT("%s\n", SDL_GetError()); 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(); context.UpdateIcon();
} }
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept { uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN); auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
if (fullscreen) { if (fullscreen) {
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP); flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN);
} }
return flags; return flags;
} }
@ -906,6 +823,7 @@ void WindowRun() {
void WindowShutdown() { void WindowShutdown() {
if (g_window != nullptr) { if (g_window != nullptr) {
HostInputShutdown();
Controller::EmergencyShutdown(); Controller::EmergencyShutdown();
g_window.reset(); g_window.reset();
} }
@ -941,7 +859,7 @@ struct WindowIcon {
void* pixels = nullptr; void* pixels = nullptr;
~WindowIcon() { ~WindowIcon() {
SDL_FreeSurface(surface); SDL_DestroySurface(surface);
stbi_image_free(pixels); stbi_image_free(pixels);
} }
}; };
@ -965,8 +883,8 @@ static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* ico
EXIT_IF(icon->pixels == nullptr); EXIT_IF(icon->pixels == nullptr);
icon->surface = SDL_CreateRGBSurfaceWithFormatFrom(icon->pixels, width, height, 32, width * 4, icon->surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, icon->pixels,
SDL_PIXELFORMAT_RGBA32); width * 4);
EXIT_NOT_IMPLEMENTED(icon->surface == nullptr); EXIT_NOT_IMPLEMENTED(icon->surface == nullptr);
} }
@ -1028,7 +946,16 @@ void WindowContext::UpdateTitle() {
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""), (has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
device_name, processor_name, frame_num, current_fps); 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<TitleUpdate*>(data);
SDL_SetWindowTitle(title.window, title.text->c_str());
},
&update, true));
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics

View File

@ -1,15 +1,14 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_ #ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_ #define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
#include "SDL_events.h" #include <SDL3/SDL.h>
#include "SDL_video.h"
#include "common/threads.h" #include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -48,9 +47,6 @@ struct WindowContext {
void ProcessDisplayEvent(const SDL_DisplayEvent& event); void ProcessDisplayEvent(const SDL_DisplayEvent& event);
void ProcessEvent(double time_seconds); void ProcessEvent(double time_seconds);
void Run(); void Run();
// SDL window operations must complete on the main thread.
void RunOnMainThread(std::function<void()> task);
void DrainMainThreadTasks();
GraphicContext graphic_ctx; GraphicContext graphic_ctx;
SDL_Window* window = nullptr; SDL_Window* window = nullptr;
@ -61,12 +57,6 @@ struct WindowContext {
WindowLoopState loop; WindowLoopState loop;
Common::Mutex mutex; Common::Mutex mutex;
Common::Mutex main_task_mutex;
Common::CondVar main_task_done;
std::vector<std::function<void()>> 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 } // namespace Libs::Graphics

View File

@ -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) if(WIN32)
target_link_libraries(launcher setupapi) target_link_libraries(launcher setupapi)
endif() endif()

View File

@ -3,7 +3,7 @@
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
#include "configuration.h" #include "configuration.h"
#include "mandatoryLineEdit.h" #include "mandatoryLineEdit.h"
#include "SDL.h" #include <SDL3/SDL.h>
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QCheckBox> #include <QCheckBox>
@ -173,17 +173,18 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
microphone->clear(); microphone->clear();
microphone->addItem(tr("None"), QString {}); microphone->addItem(tr("None"), QString {});
microphone->setToolTip(tr("Microphone used by games. None supplies silence.")); microphone->setToolTip(tr("Microphone used by games. None supplies silence."));
SDL_SetMainReady(); if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) { int device_count = 0;
const int device_count = SDL_GetNumAudioDevices(SDL_TRUE); SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count);
for (int i = 0; i < device_count; i++) { 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); const auto name = QString::fromUtf8(device);
if (microphone->findData(name) < 0) { if (microphone->findData(name) < 0) {
microphone->addItem(name, name); microphone->addItem(name, name);
} }
} }
} }
SDL_free(devices);
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
} else { } else {
microphone->setToolTip(tr("Microphones could not be listed: %1") microphone->setToolTip(tr("Microphones could not be listed: %1")

View File

@ -1,6 +1,6 @@
#include "libs/audio.h" #include "libs/audio.h"
#include "SDL.h" #include <SDL3/SDL.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
@ -115,8 +115,7 @@ private:
int channels_num = 0; int channels_num = 0;
int volume[12] = {}; int volume[12] = {};
SDL_AudioDeviceID audio_device = 0; SDL_AudioStream* stream = nullptr;
SDL_AudioSpec audio_spec = {};
}; };
struct PortIn { struct PortIn {
@ -127,7 +126,8 @@ private:
uint32_t freq = 0; uint32_t freq = 0;
uint32_t bytes_per_frame = 0; uint32_t bytes_per_frame = 0;
uint64_t last_input_time = 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. 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) { 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) { bool Audio::OpenSdlDevice(PortOut* port) {
EXIT_IF(port == nullptr); 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()); LOGF("AudioOut: SDL audio init failed: %s\n", SDL_GetError());
return false; return false;
} }
@ -256,37 +256,34 @@ bool Audio::OpenSdlDevice(PortOut* port) {
SDL_AudioSpec desired {}; SDL_AudioSpec desired {};
desired.freq = static_cast<int>(port->freq); desired.freq = static_cast<int>(port->freq);
desired.format = SdlFormat(port->format); desired.format = SdlFormat(port->format);
desired.channels = static_cast<Uint8>(OutputChannels(*port)); desired.channels = static_cast<int>(OutputChannels(*port));
desired.samples = static_cast<Uint16>(port->samples_num);
desired.callback = nullptr;
SDL_AudioSpec obtained {}; port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, nullptr,
nullptr);
port->audio_device = if (port->stream == nullptr) {
SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); LOGF("AudioOut: SDL_OpenAudioDeviceStream failed: %s\n", SDL_GetError());
if (port->audio_device == 0) { SDL_QuitSubSystem(SDL_INIT_AUDIO);
LOGF("AudioOut: SDL_OpenAudioDevice failed: %s\n", SDL_GetError()); return false;
}
if (!SDL_ResumeAudioStreamDevice(port->stream)) {
LOGF("AudioOut: SDL_ResumeAudioStreamDevice failed: %s\n", SDL_GetError());
CloseSdlDevice(port);
return false; return false;
} }
port->audio_spec = obtained; LOGF("AudioOut: opened SDL stream (%d Hz, %d ch, format 0x%04x)\n", desired.freq,
SDL_PauseAudioDevice(port->audio_device, 0); desired.channels, static_cast<unsigned>(desired.format));
LOGF("AudioOut: opened SDL device (%d Hz, %u ch, format 0x%04x)\n", obtained.freq,
obtained.channels, obtained.format);
return true; return true;
} }
void Audio::CloseSdlDevice(PortOut* port) { void Audio::CloseSdlDevice(PortOut* port) {
EXIT_IF(port == nullptr); EXIT_IF(port == nullptr);
if (port->audio_device != 0 && SDL_WasInit(SDL_INIT_AUDIO) != 0) { if (port->stream != nullptr) {
SDL_ClearQueuedAudio(port->audio_device); SDL_DestroyAudioStream(port->stream);
SDL_CloseAudioDevice(port->audio_device); SDL_QuitSubSystem(SDL_INIT_AUDIO);
} }
port->stream = nullptr;
port->audio_device = 0;
port->audio_spec = {};
} }
const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data, 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) { bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
EXIT_IF(port == nullptr); EXIT_IF(port == nullptr);
if (port->audio_device == 0 || data == nullptr) { if (port->stream == nullptr || data == nullptr) {
return false; return false;
} }
@ -374,56 +371,25 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
const auto prepared_size = const auto prepared_size =
BytesPerSample(port->format) * output_channels * port->samples_num; BytesPerSample(port->format) * output_channels * port->samples_num;
std::vector<uint8_t> 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<Uint8>(output_channels),
static_cast<int>(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<int>(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<uint32_t>(cvt.len_cvt);
}
if (blocking) { if (blocking) {
constexpr uint64_t target_latency_us = 40000; constexpr uint64_t target_latency_us = 40000;
const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0; const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0;
const auto buffers = const auto buffers =
buffer_us != 0 ? static_cast<uint32_t>((target_latency_us + buffer_us - 1) / buffer_us) buffer_us != 0 ? static_cast<uint32_t>((target_latency_us + buffer_us - 1) / buffer_us)
: 2u; : 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(); const auto wait_start = LibKernel::KernelGetProcessTime();
while (SDL_GetQueuedAudioSize(port->audio_device) > min_queued_size) { while (SDL_GetAudioStreamQueued(port->stream) > static_cast<int>(min_queued_size)) {
if (LibKernel::KernelGetProcessTime() - wait_start > 200000) { if (LibKernel::KernelGetProcessTime() - wait_start > 200000) {
SDL_ClearQueuedAudio(port->audio_device); SDL_ClearAudioStream(port->stream);
break; break;
} }
Common::Thread::SleepMicro(1000); Common::Thread::SleepMicro(1000);
} }
} }
if (SDL_QueueAudio(port->audio_device, queue_data, queue_size) < 0) { if (!SDL_PutAudioStreamData(port->stream, prepared_data, static_cast<int>(prepared_size))) {
LOGF("AudioOut: SDL_QueueAudio failed: %s\n", SDL_GetError()); LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError());
return false; return false;
} }
@ -498,7 +464,7 @@ bool Audio::AudioOutHasDevice(Id handle) {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX && 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) { 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; bool any_port_has_device = false;
for (uint32_t i = 0; i < num; i++) { 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; any_port_has_device = true;
break; break;
} }
@ -583,35 +549,65 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
return first_port.samples_num; 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) { void Audio::OpenSdlDevice(PortIn* port, Format format) {
const auto& name = Config::GetAudioInputDevice(); const auto& name = Config::GetAudioInputDevice();
if (name.empty()) { if (name.empty()) {
return; return;
} }
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError()); LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError());
return; 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 {}; SDL_AudioSpec desired {};
desired.freq = static_cast<int>(port->freq); desired.freq = static_cast<int>(port->freq);
desired.format = SdlFormat(format); desired.format = SdlFormat(format);
desired.channels = static_cast<Uint8>(port->bytes_per_frame / BytesPerSample(format)); desired.channels = static_cast<int>(port->bytes_per_frame / BytesPerSample(format));
desired.samples = static_cast<Uint16>(port->samples_num); port->stream = SDL_OpenAudioDeviceStream(device, &desired, nullptr, nullptr);
// SDL converts capture data to the guest format when allowed_changes is zero. if (port->stream == nullptr) {
port->audio_device = SDL_OpenAudioDevice(name.c_str(), 1, &desired, nullptr, 0);
if (port->audio_device == 0) {
LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError()); LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
return; 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) { void Audio::CloseSdlDevice(PortIn* port) {
if (port->audio_device != 0) { if (port->stream != nullptr) {
SDL_CloseAudioDevice(port->audio_device); SDL_DestroyAudioStream(port->stream);
SDL_QuitSubSystem(SDL_INIT_AUDIO); 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) { if (port == nullptr) {
return AUDIO_IN_ERROR_INVALID_HANDLE; return AUDIO_IN_ERROR_INVALID_HANDLE;
} }
if (port->audio_device == 0 || if (port->stream == nullptr || !RecordingDevicePresent(port->device)) {
SDL_GetAudioDeviceStatus(port->audio_device) == SDL_AUDIO_STOPPED) {
return AUDIO_IN_SILENT_STATE_DEVICE_NONE; return AUDIO_IN_SILENT_STATE_DEVICE_NONE;
} }
return 0; return 0;
@ -689,9 +684,9 @@ int Audio::AudioInInput(Id handle, void* dest) {
port->busy = true; port->busy = true;
snapshot = *port; snapshot = *port;
} }
if (snapshot.audio_device != 0 && dest != nullptr) { bool failed = snapshot.stream != nullptr && dest != nullptr &&
SDL_PauseAudioDevice(snapshot.audio_device, 0); (!RecordingDevicePresent(snapshot.device) ||
} !SDL_ResumeAudioStreamDevice(snapshot.stream));
const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq; const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq;
uint64_t wait_time = 0; uint64_t wait_time = 0;
@ -699,7 +694,7 @@ int Audio::AudioInInput(Id handle, void* dest) {
if (dest != nullptr) { if (dest != nullptr) {
wait_time = block_time; 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 now = LibKernel::KernelGetProcessTime();
const auto next = snapshot.last_input_time + block_time; const auto next = snapshot.last_input_time + block_time;
if (next > now) { if (next > now) {
@ -708,31 +703,41 @@ int Audio::AudioInInput(Id handle, void* dest) {
} }
Common::Thread::SleepMicro(wait_time); Common::Thread::SleepMicro(wait_time);
uint32_t frames = snapshot.samples_num; uint32_t frames = snapshot.samples_num;
bool failed = false; bool timed_out = false;
if (dest != nullptr) { if (dest != nullptr) {
if (snapshot.audio_device != 0) { if (snapshot.stream != nullptr && !failed) {
while (!snapshot.asynchronous && const int requested = static_cast<int>(frames * snapshot.bytes_per_frame);
SDL_GetQueuedAudioSize(snapshot.audio_device) < const auto deadline = LibKernel::KernelGetProcessTime() +
frames * snapshot.bytes_per_frame && std::max<uint64_t>(20000, block_time * 4);
SDL_GetAudioDeviceStatus(snapshot.audio_device) == SDL_AUDIO_PLAYING) { 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); Common::Thread::SleepMicro(1000);
available = SDL_GetAudioStreamAvailable(snapshot.stream);
} }
failed = SDL_GetAudioDeviceStatus(snapshot.audio_device) != SDL_AUDIO_PLAYING; failed = available < 0 || device == 0 || SDL_AudioDevicePaused(device);
if (!failed) { timed_out = !snapshot.asynchronous && available < requested;
if (!failed && !timed_out) {
if (snapshot.asynchronous) { if (snapshot.asynchronous) {
frames = AUDIO_IN_GRAIN_MAX_ASYNC; frames = AUDIO_IN_GRAIN_MAX_ASYNC;
} }
frames = SDL_DequeueAudio(snapshot.audio_device, dest, const int bytes = SDL_GetAudioStreamData(
frames * snapshot.bytes_per_frame) / snapshot.stream, dest, static_cast<int>(frames * snapshot.bytes_per_frame));
snapshot.bytes_per_frame; failed = bytes < 0;
if (!failed) {
frames = static_cast<uint32_t>(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); std::memset(dest, 0, frames * snapshot.bytes_per_frame);
} }
} else if (snapshot.audio_device != 0) { } else if (snapshot.stream != nullptr) {
SDL_PauseAudioDevice(snapshot.audio_device, 1); SDL_PauseAudioStreamDevice(snapshot.stream);
SDL_ClearQueuedAudio(snapshot.audio_device); SDL_ClearAudioStream(snapshot.stream);
} }
{ {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);

View File

@ -1,6 +1,6 @@
#include "libs/controller.h" #include "libs/controller.h"
#include "SDL.h" #include <SDL3/SDL.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
@ -293,11 +293,11 @@ void GameController::Connect(int id) {
m_connected_ids.push_back(id); m_connected_ids.push_back(id);
if (id != HOST_INPUT_CONTROLLER_ID) { if (id != HOST_INPUT_CONTROLLER_ID) {
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id)); if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
pad != nullptr) { pad != nullptr) {
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) { for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
if (SDL_GameControllerHasSensor(pad, sensor) && if (SDL_GamepadHasSensor(pad, sensor) &&
SDL_GameControllerSetSensorEnabled(pad, sensor, SDL_TRUE) != 0) { !SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
LOGF("\t enabling controller sensor failed: %s\n", SDL_GetError()); LOGF("\t enabling controller sensor failed: %s\n", SDL_GetError());
} }
} }
@ -519,22 +519,22 @@ void GameController::ResetInputState() {
void GameController::ReleaseHostPads() { void GameController::ReleaseHostPads() {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
std::vector<SDL_GameController*> pads; std::vector<SDL_Gamepad*> pads;
for (const auto id: m_connected_ids) { for (const auto id: m_connected_ids) {
if (id == HOST_INPUT_CONTROLLER_ID) { if (id == HOST_INPUT_CONTROLLER_ID) {
continue; continue;
} }
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id)); if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
pad != nullptr) { pad != nullptr) {
if (SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) { if (SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
DualSenseEffects effect {}; DualSenseEffects effect {};
effect.enable_bits = 0x0c; effect.enable_bits = 0x0c;
effect.right_trigger[0] = 0x05; effect.right_trigger[0] = 0x05;
effect.left_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_RumbleGamepad(pad, 0, 0, 0);
(void)SDL_GameControllerSetLED(pad, 0, 0, 0); (void)SDL_SetGamepadLED(pad, 0, 0, 0);
pads.push_back(pad); pads.push_back(pad);
} }
} }
@ -542,7 +542,7 @@ void GameController::ReleaseHostPads() {
if (!pads.empty()) { if (!pads.empty()) {
SDL_Delay(RELEASE_FLUSH_MS); SDL_Delay(RELEASE_FLUSH_MS);
for (auto* pad: pads) { 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; return;
} }
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id)); auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
if (pad == nullptr) { if (pad == nullptr) {
return; return;
} }
const auto large = static_cast<uint16_t>(large_motor * 0x101U); const auto large = static_cast<uint16_t>(large_motor * 0x101U);
const auto small = static_cast<uint16_t>(small_motor * 0x101U); const auto small = static_cast<uint16_t>(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()); LOGF("\t rumble failed: %s\n", SDL_GetError());
} }
} }
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) { void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id)); if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
pad != nullptr) { 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); Common::LockGuard lock(m_mutex);
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id)); auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
if (pad != nullptr && SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) { if (pad != nullptr && SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect)); (void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
} }
return true; return true;
} }

View File

@ -3,7 +3,7 @@
#include "libs/libs.h" #include "libs/libs.h"
#include "loader/symbolDatabase.h" #include "loader/symbolDatabase.h"
#include <SDL_stdinc.h> #include <SDL3/SDL_stdinc.h>
#include <array> #include <array>
#include <cstring> #include <cstring>

View File

@ -1,4 +1,6 @@
#include "SDL.h" #include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
#include "common/file.h" #include "common/file.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -478,11 +480,11 @@ int main() {
Config::Load(options); Config::Load(options);
subsystems.Initialize<Log::Lifecycle>(); subsystems.Initialize<Log::Lifecycle>();
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<Libs::Graphics::WindowContext>(); auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
graphics->graphic_ctx.screen_width = 64; graphics->graphic_ctx.screen_width = 64;
graphics->graphic_ctx.screen_height = 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); SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
Check(graphics->window != nullptr, "create hidden Vulkan test window"); Check(graphics->window != nullptr, "create hidden Vulkan test window");
graphics->CreateVulkan(); graphics->CreateVulkan();