WIP: [common] port to SDL3 #2645

Draft
octocar wants to merge 16 commits from octocar/eden:sdl3 into master
41 changed files with 734 additions and 737 deletions

View file

@ -97,8 +97,8 @@ cmake .. -G Ninja \
-DCMAKE_CXX_FLAGS="$ARCH_FLAGS" \ -DCMAKE_CXX_FLAGS="$ARCH_FLAGS" \
-DCMAKE_C_FLAGS="$ARCH_FLAGS" \ -DCMAKE_C_FLAGS="$ARCH_FLAGS" \
-DYUZU_USE_BUNDLED_QT=OFF \ -DYUZU_USE_BUNDLED_QT=OFF \
-DYUZU_USE_BUNDLED_SDL2=OFF \ -DYUZU_USE_BUNDLED_SDL3=OFF \
-DYUZU_USE_EXTERNAL_SDL2=ON \ -DYUZU_USE_EXTERNAL_SDL3=ON \
-DYUZU_TESTS=OFF \ -DYUZU_TESTS=OFF \
-DYUZU_USE_QT_MULTIMEDIA=$MULTIMEDIA \ -DYUZU_USE_QT_MULTIMEDIA=$MULTIMEDIA \
-DYUZU_USE_QT_WEB_ENGINE=$WEBENGINE \ -DYUZU_USE_QT_WEB_ENGINE=$WEBENGINE \

View file

@ -24,7 +24,7 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE:-Release}" \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE:-Release}" \
-DENABLE_QT_TRANSLATION=ON \ -DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \ -DUSE_DISCORD_PRESENCE=ON \
-DYUZU_USE_BUNDLED_SDL2=ON \ -DYUZU_USE_BUNDLED_SDL3=ON \
-DBUILD_TESTING=OFF \ -DBUILD_TESTING=OFF \
-DYUZU_TESTS=OFF \ -DYUZU_TESTS=OFF \
-DDYNARMIC_TESTS=OFF \ -DDYNARMIC_TESTS=OFF \

View file

@ -147,14 +147,14 @@ if (PLATFORM_FREEBSD)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
endif() endif()
# Set bundled sdl2/qt as dependent options. # Set bundled sdl3/qt as dependent options.
# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion # On Linux system SDL3 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
cmake_dependent_option(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" OFF) cmake_dependent_option(ENABLE_SDL3 "Enable the SDL3 frontend" ON "NOT ANDROID" OFF)
if (ENABLE_SDL2) if (ENABLE_SDL3)
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system # TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF) cmake_dependent_option(YUZU_USE_EXTERNAL_SDL3 "Compile external SDL3" OFF "NOT MSVC" OFF)
option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}") option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" "${MSVC}")
endif() endif()
# qt stuff # qt stuff
@ -232,7 +232,7 @@ option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF) cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF) cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL2;NOT ANDROID" OFF) cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL3;NOT ANDROID" OFF)
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
@ -384,9 +384,6 @@ if (PLATFORM_LINUX OR CXX_CLANG)
endif() endif()
endif() endif()
# Other presets, e.g. steamdeck
set(YUZU_SYSTEM_PROFILE "generic" CACHE STRING "CMake and Externals profile to use. One of: generic, steamdeck")
# Configure C++ standard # Configure C++ standard
# =========================== # ===========================
@ -578,8 +575,8 @@ if (ARCHITECTURE_arm64 OR DYNARMIC_TESTS)
find_package(oaknut) find_package(oaknut)
endif() endif()
if (ENABLE_SDL2) if (ENABLE_SDL3)
find_package(SDL2) find_package(SDL3)
endif() endif()
if (USE_DISCORD_PRESENCE) if (USE_DISCORD_PRESENCE)

View file

@ -1,8 +1,10 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2016 Citra Emulator Project # SPDX-FileCopyrightText: 2016 Citra Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
function(copy_yuzu_SDL_deps target_dir) function(copy_yuzu_SDL_deps target_dir)
include(WindowsCopyFiles) include(WindowsCopyFiles)
set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/") set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll) windows_copy_files(${target_dir} ${SDL3_DLL_DIR} ${DLL_DEST} SDL3.dll)
endfunction(copy_yuzu_SDL_deps) endfunction(copy_yuzu_SDL_deps)

View file

@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project # SPDX-FileCopyrightText: 2022 yuzu Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
@ -6,7 +8,7 @@ set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX}) set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX})
set(SDL2_PATH ${MINGW_PREFIX}) set(SDL3_PATH ${MINGW_PREFIX})
set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-) set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-)
# Specify the cross compiler # Specify the cross compiler

View file

@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 tech4me <guiwanglong@gmail.com> # SPDX-FileCopyrightText: 2018 tech4me <guiwanglong@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
@ -9,7 +11,7 @@ set(CMAKE_HOST_WIN32 TRUE)
set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX}) set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX})
set(SDL2_PATH ${MINGW_PREFIX}) set(SDL3_PATH ${MINGW_PREFIX})
set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-) set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-)
# Specify the cross compiler # Specify the cross compiler

View file

@ -87,6 +87,18 @@
"hash": "d1dece16f3b209109de02123c537bfe1adf07a62b16c166367e7e5d62e0f7c323bf804c89b3192dd6871bc58a9d879d25a1cc3f7b9da0e497cf266f165816e2a", "hash": "d1dece16f3b209109de02123c537bfe1adf07a62b16c166367e7e5d62e0f7c323bf804c89b3192dd6871bc58a9d879d25a1cc3f7b9da0e497cf266f165816e2a",
"bundled": true "bundled": true
}, },
"discord-rpc": {
"repo": "eden-emulator/discord-rpc",
"sha": "1cf7772bb6",
"hash": "e9b35e6f2c075823257bcd59f06fe7bb2ccce1976f44818d2e28810435ef79c712a3c4f20f40da41f691342a4058cf86b078eb7f9d9e4dae83c0547c21ec4f97"
},
"simpleini": {
"package": "SimpleIni",
"repo": "brofield/simpleini",
"sha": "09c21bda1d",
"hash": "99779ca9b6e040d36558cadf484f9ffdab5b47bcc8fc72e4d33639d1d60c0ceb4410d335ba445d72a4324e455167fd6769d99b459943aa135bec085dff2d4b7c",
"find_args": "MODULE"
},
"llvm-mingw": { "llvm-mingw": {
"repo": "misc/llvm-mingw", "repo": "misc/llvm-mingw",
"git_host": "git.crueter.xyz", "git_host": "git.crueter.xyz",

View file

@ -38,7 +38,7 @@ export LIBGL_ALWAYS_SOFTWARE=1
``` ```
- Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`). - Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`).
- If using OpenIndiana, due to a bug in SDL2's CMake configuration, audio driver defaults to SunOS `<sys/audioio.h>`, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this. - If using OpenIndiana, due to a bug in SDL3's CMake configuration, audio driver defaults to SunOS `<sys/audioio.h>`, which does not exist on OpenIndiana. Using external or bundled SDL3 may solve this.
- System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source. - System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source.
## OpenBSD ## OpenBSD

View file

@ -32,7 +32,7 @@ If you are on Windows and NOT building with MSYS2, you may go [back home](Build.
The following are handled by Eden's externals: The following are handled by Eden's externals:
* [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`) * [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`)
* [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ (should use `-DYUZU_USE_EXTERNAL_SDL2=ON` OR `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time) * [SDL3](https://github.com/libsdl-org/SDL/releases) 3.2.12+ (should use `-DYUZU_USE_EXTERNAL_SDL3=ON` OR `-DYUZU_USE_BUNDLED_SDL3=ON` to reduce compile time)
octocar marked this conversation as resolved Outdated

change to 3.2.12+

change to 3.2.12+
All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available (UNIX-like only): All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available (UNIX-like only):
@ -90,7 +90,7 @@ Click on the arrows to expand.
<summary>Arch Linux</summary> <summary>Arch Linux</summary>
```sh ```sh
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl2 zlib zstd zip unzip zydis zycore vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl3 zlib zstd zip unzip zydis zycore vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
``` ```
* Building with QT Web Engine requires `qt6-webengine` as well. * Building with QT Web Engine requires `qt6-webengine` as well.
@ -117,7 +117,7 @@ sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel
``` ```
* Force system libraries via CMake arguments: * Force system libraries via CMake arguments:
* SDL2: `-DYUZU_USE_BUNDLED_SDL2=OFF -DYUZU_USE_EXTERNAL_SDL2=OFF` * SDL3: `-DYUZU_USE_BUNDLED_SDL3=OFF -DYUZU_USE_EXTERNAL_SDL3=OFF`
* FFmpeg: `-DYUZU_USE_EXTERNAL_FFMPEG=OFF` * FFmpeg: `-DYUZU_USE_EXTERNAL_FFMPEG=OFF`
* [RPM Fusion](https://rpmfusion.org/) is required for `ffmpeg-devel` * [RPM Fusion](https://rpmfusion.org/) is required for `ffmpeg-devel`
* Fedora 32 or later is required. * Fedora 32 or later is required.
@ -130,7 +130,7 @@ sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel
Install dependencies from **[Homebrew](https://brew.sh/)** Install dependencies from **[Homebrew](https://brew.sh/)**
```sh ```sh
brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl3 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools
``` ```
If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`. If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`.
@ -147,7 +147,7 @@ brew install molten-vk vulkan-loader
``` ```
devel/cmake devel/cmake
devel/sdl20 devel/sdl30
devel/boost-libs devel/boost-libs
devel/catch2 devel/catch2
devel/libfmt devel/libfmt
@ -181,7 +181,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
```sh ```sh
pkg_add -u pkg_add -u
pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake llvm-19.1.7p3 qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1.1.0.27 pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake llvm-19.1.7p3 qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl3 libusb1.1.0.27
``` ```
</details> </details>
@ -195,7 +195,7 @@ Run the usual update + install of essential toolings: `sudo pkg update && sudo p
- **gcc**: `sudo pkg install developer/gcc-14`. - **gcc**: `sudo pkg install developer/gcc-14`.
- **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`. - **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`.
Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt`. Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt`.
</details> </details>
<details> <details>
@ -203,7 +203,7 @@ Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/l
* Open the `MSYS2 MinGW 64-bit` shell (`mingw64.exe`) * Open the `MSYS2 MinGW 64-bit` shell (`mingw64.exe`)
* Download and install all dependencies using: * Download and install all dependencies using:
octocar marked this conversation as resolved Outdated

sdl is now lower case here

sdl is now lower case here
* `pacman -Syu git make mingw-w64-x86_64-SDL2 mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-qt6 mingw-w64-x86_64-toolchain autoconf libtool automake-wrapper` * `pacman -Syu git make mingw-w64-x86_64-sdl3 mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-qt6 mingw-w64-x86_64-toolchain autoconf libtool automake-wrapper`
* Add MinGW binaries to the PATH: * Add MinGW binaries to the PATH:
* `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc` * `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc`
* Add VulkanSDK to the PATH: * Add VulkanSDK to the PATH:

View file

@ -40,12 +40,12 @@ Notes:
* Unavailable on OpenBSD * Unavailable on OpenBSD
The following options are desktop only: The following options are desktop only:
- `ENABLE_SDL2` (ON) Enable the SDL2 desktop, audio, and input frontend (HIGHLY RECOMMENDED!) - `ENABLE_SDL3` (ON) Enable the SDL3 desktop, audio, and input frontend (HIGHLY RECOMMENDED!)
* Unavailable on Android * Unavailable on Android
- `YUZU_USE_EXTERNAL_SDL2` (ON for non-UNIX) Compiles SDL2 from source - `YUZU_USE_EXTERNAL_SDL3` (ON for non-UNIX) Compiles SDL3 from source
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2 - `YUZU_USE_BUNDLED_SDL3` (ON for MSVC) Download a prebuilt SDL3
* Unavailable on OpenBSD * Unavailable on OpenBSD
* Only enabled if YUZU_USE_CPM and ENABLE_SDL2 are both ON * Only enabled if YUZU_USE_CPM and ENABLE_SDL3 are both ON
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED) - `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED)
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend - `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend
* Unavailable on Windows/ARM64 and Android * Unavailable on Windows/ARM64 and Android
@ -62,7 +62,7 @@ The following options are desktop only:
- `YUZU_ROOM` (ON) Enable dedicated room functionality - `YUZU_ROOM` (ON) Enable dedicated room functionality
- `YUZU_ROOM_STANDALONE` (ON) Enable standalone room executable (eden-room) - `YUZU_ROOM_STANDALONE` (ON) Enable standalone room executable (eden-room)
* Requires `YUZU_ROOM` * Requires `YUZU_ROOM`
- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli) - requires SDL2 - `YUZU_CMD` (ON) Compile the SDL3 frontend (eden-cli) - requires SDL3
- `YUZU_CRASH_DUMPS` Compile crash dump (Minidump) support" - `YUZU_CRASH_DUMPS` Compile crash dump (Minidump) support"
* Currently only available on Windows and Linux * Currently only available on Windows and Linux

View file

@ -109,10 +109,10 @@ if(ENABLE_CUBEB)
endif() endif()
endif() endif()
# find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the YUZU_find_package # find SDL3 exports a bunch of variables that are needed, so its easier to do this outside of the YUZU_find_package
if (ENABLE_SDL2) if (ENABLE_SDL3)
if (YUZU_USE_EXTERNAL_SDL2) if (YUZU_USE_EXTERNAL_SDL3)
message(STATUS "Using SDL2 from externals.") message(STATUS "Using SDL3 from externals.")
if (NOT WIN32) if (NOT WIN32)
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
@ -133,18 +133,16 @@ if (ENABLE_SDL2)
set(SDL_FILE ON) set(SDL_FILE ON)
endif() endif()
if ("${YUZU_SYSTEM_PROFILE}" STREQUAL "steamdeck") AddJsonPackage(sdl3)
set(SDL_PIPEWIRE OFF) # build errors out with this on
AddJsonPackage("sdl2_steamdeck") # annoying
else() target_include_directories(SDL3_Headers INTERFACE ${SDL3_SOURCE_DIR}/include/SDL3)
AddJsonPackage("sdl2_generic") elseif (YUZU_USE_BUNDLED_SDL3)
endif() message(STATUS "Using bundled SDL3")
elseif (YUZU_USE_BUNDLED_SDL2) AddJsonPackage(sdl3-ci)
message(STATUS "Using bundled SDL2")
AddJsonPackage(sdl2)
endif() endif()
find_package(SDL2 2.26.4 REQUIRED) find_package(SDL3 3.2.12 REQUIRED)
endif() endif()
# SPIRV Headers # SPIRV Headers

View file

@ -146,13 +146,21 @@
"BUNDLE_SPEEX ON" "BUNDLE_SPEEX ON"
] ]
}, },
"sdl2": { "sdl3": {
"package": "SDL3",
"repo": "libsdl-org/SDL",
"tag": "release-%VERSION%",
"hash": "93766ed1f2be0af75e82c05fcb1dc0aac29ded4d0ae9a98137edfc6a4ab85412ea51199d0469254e7e5751fb37d78daff8bc0cbbc20650972f182d976c6bcc61",
"git_version": "3.2.24",
"bundled": true
},
"sdl3-ci": {
"ci": true, "ci": true,
"package": "SDL2", "package": "SDL3",
"name": "SDL2", "name": "SDL3",
"repo": "crueter-ci/SDL2", "repo": "crueter-ci/SDL3",
"version": "2.32.10", "version": "3.2.24",
"min_version": "2.26.4", "min_version": "3.2.12",
"disabled_platforms": [ "disabled_platforms": [
"macos-universal" "macos-universal"
] ]
@ -179,24 +187,5 @@
"hash": "6c198636816a0018adbf7f735d402c64245c6fcd540b7360d4388d46f007f3a520686cdaec4705cb8cb31401b2cb4797a80b42ea5d08a6a5807c0848386f7ca1", "hash": "6c198636816a0018adbf7f735d402c64245c6fcd540b7360d4388d46f007f3a520686cdaec4705cb8cb31401b2cb4797a80b42ea5d08a6a5807c0848386f7ca1",
"find_args": "MODULE", "find_args": "MODULE",
"git_version": "4.22" "git_version": "4.22"
},
"sdl2_generic": {
"package": "SDL2",
"repo": "libsdl-org/SDL",
"tag": "release-%VERSION%",
"hash": "d5622d6bb7266f7942a7b8ad43e8a22524893bf0c2ea1af91204838d9b78d32768843f6faa248757427b8404b8c6443776d4afa6b672cd8571a4e0c03a829383",
"key": "generic",
"bundled": true,
"git_version": "2.32.10",
"skip_updates": true
},
"sdl2_steamdeck": {
"package": "SDL2",
"repo": "libsdl-org/SDL",
"sha": "cc016b0046",
"hash": "34d5ef58da6a4f9efa6689c82f67badcbd741f5a4f562a9c2c30828fa839830fb07681c5dc6a7851520e261c8405a416ac0a2c2513b51984fb3b4fa4dcb3e20b",
"key": "steamdeck",
"bundled": true,
"skip_updates": "true"
} }
} }

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* /*
Copyright (c) 2017-2019, Feral Interactive Copyright (c) 2017-2019, Feral Interactive
@ -89,7 +91,7 @@ static char internal_gamemode_client_error_string[512] = { 0 };
/** /**
* Load libgamemode dynamically to dislodge us from most dependencies. * Load libgamemode dynamically to dislodge us from most dependencies.
* This allows clients to link and/or use this regardless of runtime. * This allows clients to link and/or use this regardless of runtime.
* See SDL2 for an example of the reasoning behind this in terms of * See SDL3 for an example of the reasoning behind this in terms of
* dynamic versioning as well. * dynamic versioning as well.
*/ */
static volatile int internal_libgamemode_loaded = 1; static volatile int internal_libgamemode_loaded = 1;

View file

@ -223,7 +223,7 @@ if (YUZU_TESTS)
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()
if (ENABLE_SDL2 AND YUZU_CMD) if (ENABLE_SDL3 AND YUZU_CMD)
add_subdirectory(yuzu_cmd) add_subdirectory(yuzu_cmd)
set_target_properties(yuzu-cmd PROPERTIES OUTPUT_NAME "eden-cli") set_target_properties(yuzu-cmd PROPERTIES OUTPUT_NAME "eden-cli")
endif() endif()

View file

@ -77,7 +77,7 @@ android {
cmake { cmake {
arguments.addAll(listOf( arguments.addAll(listOf(
"-DENABLE_QT=0", // Don't use QT "-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL "-DENABLE_SDL3=0", // Don't use SDL
"-DENABLE_WEB_SERVICE=1", // Enable web service "-DENABLE_WEB_SERVICE=1", // Enable web service
"-DENABLE_OPENSSL=ON", "-DENABLE_OPENSSL=ON",
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work

View file

@ -247,14 +247,18 @@ if (ENABLE_CUBEB)
target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1) target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1)
endif() endif()
if (ENABLE_SDL2) if (ENABLE_SDL3)
target_sources(audio_core PRIVATE target_sources(audio_core PRIVATE
sink/sdl2_sink.cpp sink/sdl3_sink.cpp
sink/sdl2_sink.h sink/sdl3_sink.h
octocar marked this conversation as resolved Outdated

Update filenames to sdl3

Update filenames to sdl3
) )
target_link_libraries(audio_core PRIVATE SDL2::SDL2) target_link_libraries(audio_core PRIVATE SDL3::SDL3)
target_compile_definitions(audio_core PRIVATE HAVE_SDL2) if (TARGET SDL3::Headers)
target_link_libraries(audio_core PRIVATE SDL3::Headers)
endif()
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
endif() endif()
if(ANDROID) if(ANDROID)

View file

@ -4,16 +4,16 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <span> #include <span>
#include <vector> #include <vector>
#include <SDL.h> #include <SDL3/SDL.h>
crueter marked this conversation as resolved Outdated

Linking to SDL3::SDL3 should set the include directories to include the /path/to/include/SDL3 so SDL.h should still work.

Linking to SDL3::SDL3 *should* set the include directories to include the `/path/to/include/SDL3` so SDL.h should still work.

SDL.h does not seem to work for me on external dependency

SDL.h does not seem to work for me on external dependency
#include "audio_core/common/common.h" #include "audio_core/common/common.h"
#include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sdl3_sink.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
namespace AudioCore::Sink { namespace AudioCore::Sink {
@ -39,37 +39,53 @@ public:
system_channels = system_channels_; system_channels = system_channels_;
device_channels = device_channels_; device_channels = device_channels_;
SDL_AudioSpec spec; SDL_AudioSpec spec{};
spec.freq = TargetSampleRate; spec.freq = TargetSampleRate;
spec.channels = static_cast<u8>(device_channels); spec.channels = static_cast<int>(device_channels);
spec.format = AUDIO_S16SYS; spec.format = SDL_AUDIO_S16;
spec.samples = TargetSampleCount * 2;
spec.callback = &SDLSinkStream::DataCallback;
spec.userdata = this;
SDL_AudioDeviceID device_id = 0;
std::string device_name{output_device}; std::string device_name{output_device};
bool capture{false}; bool is_capture{false};
if (type == StreamType::In) { if (type == StreamType::In) {
device_name = input_device; device_name = input_device;
capture = true; is_capture = true;
} }
SDL_AudioSpec obtained; if (!device_name.empty()) {
if (device_name.empty()) { int count = 0;
device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); SDL_AudioDeviceID* devices = is_capture ? SDL_GetAudioRecordingDevices(&count)
} else { : SDL_GetAudioPlaybackDevices(&count);
device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false);
if (devices) {
for (int i = 0; i < count; ++i) {
const char* devname = SDL_GetAudioDeviceName(devices[i]);
if (devname && device_name == devname) {
device_id = devices[i];
break;
}
}
SDL_free(devices);
}
} }
if (device == 0) { if (device_id == 0) {
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); device_id =
is_capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
}
stream = SDL_OpenAudioDeviceStream(
device_id, &spec,
is_capture ? &SDLSinkStream::CaptureCallback : &SDLSinkStream::PlaybackCallback, this);
if (!stream) {
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio stream: {}", SDL_GetError());
return; return;
} }
LOG_INFO(Service_Audio, LOG_INFO(Service_Audio, "Opening SDL stream with: rate {} channels {} (system channels {})",
"Opening SDL stream {} with: rate {} channels {} (system channels {}) " spec.freq, spec.channels, system_channels);
" samples {}",
device, obtained.freq, obtained.channels, system_channels, obtained.samples);
} }
/** /**
@ -84,13 +100,14 @@ public:
* Finalize the sink stream. * Finalize the sink stream.
*/ */
void Finalize() override { void Finalize() override {
if (device == 0) { if (!stream) {
return; return;
} }
Stop(); Stop();
SDL_ClearQueuedAudio(device); SDL_ClearAudioStream(stream);
SDL_CloseAudioDevice(device); SDL_DestroyAudioStream(stream);
stream = nullptr;
} }
/** /**
@ -100,62 +117,80 @@ public:
* Default false. * Default false.
*/ */
void Start(bool resume = false) override { void Start(bool resume = false) override {
if (device == 0 || !paused) { if (!stream || !paused) {
return; return;
} }
paused = false; paused = false;
SDL_PauseAudioDevice(device, 0); SDL_ResumeAudioStreamDevice(stream);
} }
/** /**
* Stop the sink stream. * Stop the sink stream.
*/ */
void Stop() override { void Stop() override {
if (device == 0 || paused) { if (!stream || paused) {
return; return;
} }
SignalPause(); SignalPause();
SDL_PauseAudioDevice(device, 1); SDL_PauseAudioStreamDevice(stream);
paused = true;
} }
private: private:
/** static void PlaybackCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
* Main callback from SDL. Either expects samples from us (audio render/audio out), or will int total_amount) {
* provide samples to be copied (audio in).
*
* @param userdata - Custom data pointer passed along, points to a SDLSinkStream.
* @param stream - Buffer of samples to be filled or read.
* @param len - Length of the stream in bytes.
*/
static void DataCallback(void* userdata, Uint8* stream, int len) {
auto* impl = static_cast<SDLSinkStream*>(userdata); auto* impl = static_cast<SDLSinkStream*>(userdata);
if (!impl) { if (!impl) {
return; return;
} }
const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels; const std::size_t frame_size = num_channels;
const std::size_t num_frames{len / num_channels / sizeof(s16)}; const std::size_t num_frames = additional_amount / (sizeof(s16) * frame_size);
if (impl->type == StreamType::In) { if (num_frames == 0) {
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream), return;
num_frames * frame_size}; }
impl->ProcessAudioIn(input_buffer, num_frames);
} else { std::vector<s16> buffer(num_frames * frame_size);
std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; impl->ProcessAudioOutAndRender(buffer, num_frames);
impl->ProcessAudioOutAndRender(output_buffer, num_frames); SDL_PutAudioStreamData(stream, buffer.data(),
static_cast<int>(buffer.size() * sizeof(s16)));
}
static void CaptureCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
int total_amount) {
auto* impl = static_cast<SDLSinkStream*>(userdata);
if (!impl) {
return;
}
const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t bytes_available = SDL_GetAudioStreamAvailable(stream);
if (bytes_available == 0) {
return;
}
const std::size_t num_frames = bytes_available / (sizeof(s16) * frame_size);
std::vector<s16> buffer(num_frames * frame_size);
int bytes_read =
SDL_GetAudioStreamData(stream, buffer.data(), static_cast<int>(bytes_available));
if (bytes_read > 0) {
const std::size_t frames_read = bytes_read / (sizeof(s16) * frame_size);
impl->ProcessAudioIn(buffer, frames_read);
} }
} }
/// SDL device id of the opened input/output device SDL_AudioStream* stream{nullptr};
SDL_AudioDeviceID device{};
}; };
SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::SDLSink(std::string_view target_device_name) {
if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return; return;
} }
@ -218,66 +253,31 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
std::vector<std::string> device_list; std::vector<std::string> device_list;
if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return {}; return {};
} }
} }
const int device_count = SDL_GetNumAudioDevices(capture); int count = 0;
for (int i = 0; i < device_count; ++i) { SDL_AudioDeviceID* devices =
if (const char* name = SDL_GetAudioDeviceName(i, capture)) { capture ? SDL_GetAudioRecordingDevices(&count) : SDL_GetAudioPlaybackDevices(&count);
device_list.emplace_back(name);
if (devices) {
for (int i = 0; i < count; ++i) {
const char* name = SDL_GetAudioDeviceName(devices[i]);
if (name) {
device_list.emplace_back(name);
}
} }
SDL_free(devices);
} }
return device_list; return device_list;
} }
/* REVERSION to 3833 - function GetSDLLatency() REINTRODUCED FROM 3833 - DIABLO 3 FIX */
u32 GetSDLLatency() { u32 GetSDLLatency() {
return TargetSampleCount * 2; return TargetSampleCount * 2;
} }
// REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX
/*
bool IsSDLSuitable() {
#if !defined(HAVE_SDL2)
return false;
#else
// Check SDL can init
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
}
// We can set any latency frequency we want with SDL, so no need to check that.
// Check we can open a device with standard parameters
SDL_AudioSpec spec;
spec.freq = TargetSampleRate;
spec.channels = 2u;
spec.format = AUDIO_S16SYS;
spec.samples = TargetSampleCount * 2;
spec.callback = nullptr;
spec.userdata = nullptr;
SDL_AudioSpec obtained;
auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
if (device == 0) {
LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
SDL_CloseAudioDevice(device);
return true;
#endif
}
*/
} // namespace AudioCore::Sink } // namespace AudioCore::Sink

View file

@ -16,8 +16,8 @@
#ifdef HAVE_CUBEB #ifdef HAVE_CUBEB
#include "audio_core/sink/cubeb_sink.h" #include "audio_core/sink/cubeb_sink.h"
#endif #endif
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
#include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sdl3_sink.h"
#endif #endif
#include "audio_core/sink/null_sink.h" #include "audio_core/sink/null_sink.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -71,9 +71,9 @@ constexpr SinkDetails sink_details[] = {
&GetCubebLatency, &GetCubebLatency,
}, },
#endif #endif
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
SinkDetails{ SinkDetails{
Settings::AudioEngine::Sdl2, Settings::AudioEngine::Sdl3,
[](std::string_view device_id) -> std::unique_ptr<Sink> { [](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<SDLSink>(device_id); return std::make_unique<SDLSink>(device_id);
}, },
@ -115,10 +115,10 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
// BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX // BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
// causes audio issues, in that case go with SDL. // causes audio issues, in that case go with SDL.
#if defined(HAVE_CUBEB) && defined(HAVE_SDL2) #if defined(HAVE_CUBEB) && defined(HAVE_SDL3)
iter = find_backend(Settings::AudioEngine::Cubeb); iter = find_backend(Settings::AudioEngine::Cubeb);
if (iter->latency() > TargetSampleCount * 3) { if (iter->latency() > TargetSampleCount * 3) {
iter = find_backend(Settings::AudioEngine::Sdl2); iter = find_backend(Settings::AudioEngine::Sdl3);
} }
#else #else
iter = std::begin(sink_details); iter = std::begin(sink_details);

View file

@ -92,11 +92,11 @@ struct EnumMetadata {
// AudioEngine must be specified discretely due to having existing but slightly different // AudioEngine must be specified discretely due to having existing but slightly different
// canonicalizations // canonicalizations
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id // TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, }; enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, Oboe, };
template<> template<>
inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() { inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
return { return {
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2}, {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl3", AudioEngine::Sdl3},
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe}, {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
}; };
} }

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -51,7 +53,7 @@ void Config::Initialize(const std::string& config_name) {
void Config::Initialize(const std::optional<std::string> config_path) { void Config::Initialize(const std::optional<std::string> config_path) {
const std::filesystem::path default_sdl_config_path = const std::filesystem::path default_sdl_config_path =
FS::GetEdenPath(FS::EdenPath::ConfigDir) / "sdl2-config.ini"; FS::GetEdenPath(FS::EdenPath::ConfigDir) / "sdl3-config.ini";
config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path)); config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path));
void(FS::CreateParentDir(config_loc)); void(FS::CreateParentDir(config_loc));
SetUpIni(); SetUpIni();

View file

@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project # SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
@ -47,7 +49,7 @@ else()
) )
endif() endif()
if (ENABLE_SDL2) if (ENABLE_SDL3)
target_sources(input_common PRIVATE target_sources(input_common PRIVATE
drivers/joycon.cpp drivers/joycon.cpp
drivers/joycon.h drivers/joycon.h
@ -73,8 +75,8 @@ if (ENABLE_SDL2)
helpers/joycon_protocol/rumble.cpp helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h helpers/joycon_protocol/rumble.h
) )
target_link_libraries(input_common PRIVATE SDL2::SDL2) target_link_libraries(input_common PRIVATE SDL3::SDL3)
target_compile_definitions(input_common PRIVATE HAVE_SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL3)
endif() endif()
if (ENABLE_LIBUSB) if (ENABLE_LIBUSB)

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -6,7 +8,7 @@
#include <array> #include <array>
#include <span> #include <span>
#include <thread> #include <thread>
#include <SDL_hidapi.h> #include <SDL3/SDL_hidapi.h>
#include "input_common/input_engine.h" #include "input_common/input_engine.h"

View file

@ -8,55 +8,50 @@
#include "common/param_package.h" #include "common/param_package.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/thread.h" #include "common/thread.h"
#include "common/vector_math.h"
#include "input_common/drivers/sdl_driver.h" #include "input_common/drivers/sdl_driver.h"
octocar marked this conversation as resolved Outdated

If it's not used you can remove it, no need for comment

If it's not used you can remove it, no need for comment
namespace InputCommon { namespace InputCommon {
namespace { namespace {
Common::UUID GetGUID(SDL_Joystick* joystick) { Common::UUID GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); const SDL_GUID guid = SDL_GetJoystickGUID(joystick);
std::array<u8, 16> data{}; std::array<u8, 16> data{};
std::memcpy(data.data(), guid.data, sizeof(data)); std::memcpy(data.data(), guid.data, sizeof(data));
// Clear controller name crc
std::memset(data.data() + 2, 0, sizeof(u16)); std::memset(data.data() + 2, 0, sizeof(u16));
return Common::UUID{data}; return Common::UUID{data};
} }
} // Anonymous namespace } // Anonymous namespace
static int SDLEventWatcher(void* user_data, SDL_Event* event) { static bool SDLEventWatcher(void* user_data, SDL_Event* event) {
auto* const sdl_state = static_cast<SDLDriver*>(user_data); auto* const sdl_state = static_cast<SDLDriver*>(user_data);
sdl_state->HandleGameControllerEvent(*event); sdl_state->HandleGameControllerEvent(*event);
return false;
return 0;
} }
class SDLJoystick { class SDLJoystick {
public: public:
SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, SDL_Gamepad* gamepad)
SDL_GameController* game_controller) : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_CloseJoystick},
: guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, sdl_gamepad{gamepad, &SDL_CloseGamepad} {
sdl_controller{game_controller, &SDL_GameControllerClose} {
EnableMotion(); EnableMotion();
} }
void EnableMotion() { void EnableMotion() {
if (!sdl_controller) { if (!sdl_gamepad) {
return; return;
} }
SDL_GameController* controller = sdl_controller.get(); SDL_Gamepad* gamepad = sdl_gamepad.get();
if (HasMotion()) { if (HasMotion()) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, false);
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, false);
} }
has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
if (has_accel) { if (has_accel) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, true);
} }
if (has_gyro) { if (has_gyro) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, true);
} }
} }
@ -64,7 +59,7 @@ public:
return has_gyro || has_accel; return has_gyro || has_accel;
} }
bool UpdateMotion(SDL_ControllerSensorEvent event) { bool UpdateMotion(SDL_GamepadSensorEvent event) {
constexpr float gravity_constant = 9.80665f; constexpr float gravity_constant = 9.80665f;
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
const u64 time_difference = event.timestamp - last_motion_update; const u64 time_difference = event.timestamp - last_motion_update;
@ -84,25 +79,22 @@ public:
} }
} }
// Ignore duplicated timestamps
if (time_difference == 0) { if (time_difference == 0) {
return false; return false;
} }
// Motion data is invalid
if (motion.accel_x == 0 && motion.gyro_x == 0 && motion.accel_y == 0 && if (motion.accel_x == 0 && motion.gyro_x == 0 && motion.accel_y == 0 &&
motion.gyro_y == 0 && motion.accel_z == 0 && motion.gyro_z == 0) { motion.gyro_y == 0 && motion.accel_z == 0 && motion.gyro_z == 0) {
if (motion_error_count++ < 200) { if (motion_error_count++ < 200) {
return false; return false;
} }
// Try restarting the sensor
motion_error_count = 0; motion_error_count = 0;
EnableMotion(); EnableMotion();
return false; return false;
} }
motion_error_count = 0; motion_error_count = 0;
motion.delta_timestamp = time_difference * 1000; motion.delta_timestamp = time_difference;
return true; return true;
} }
@ -116,13 +108,13 @@ public:
constexpr f32 low_width_sensitivity_limit = 400.0f; constexpr f32 low_width_sensitivity_limit = 400.0f;
constexpr f32 high_start_sensitivity_limit = 200.0f; constexpr f32 high_start_sensitivity_limit = 200.0f;
constexpr f32 high_width_sensitivity_limit = 700.0f; constexpr f32 high_width_sensitivity_limit = 700.0f;
// Try to provide some feeling of the frequency by reducing the amplitude depending on it.
f32 low_frequency_scale = 1.0f; f32 low_frequency_scale = 1.0f;
if (vibration.low_frequency > low_start_sensitivity_limit) { if (vibration.low_frequency > low_start_sensitivity_limit) {
low_frequency_scale = low_frequency_scale =
(std::max)(1.0f - (vibration.low_frequency - low_start_sensitivity_limit) / (std::max)(1.0f - (vibration.low_frequency - low_start_sensitivity_limit) /
low_width_sensitivity_limit, low_width_sensitivity_limit,
0.3f); 0.3f);
} }
f32 low_amplitude = vibration.low_amplitude * low_frequency_scale; f32 low_amplitude = vibration.low_amplitude * low_frequency_scale;
@ -130,31 +122,29 @@ public:
if (vibration.high_frequency > high_start_sensitivity_limit) { if (vibration.high_frequency > high_start_sensitivity_limit) {
high_frequency_scale = high_frequency_scale =
(std::max)(1.0f - (vibration.high_frequency - high_start_sensitivity_limit) / (std::max)(1.0f - (vibration.high_frequency - high_start_sensitivity_limit) /
high_width_sensitivity_limit, high_width_sensitivity_limit,
0.3f); 0.3f);
} }
f32 high_amplitude = vibration.high_amplitude * high_frequency_scale; f32 high_amplitude = vibration.high_amplitude * high_frequency_scale;
if (sdl_controller) { if (sdl_gamepad) {
return SDL_GameControllerRumble(sdl_controller.get(), static_cast<u16>(low_amplitude), return SDL_RumbleGamepad(sdl_gamepad.get(), static_cast<u16>(low_amplitude),
static_cast<u16>(high_amplitude), static_cast<u16>(high_amplitude), rumble_max_duration_ms);
rumble_max_duration_ms) != -1;
} else if (sdl_joystick) { } else if (sdl_joystick) {
return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(low_amplitude), return SDL_RumbleJoystick(sdl_joystick.get(), static_cast<u16>(low_amplitude),
static_cast<u16>(high_amplitude), static_cast<u16>(high_amplitude), rumble_max_duration_ms);
rumble_max_duration_ms) != -1;
} }
return false; return false;
} }
bool HasHDRumble() const { bool HasHDRumble() const {
if (sdl_controller) { if (sdl_gamepad) {
const auto type = SDL_GameControllerGetType(sdl_controller.get()); const auto type = SDL_GetGamepadType(sdl_gamepad.get());
return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) || return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) ||
(type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) ||
(type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) ||
(type == SDL_CONTROLLER_TYPE_PS5); (type == SDL_GAMEPAD_TYPE_PS5);
} }
return false; return false;
} }
@ -172,9 +162,6 @@ public:
return is_vibration_tested; return is_vibration_tested;
} }
/**
* The Pad identifier of the joystick
*/
const PadIdentifier GetPadIdentifier() const { const PadIdentifier GetPadIdentifier() const {
return { return {
.guid = guid, .guid = guid,
@ -183,16 +170,10 @@ public:
}; };
} }
/**
* The guid of the joystick
*/
const Common::UUID& GetGUID() const { const Common::UUID& GetGUID() const {
return guid; return guid;
} }
/**
* The number of joystick from the same type that were connected before this joystick
*/
int GetPort() const { int GetPort() const {
return port; return port;
} }
@ -201,13 +182,13 @@ public:
return sdl_joystick.get(); return sdl_joystick.get();
} }
SDL_GameController* GetSDLGameController() const { SDL_Gamepad* GetSDLGamepad() const {
return sdl_controller.get(); return sdl_gamepad.get();
} }
void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { void SetSDLJoystick(SDL_Joystick* joystick, SDL_Gamepad* gamepad) {
sdl_joystick.reset(joystick); sdl_joystick.reset(joystick);
sdl_controller.reset(controller); sdl_gamepad.reset(gamepad);
} }
bool IsJoyconLeft() const { bool IsJoyconLeft() const {
@ -232,49 +213,48 @@ public:
return false; return false;
} }
Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) { Common::Input::BatteryLevel GetBatteryLevel(SDL_PowerState battery_level) {
switch (battery_level) { switch (battery_level) {
case SDL_JOYSTICK_POWER_EMPTY: case SDL_POWERSTATE_ERROR:
return Common::Input::BatteryLevel::Empty; case SDL_POWERSTATE_UNKNOWN:
case SDL_JOYSTICK_POWER_LOW: return Common::Input::BatteryLevel::None;
case SDL_POWERSTATE_ON_BATTERY:
return Common::Input::BatteryLevel::Low; return Common::Input::BatteryLevel::Low;
case SDL_JOYSTICK_POWER_MEDIUM: case SDL_POWERSTATE_NO_BATTERY:
return Common::Input::BatteryLevel::Medium; return Common::Input::BatteryLevel::None;
case SDL_JOYSTICK_POWER_FULL: case SDL_POWERSTATE_CHARGING:
case SDL_JOYSTICK_POWER_MAX:
return Common::Input::BatteryLevel::Full;
case SDL_JOYSTICK_POWER_WIRED:
return Common::Input::BatteryLevel::Charging; return Common::Input::BatteryLevel::Charging;
case SDL_JOYSTICK_POWER_UNKNOWN: case SDL_POWERSTATE_CHARGED:
return Common::Input::BatteryLevel::Full;
default: default:
return Common::Input::BatteryLevel::None; return Common::Input::BatteryLevel::None;
} }
} }
std::string GetControllerName() const { std::string GetControllerName() const {
if (sdl_controller) { if (sdl_gamepad) {
switch (SDL_GameControllerGetType(sdl_controller.get())) { switch (SDL_GetGamepadType(sdl_gamepad.get())) {
case SDL_CONTROLLER_TYPE_XBOX360: case SDL_GAMEPAD_TYPE_XBOX360:
return "Xbox 360 Controller"; return "Xbox 360 Controller";
case SDL_CONTROLLER_TYPE_XBOXONE: case SDL_GAMEPAD_TYPE_XBOXONE:
return "Xbox One Controller"; return "Xbox One Controller";
case SDL_CONTROLLER_TYPE_PS3: case SDL_GAMEPAD_TYPE_PS3:
return "DualShock 3 Controller"; return "DualShock 3 Controller";
case SDL_CONTROLLER_TYPE_PS4: case SDL_GAMEPAD_TYPE_PS4:
return "DualShock 4 Controller"; return "DualShock 4 Controller";
case SDL_CONTROLLER_TYPE_PS5: case SDL_GAMEPAD_TYPE_PS5:
return "DualSense Controller"; return "DualSense Controller";
default: default:
break; break;
} }
const auto name = SDL_GameControllerName(sdl_controller.get()); const auto name = SDL_GetGamepadName(sdl_gamepad.get());
if (name) { if (name) {
return name; return name;
} }
} }
if (sdl_joystick) { if (sdl_joystick) {
const auto name = SDL_JoystickName(sdl_joystick.get()); const auto name = SDL_GetJoystickName(sdl_joystick.get());
if (name) { if (name) {
return name; return name;
} }
@ -286,8 +266,8 @@ public:
private: private:
Common::UUID guid; Common::UUID guid;
int port; int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; std::unique_ptr<SDL_Joystick, decltype(&SDL_CloseJoystick)> sdl_joystick;
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; std::unique_ptr<SDL_Gamepad, decltype(&SDL_CloseGamepad)> sdl_gamepad;
mutable std::mutex mutex; mutable std::mutex mutex;
u64 last_motion_update{}; u64 last_motion_update{};
@ -323,7 +303,7 @@ std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string&
} }
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); auto sdl_joystick = SDL_GetJoystickFromID(sdl_id);
const auto guid = GetGUID(sdl_joystick); const auto guid = GetGUID(sdl_joystick);
std::scoped_lock lock{joystick_map_mutex}; std::scoped_lock lock{joystick_map_mutex};
@ -345,16 +325,16 @@ std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl
return *vec_it; return *vec_it;
} }
void SDLDriver::InitJoystick(int joystick_index) { void SDLDriver::InitJoystick(SDL_JoystickID joystick_id) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); SDL_Joystick* sdl_joystick = SDL_OpenJoystick(joystick_id);
SDL_GameController* sdl_gamecontroller = nullptr; SDL_Gamepad* sdl_gamepad = nullptr;
if (SDL_IsGameController(joystick_index)) { if (SDL_IsGamepad(joystick_id)) {
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); sdl_gamepad = SDL_OpenGamepad(joystick_id);
} }
if (!sdl_joystick) { if (!sdl_joystick) {
LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); LOG_ERROR(Input, "Failed to open joystick {}", joystick_id);
return; return;
} }
@ -363,23 +343,23 @@ void SDLDriver::InitJoystick(int joystick_index) {
if (Settings::values.enable_joycon_driver) { if (Settings::values.enable_joycon_driver) {
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
(guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); LOG_WARNING(Input, "Preferring joycon driver for device ID {}", joystick_id);
SDL_JoystickClose(sdl_joystick); SDL_CloseJoystick(sdl_joystick);
return; return;
} }
} }
if (Settings::values.enable_procon_driver) { if (Settings::values.enable_procon_driver) {
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) { if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) {
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); LOG_WARNING(Input, "Preferring joycon driver for device ID {}", joystick_id);
SDL_JoystickClose(sdl_joystick); SDL_CloseJoystick(sdl_joystick);
return; return;
} }
} }
std::scoped_lock lock{joystick_map_mutex}; std::scoped_lock lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) { if (joystick_map.find(guid) == joystick_map.end()) {
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamepad);
PreSetController(joystick->GetPadIdentifier()); PreSetController(joystick->GetPadIdentifier());
joystick->EnableMotion(); joystick->EnableMotion();
joystick_map[guid].emplace_back(std::move(joystick)); joystick_map[guid].emplace_back(std::move(joystick));
@ -392,13 +372,13 @@ void SDLDriver::InitJoystick(int joystick_index) {
[](const auto& joystick) { return !joystick->GetSDLJoystick(); }); [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
if (joystick_it != joystick_guid_list.end()) { if (joystick_it != joystick_guid_list.end()) {
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamepad);
(*joystick_it)->EnableMotion(); (*joystick_it)->EnableMotion();
return; return;
} }
const int port = static_cast<int>(joystick_guid_list.size()); const int port = static_cast<int>(joystick_guid_list.size());
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamepad);
PreSetController(joystick->GetPadIdentifier()); PreSetController(joystick->GetPadIdentifier());
joystick->EnableMotion(); joystick->EnableMotion();
joystick_guid_list.emplace_back(std::move(joystick)); joystick_guid_list.emplace_back(std::move(joystick));
@ -408,7 +388,6 @@ void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
const auto guid = GetGUID(sdl_joystick); const auto guid = GetGUID(sdl_joystick);
std::scoped_lock lock{joystick_map_mutex}; std::scoped_lock lock{joystick_map_mutex};
// This call to guid is safe since the joystick is guaranteed to be in the map
const auto& joystick_guid_list = joystick_map[guid]; const auto& joystick_guid_list = joystick_map[guid];
const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
[&sdl_joystick](const auto& joystick) { [&sdl_joystick](const auto& joystick) {
@ -428,56 +407,85 @@ void SDLDriver::PumpEvents() const {
void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
switch (event.type) { switch (event.type) {
case SDL_JOYBUTTONUP: { case SDL_EVENT_GAMEPAD_BUTTON_UP: {
if (const auto joystick = GetSDLJoystickBySDLID(event.gbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.gbutton.button, false);
}
break;
}
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: {
if (const auto joystick = GetSDLJoystickBySDLID(event.gbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.gbutton.button, true);
}
break;
}
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.gaxis.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetAxis(identifier, event.gaxis.axis, event.gaxis.value / 32767.0f);
}
break;
}
case SDL_EVENT_JOYSTICK_BUTTON_UP: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier(); const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, false); SetButton(identifier, event.jbutton.button, false);
} }
break; break;
} }
case SDL_JOYBUTTONDOWN: { case SDL_EVENT_JOYSTICK_BUTTON_DOWN: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier(); const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, true); SetButton(identifier, event.jbutton.button, true);
} }
break; break;
} }
case SDL_JOYHATMOTION: { case SDL_EVENT_JOYSTICK_HAT_MOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier(); const PadIdentifier identifier = joystick->GetPadIdentifier();
SetHatButton(identifier, event.jhat.hat, event.jhat.value); SetHatButton(identifier, event.jhat.hat, event.jhat.value);
} }
break; break;
} }
case SDL_JOYAXISMOTION: { case SDL_EVENT_JOYSTICK_AXIS_MOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier(); const PadIdentifier identifier = joystick->GetPadIdentifier();
SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
} }
break; break;
} }
case SDL_CONTROLLERSENSORUPDATE: { case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { if (auto joystick = GetSDLJoystickBySDLID(event.gsensor.which)) {
if (joystick->UpdateMotion(event.csensor)) { if (joystick->UpdateMotion(event.gsensor)) {
const PadIdentifier identifier = joystick->GetPadIdentifier(); const PadIdentifier identifier = joystick->GetPadIdentifier();
SetMotion(identifier, 0, joystick->GetMotion()); SetMotion(identifier, 0, joystick->GetMotion());
} }
} }
break; break;
} }
case SDL_JOYBATTERYUPDATED: { case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: {
if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) { if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier(); const PadIdentifier identifier = joystick->GetPadIdentifier();
SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level)); SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.state));
} }
break; break;
} }
case SDL_JOYDEVICEREMOVED: case SDL_EVENT_GAMEPAD_REMOVED:
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); LOG_DEBUG(Input, "Gamepad removed with Instance_ID {}", event.gdevice.which);
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); CloseJoystick(SDL_GetJoystickFromID(event.gdevice.which));
break; break;
case SDL_JOYDEVICEADDED: case SDL_EVENT_GAMEPAD_ADDED:
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); LOG_DEBUG(Input, "Gamepad connected with device ID {}", event.gdevice.which);
InitJoystick(event.gdevice.which);
break;
case SDL_EVENT_JOYSTICK_REMOVED:
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
CloseJoystick(SDL_GetJoystickFromID(event.jdevice.which));
break;
case SDL_EVENT_JOYSTICK_ADDED:
LOG_DEBUG(Input, "Controller connected with device ID {}", event.jdevice.which);
InitJoystick(event.jdevice.which); InitJoystick(event.jdevice.which);
break; break;
} }
@ -489,24 +497,17 @@ void SDLDriver::CloseJoysticks() {
} }
SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
// Set our application name. Currently passed to DBus by SDL and visible to the user through
// their desktop environment.
SDL_SetHint(SDL_HINT_APP_NAME, "Eden"); SDL_SetHint(SDL_HINT_APP_NAME, "Eden");
if (!Settings::values.enable_raw_input) { if (!Settings::values.enable_raw_input) {
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
} }
// Prevent SDL from adding undesired axis // SDL_HINT_ACCELEROMETER_AS_JOYSTICK was removed in SDL3
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE and SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE were removed in
// SDL3 These are now handled by SDL_HINT_JOYSTICK_ENHANCED_REPORTS
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
// Disable hidapi drivers for joycon controllers when the custom joycon driver is enabled
if (Settings::values.enable_joycon_driver) { if (Settings::values.enable_joycon_driver) {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
} else { } else {
@ -516,7 +517,6 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, "1");
} }
// Disable hidapi drivers for pro controllers when the custom joycon driver is enabled
if (Settings::values.enable_procon_driver) { if (Settings::values.enable_procon_driver) {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
} else { } else {
@ -525,16 +525,11 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
} }
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1");
// Share the same button mapping with non-Nintendo controllers // SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS was removed in SDL3
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
// driver on Linux.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
// If the frontend is going to manage the event loop, then we don't start one here start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD);
start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; if (start_thread && !SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
return; return;
} }
@ -552,21 +547,25 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
} }
}); });
} }
// Because the events for joystick connection happens before we have our event watcher added, we
// can just open all the joysticks right here int num_joysticks;
for (int i = 0; i < SDL_NumJoysticks(); ++i) { SDL_JoystickID* joysticks = SDL_GetJoysticks(&num_joysticks);
InitJoystick(i); if (joysticks) {
for (int i = 0; i < num_joysticks; ++i) {
InitJoystick(joysticks[i]);
}
SDL_free(joysticks);
} }
} }
SDLDriver::~SDLDriver() { SDLDriver::~SDLDriver() {
CloseJoysticks(); CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this); SDL_RemoveEventWatch(&SDLEventWatcher, this);
initialized = false; initialized = false;
if (start_thread) { if (start_thread) {
vibration_thread.join(); vibration_thread.join();
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD);
} }
} }
@ -592,7 +591,6 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
} }
} }
// Add dual controllers
for (const auto& [key, value] : joystick_map) { for (const auto& [key, value] : joystick_map) {
for (const auto& joystick : value) { for (const auto& joystick : value) {
if (joystick->IsJoyconRight()) { if (joystick->IsJoyconRight()) {
@ -624,15 +622,12 @@ Common::Input::DriverResult SDLDriver::SetVibration(
return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF; return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
}; };
// Default exponential curve for rumble
f32 factor = 0.35f; f32 factor = 0.35f;
// If vibration is set as a linear output use a flatter value
if (vibration.type == Common::Input::VibrationAmplificationType::Linear) { if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
factor = 0.5f; factor = 0.5f;
} }
// Amplitude for HD rumble needs no modification
if (joystick->HasHDRumble()) { if (joystick->HasHDRumble()) {
factor = 1.0f; factor = 1.0f;
} }
@ -677,10 +672,7 @@ bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
return joystick->HasVibration(); return joystick->HasVibration();
} }
// First vibration might fail
joystick->RumblePlay(test_vibration); joystick->RumblePlay(test_vibration);
// Wait for about 15ms to ensure the controller is ready for the stop command
std::this_thread::sleep_for(std::chrono::milliseconds(15)); std::this_thread::sleep_for(std::chrono::milliseconds(15));
if (!joystick->RumblePlay(zero_vibration)) { if (!joystick->RumblePlay(zero_vibration)) {
@ -760,17 +752,17 @@ Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& g
} }
Common::ParamPackage SDLDriver::BuildParamPackageForBinding( Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const { int port, const Common::UUID& guid, const SDL_GamepadBinding& binding) const {
switch (binding.bindType) { switch (binding.input_type) {
case SDL_CONTROLLER_BINDTYPE_NONE: case SDL_GAMEPAD_BINDTYPE_NONE:
break; break;
case SDL_CONTROLLER_BINDTYPE_AXIS: case SDL_GAMEPAD_BINDTYPE_AXIS:
return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); return BuildAnalogParamPackageForButton(port, guid, binding.input.axis.axis);
case SDL_CONTROLLER_BINDTYPE_BUTTON: case SDL_GAMEPAD_BINDTYPE_BUTTON:
return BuildButtonParamPackageForButton(port, guid, binding.value.button); return BuildButtonParamPackageForButton(port, guid, binding.input.button);
case SDL_CONTROLLER_BINDTYPE_HAT: case SDL_GAMEPAD_BINDTYPE_HAT:
return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, return BuildHatParamPackageForButton(port, guid, binding.input.hat.hat,
static_cast<u8>(binding.value.hat.hat_mask)); static_cast<u8>(binding.input.hat.hat_mask));
} }
return {}; return {};
} }
@ -797,28 +789,23 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
} }
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController(); auto* gamepad = joystick->GetSDLGamepad();
if (controller == nullptr) { if (gamepad == nullptr) {
return {}; return {};
} }
// This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
// We will add those afterwards
ButtonBindings switch_to_sdl_button; ButtonBindings switch_to_sdl_button;
switch_to_sdl_button = GetDefaultButtonBinding(joystick); switch_to_sdl_button = GetDefaultButtonBinding(joystick);
// Add the missing bindings for ZL/ZR
static constexpr ZButtonBindings switch_to_sdl_axis{{ static constexpr ZButtonBindings switch_to_sdl_axis{{
{Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, {Settings::NativeButton::ZL, SDL_GAMEPAD_AXIS_LEFT_TRIGGER},
{Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, {Settings::NativeButton::ZR, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER},
}}; }};
// Parameters contain two joysticks return dual
if (params.Has("guid2")) { if (params.Has("guid2")) {
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
if (joystick2->GetSDLGameController() != nullptr) { if (joystick2->GetSDLGamepad() != nullptr) {
return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
switch_to_sdl_axis); switch_to_sdl_axis);
} }
@ -829,42 +816,41 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
ButtonBindings SDLDriver::GetDefaultButtonBinding( ButtonBindings SDLDriver::GetDefaultButtonBinding(
const std::shared_ptr<SDLJoystick>& joystick) const { const std::shared_ptr<SDLJoystick>& joystick) const {
// Default SL/SR mapping for other controllers auto sll_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
auto sll_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; auto srl_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
auto srl_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; auto slr_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
auto slr_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; auto srr_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
auto srr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
if (joystick->IsJoyconLeft()) { if (joystick->IsJoyconLeft()) {
sll_button = SDL_CONTROLLER_BUTTON_PADDLE2; sll_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE1;
srl_button = SDL_CONTROLLER_BUTTON_PADDLE4; srl_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE2;
} }
if (joystick->IsJoyconRight()) { if (joystick->IsJoyconRight()) {
slr_button = SDL_CONTROLLER_BUTTON_PADDLE3; slr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2;
srr_button = SDL_CONTROLLER_BUTTON_PADDLE1; srr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1;
} }
return { return {
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, std::pair{Settings::NativeButton::A, SDL_GAMEPAD_BUTTON_EAST},
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, {Settings::NativeButton::B, SDL_GAMEPAD_BUTTON_SOUTH},
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, {Settings::NativeButton::X, SDL_GAMEPAD_BUTTON_NORTH},
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, {Settings::NativeButton::Y, SDL_GAMEPAD_BUTTON_WEST},
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, {Settings::NativeButton::LStick, SDL_GAMEPAD_BUTTON_LEFT_STICK},
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, {Settings::NativeButton::RStick, SDL_GAMEPAD_BUTTON_RIGHT_STICK},
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, {Settings::NativeButton::L, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, {Settings::NativeButton::R, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, {Settings::NativeButton::Plus, SDL_GAMEPAD_BUTTON_START},
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, {Settings::NativeButton::Minus, SDL_GAMEPAD_BUTTON_BACK},
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, {Settings::NativeButton::DLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT},
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, {Settings::NativeButton::DUp, SDL_GAMEPAD_BUTTON_DPAD_UP},
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, {Settings::NativeButton::DRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, {Settings::NativeButton::DDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN},
{Settings::NativeButton::SLLeft, sll_button}, {Settings::NativeButton::SLLeft, sll_button},
{Settings::NativeButton::SRLeft, srl_button}, {Settings::NativeButton::SRLeft, srl_button},
{Settings::NativeButton::SLRight, slr_button}, {Settings::NativeButton::SLRight, slr_button},
{Settings::NativeButton::SRRight, srr_button}, {Settings::NativeButton::SRRight, srr_button},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, {Settings::NativeButton::Home, SDL_GAMEPAD_BUTTON_GUIDE},
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, {Settings::NativeButton::Screenshot, SDL_GAMEPAD_BUTTON_MISC1},
}; };
} }
@ -873,16 +859,25 @@ ButtonMapping SDLDriver::GetSingleControllerMapping(
const ZButtonBindings& switch_to_sdl_axis) const { const ZButtonBindings& switch_to_sdl_axis) const {
ButtonMapping mapping; ButtonMapping mapping;
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
auto* controller = joystick->GetSDLGameController();
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); // SDL_GetGamepadBindForButton was removed in SDL3
// We need to use SDL_GetGamepadStringForButton or work with joystick directly
// For now, create a dummy binding
SDL_GamepadBinding binding{};
binding.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON;
binding.input.button = sdl_button;
mapping.insert_or_assign( mapping.insert_or_assign(
switch_button, switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
} }
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); // SDL_GetGamepadBindForAxis was removed in SDL3
// We need to use SDL_GetGamepadStringForAxis or work with joystick directly
// For now, create a dummy binding
SDL_GamepadBinding binding{};
binding.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding.input.axis.axis = sdl_axis;
mapping.insert_or_assign( mapping.insert_or_assign(
switch_button, switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
@ -897,31 +892,41 @@ ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoyst
const ZButtonBindings& switch_to_sdl_axis) const { const ZButtonBindings& switch_to_sdl_axis) const {
ButtonMapping mapping; ButtonMapping mapping;
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
auto* controller = joystick->GetSDLGameController();
auto* controller2 = joystick2->GetSDLGameController();
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
if (IsButtonOnLeftSide(switch_button)) { if (IsButtonOnLeftSide(switch_button)) {
const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); // SDL_GetGamepadBindForButton was removed in SDL3
SDL_GamepadBinding binding{};
binding.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON;
binding.input.button = sdl_button;
mapping.insert_or_assign( mapping.insert_or_assign(
switch_button, switch_button,
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
continue; continue;
} }
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); // SDL_GetGamepadBindForButton was removed in SDL3
SDL_GamepadBinding binding{};
binding.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON;
binding.input.button = sdl_button;
mapping.insert_or_assign( mapping.insert_or_assign(
switch_button, switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
} }
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
if (IsButtonOnLeftSide(switch_button)) { if (IsButtonOnLeftSide(switch_button)) {
const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); // SDL_GetGamepadBindForAxis was removed in SDL3
SDL_GamepadBinding binding{};
binding.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding.input.axis.axis = sdl_axis;
mapping.insert_or_assign( mapping.insert_or_assign(
switch_button, switch_button,
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
continue; continue;
} }
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); // SDL_GetGamepadBindForAxis was removed in SDL3
SDL_GamepadBinding binding{};
binding.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding.input.axis.axis = sdl_axis;
mapping.insert_or_assign( mapping.insert_or_assign(
switch_button, switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
@ -953,53 +958,70 @@ AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& p
} }
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController(); auto* gamepad = joystick->GetSDLGamepad();
if (controller == nullptr) { if (gamepad == nullptr) {
return {}; return {};
} }
AnalogMapping mapping = {}; AnalogMapping mapping = {};
const auto& binding_left_x = // SDL_GetGamepadBindForAxis was removed in SDL3
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); // We need to work with the underlying joystick directly
const auto& binding_left_y = SDL_Joystick* sdl_joystick = SDL_GetGamepadJoystick(gamepad);
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); if (!sdl_joystick) {
return {};
}
// For now, use hardcoded axis mappings
SDL_GamepadBinding binding_left_x{};
binding_left_x.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding_left_x.input.axis.axis = 0; // Left stick X
SDL_GamepadBinding binding_left_y{};
binding_left_y.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding_left_y.input.axis.axis = 1; // Left stick Y
if (params.Has("guid2")) { if (params.Has("guid2")) {
const auto identifier = joystick2->GetPadIdentifier(); const auto identifier = joystick2->GetPadIdentifier();
PreSetController(identifier); PreSetController(identifier);
PreSetAxis(identifier, binding_left_x.value.axis); PreSetAxis(identifier, binding_left_x.input.axis.axis);
PreSetAxis(identifier, binding_left_y.value.axis); PreSetAxis(identifier, binding_left_y.input.axis.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis);
const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis);
mapping.insert_or_assign(Settings::NativeAnalog::LStick, mapping.insert_or_assign(Settings::NativeAnalog::LStick,
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, BuildParamPackageForAnalog(
binding_left_y.value.axis, identifier, binding_left_x.input.axis.axis,
left_offset_x, left_offset_y)); binding_left_y.input.axis.axis, left_offset_x, left_offset_y));
} else { } else {
const auto identifier = joystick->GetPadIdentifier(); const auto identifier = joystick->GetPadIdentifier();
PreSetController(identifier); PreSetController(identifier);
PreSetAxis(identifier, binding_left_x.value.axis); PreSetAxis(identifier, binding_left_x.input.axis.axis);
PreSetAxis(identifier, binding_left_y.value.axis); PreSetAxis(identifier, binding_left_y.input.axis.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis);
const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis);
mapping.insert_or_assign(Settings::NativeAnalog::LStick, mapping.insert_or_assign(Settings::NativeAnalog::LStick,
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, BuildParamPackageForAnalog(
binding_left_y.value.axis, identifier, binding_left_x.input.axis.axis,
left_offset_x, left_offset_y)); binding_left_y.input.axis.axis, left_offset_x, left_offset_y));
} }
const auto& binding_right_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); // For right stick
const auto& binding_right_y = SDL_GamepadBinding binding_right_x{};
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); binding_right_x.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding_right_x.input.axis.axis = 2; // Right stick X
SDL_GamepadBinding binding_right_y{};
binding_right_y.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
binding_right_y.input.axis.axis = 3; // Right stick Y
const auto identifier = joystick->GetPadIdentifier(); const auto identifier = joystick->GetPadIdentifier();
PreSetController(identifier); PreSetController(identifier);
PreSetAxis(identifier, binding_right_x.value.axis); PreSetAxis(identifier, binding_right_x.input.axis.axis);
PreSetAxis(identifier, binding_right_y.value.axis); PreSetAxis(identifier, binding_right_y.input.axis.axis);
const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); const auto right_offset_x = -GetAxis(identifier, binding_right_x.input.axis.axis);
const auto right_offset_y = GetAxis(identifier, binding_right_y.value.axis); const auto right_offset_y = GetAxis(identifier, binding_right_y.input.axis.axis);
mapping.insert_or_assign(Settings::NativeAnalog::RStick, mapping.insert_or_assign(Settings::NativeAnalog::RStick,
BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, BuildParamPackageForAnalog(identifier, binding_right_x.input.axis.axis,
binding_right_y.value.axis, right_offset_x, binding_right_y.input.axis.axis,
right_offset_y)); right_offset_x, right_offset_y));
return mapping; return mapping;
} }
@ -1009,8 +1031,8 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p
} }
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController(); auto* gamepad = joystick->GetSDLGamepad();
if (controller == nullptr) { if (gamepad == nullptr) {
return {}; return {};
} }
@ -1039,7 +1061,6 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p
Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const { Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) { if (params.Has("button")) {
// TODO(German77): Find how to substitute the values for real button names
return Common::Input::ButtonNames::Value; return Common::Input::ButtonNames::Value;
} }
if (params.Has("hat")) { if (params.Has("hat")) {
@ -1097,29 +1118,27 @@ bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) {
if (joystick == nullptr) { if (joystick == nullptr) {
return false; return false;
} }
auto* controller = joystick->GetSDLGameController(); auto* gamepad = joystick->GetSDLGamepad();
if (controller == nullptr) { if (gamepad == nullptr) {
return false; return false;
} }
const auto& axis_x = params.Get("axis_x", 0); const auto& axis_x = params.Get("axis_x", 0);
const auto& axis_y = params.Get("axis_y", 0); const auto& axis_y = params.Get("axis_y", 0);
const auto& binding_left_x = // SDL_GetGamepadBindForAxis was removed in SDL3
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); // Use hardcoded axis mappings for now
const auto& binding_right_x = const int binding_left_x = 0; // Left stick X
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); const int binding_right_x = 2; // Right stick X
const auto& binding_left_y = const int binding_left_y = 1; // Left stick Y
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); const int binding_right_y = 3; // Right stick Y
const auto& binding_right_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) { if (axis_x != binding_left_y && axis_x != binding_right_y) {
return false; return false;
} }
if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) { if (axis_y != binding_left_x && axis_y != binding_right_x) {
return false; return false;
} }
return true; return true;
} }
} // namespace InputCommon } // namespace InputCommon

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2018 Citra Emulator Project // SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -8,46 +10,33 @@
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <SDL.h> #include <SDL3/SDL.h>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/threadsafe_queue.h" #include "common/threadsafe_queue.h"
#include "input_common/input_engine.h" #include "input_common/input_engine.h"
union SDL_Event; union SDL_Event;
using SDL_GameController = struct _SDL_GameController; using SDL_Gamepad = struct SDL_Gamepad;
using SDL_Joystick = struct _SDL_Joystick; using SDL_Joystick = struct SDL_Joystick;
using SDL_JoystickID = s32; using SDL_JoystickID = Uint32;
namespace InputCommon { namespace InputCommon {
class SDLJoystick; class SDLJoystick;
using ButtonBindings = using ButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadButton>, 20>;
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 20>; using ZButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadAxis>, 2>;
using ZButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
class SDLDriver : public InputEngine { class SDLDriver : public InputEngine {
public: public:
/// Initializes and registers SDL device factories
explicit SDLDriver(std::string input_engine_); explicit SDLDriver(std::string input_engine_);
/// Unregisters SDL device factories and shut them down.
~SDLDriver() override; ~SDLDriver() override;
void PumpEvents() const; void PumpEvents() const;
/// Handle SDL_Events for joysticks from SDL_PollEvent
void HandleGameControllerEvent(const SDL_Event& event); void HandleGameControllerEvent(const SDL_Event& event);
/// Get the nth joystick with the corresponding GUID
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
/**
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
* tie it to a SDLJoystick with the same guid and that port
*/
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const Common::UUID& guid, int port); std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const Common::UUID& guid, int port);
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
@ -69,59 +58,40 @@ public:
bool IsVibrationEnabled(const PadIdentifier& identifier) override; bool IsVibrationEnabled(const PadIdentifier& identifier) override;
private: private:
void InitJoystick(int joystick_index); void InitJoystick(SDL_JoystickID joystick_id);
void CloseJoystick(SDL_Joystick* sdl_joystick); void CloseJoystick(SDL_Joystick* sdl_joystick);
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks(); void CloseJoysticks();
/// Takes all vibrations from the queue and sends the command to the controller
void SendVibrations(); void SendVibrations();
Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid, Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid,
s32 axis, float value = 0.1f) const; s32 axis, float value = 0.1f) const;
Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid, Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid,
s32 button) const; s32 button) const;
Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat, Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat,
u8 value) const; u8 value) const;
Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const; Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const;
Common::ParamPackage BuildParamPackageForBinding(int port, const Common::UUID& guid,
Common::ParamPackage BuildParamPackageForBinding( const SDL_GamepadBinding& binding) const;
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const;
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
int axis_y, float offset_x, int axis_y, float offset_x,
float offset_y) const; float offset_y) const;
/// Returns the default button bindings list
ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const; ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
/// Returns the button mappings from a single controller
ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
const ButtonBindings& switch_to_sdl_button, const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const; const ZButtonBindings& switch_to_sdl_axis) const;
/// Returns the button mappings from two different controllers
ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
const std::shared_ptr<SDLJoystick>& joystick2, const std::shared_ptr<SDLJoystick>& joystick2,
const ButtonBindings& switch_to_sdl_button, const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const; const ZButtonBindings& switch_to_sdl_axis) const;
/// Returns true if the button is on the left joycon
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
/// Queue of vibration request to controllers
Common::SPSCQueue<VibrationRequest> vibration_queue; Common::SPSCQueue<VibrationRequest> vibration_queue;
/// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex; std::mutex joystick_map_mutex;
bool start_thread = false; bool start_thread = false;
std::atomic<bool> initialized = false; std::atomic<bool> initialized = false;
std::thread vibration_thread; std::thread vibration_thread;
}; };
} // namespace InputCommon } // namespace InputCommon

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -10,7 +12,7 @@
#include <array> #include <array>
#include <functional> #include <functional>
#include <SDL_hidapi.h> #include <SDL3/SDL_hidapi.h>
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_funcs.h" #include "common/common_funcs.h"

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -22,7 +24,7 @@
#ifdef HAVE_LIBUSB #ifdef HAVE_LIBUSB
#include "input_common/drivers/gc_adapter.h" #include "input_common/drivers/gc_adapter.h"
#endif #endif
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
#include "input_common/drivers/joycon.h" #include "input_common/drivers/joycon.h"
#include "input_common/drivers/sdl_driver.h" #include "input_common/drivers/sdl_driver.h"
#endif #endif
@ -87,7 +89,7 @@ struct InputSubsystem::Impl {
#endif #endif
RegisterEngine("virtual_amiibo", virtual_amiibo); RegisterEngine("virtual_amiibo", virtual_amiibo);
RegisterEngine("virtual_gamepad", virtual_gamepad); RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
RegisterEngine("sdl", sdl); RegisterEngine("sdl", sdl);
RegisterEngine("joycon", joycon); RegisterEngine("joycon", joycon);
#endif #endif
@ -121,7 +123,7 @@ struct InputSubsystem::Impl {
#endif #endif
UnregisterEngine(virtual_amiibo); UnregisterEngine(virtual_amiibo);
UnregisterEngine(virtual_gamepad); UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
UnregisterEngine(sdl); UnregisterEngine(sdl);
UnregisterEngine(joycon); UnregisterEngine(joycon);
#endif #endif
@ -151,7 +153,7 @@ struct InputSubsystem::Impl {
#endif #endif
auto udp_devices = udp_client->GetInputDevices(); auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
auto joycon_devices = joycon->GetInputDevices(); auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
auto sdl_devices = sdl->GetInputDevices(); auto sdl_devices = sdl->GetInputDevices();
@ -186,7 +188,7 @@ struct InputSubsystem::Impl {
if (engine == udp_client->GetEngineName()) { if (engine == udp_client->GetEngineName()) {
return udp_client; return udp_client;
} }
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) { if (engine == sdl->GetEngineName()) {
return sdl; return sdl;
} }
@ -277,7 +279,7 @@ struct InputSubsystem::Impl {
if (engine == virtual_gamepad->GetEngineName()) { if (engine == virtual_gamepad->GetEngineName()) {
return true; return true;
} }
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) { if (engine == sdl->GetEngineName()) {
return true; return true;
} }
@ -298,7 +300,7 @@ struct InputSubsystem::Impl {
gcadapter->BeginConfiguration(); gcadapter->BeginConfiguration();
#endif #endif
udp_client->BeginConfiguration(); udp_client->BeginConfiguration();
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
sdl->BeginConfiguration(); sdl->BeginConfiguration();
joycon->BeginConfiguration(); joycon->BeginConfiguration();
#endif #endif
@ -314,7 +316,7 @@ struct InputSubsystem::Impl {
gcadapter->EndConfiguration(); gcadapter->EndConfiguration();
#endif #endif
udp_client->EndConfiguration(); udp_client->EndConfiguration();
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
sdl->EndConfiguration(); sdl->EndConfiguration();
joycon->EndConfiguration(); joycon->EndConfiguration();
#endif #endif
@ -322,7 +324,7 @@ struct InputSubsystem::Impl {
void PumpEvents() const { void PumpEvents() const {
update_engine->PumpEvents(); update_engine->PumpEvents();
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
sdl->PumpEvents(); sdl->PumpEvents();
#endif #endif
} }
@ -347,7 +349,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<GCAdapter> gcadapter; std::shared_ptr<GCAdapter> gcadapter;
#endif #endif
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
std::shared_ptr<SDLDriver> sdl; std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<Joycons> joycon; std::shared_ptr<Joycons> joycon;
#endif #endif

View file

@ -466,9 +466,9 @@ if (YUZU_USE_BUNDLED_QT)
copy_yuzu_Qt6_deps(yuzu) copy_yuzu_Qt6_deps(yuzu)
endif() endif()
if (ENABLE_SDL2) if (ENABLE_SDL3)
target_link_libraries(yuzu PRIVATE SDL2::SDL2) target_link_libraries(yuzu PRIVATE SDL3::SDL3)
target_compile_definitions(yuzu PRIVATE HAVE_SDL2) target_compile_definitions(yuzu PRIVATE HAVE_SDL3)
endif() endif()
if (MSVC) if (MSVC)

View file

@ -98,8 +98,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QCheckBox> #include <QCheckBox>
#include <QStringLiteral> #include <QStringLiteral>
#ifdef HAVE_SDL2 #ifdef HAVE_SDL3
#include <SDL.h> // For SDL ScreenSaver functions #include <SDL3/SDL.h> // For SDL ScreenSaver functions
#endif #endif
#include <fmt/ranges.h> #include <fmt/ranges.h>
@ -588,7 +588,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle()); VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle());
} }
#if defined(HAVE_SDL2) && !defined(_WIN32) #if defined(HAVE_SDL3) && !defined(_WIN32)
SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_InitSubSystem(SDL_INIT_VIDEO);
// Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to // Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to
@ -1868,7 +1868,7 @@ void GMainWindow::OnSigInterruptNotifierActivated() {
void GMainWindow::PreventOSSleep() { void GMainWindow::PreventOSSleep() {
#ifdef _WIN32 #ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#elif defined(HAVE_SDL2) #elif defined(HAVE_SDL3)
SDL_DisableScreenSaver(); SDL_DisableScreenSaver();
#endif #endif
} }
@ -1876,7 +1876,7 @@ void GMainWindow::PreventOSSleep() {
void GMainWindow::AllowOSSleep() { void GMainWindow::AllowOSSleep() {
#ifdef _WIN32 #ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS); SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(HAVE_SDL2) #elif defined(HAVE_SDL3)
SDL_EnableScreenSaver(); SDL_EnableScreenSaver();
#endif #endif
} }

View file

@ -16,14 +16,14 @@ function(create_resource file output filename)
endfunction() endfunction()
add_executable(yuzu-cmd add_executable(yuzu-cmd
emu_window/emu_window_sdl2.cpp emu_window/emu_window_sdl3.cpp
emu_window/emu_window_sdl2.h emu_window/emu_window_sdl3.h
emu_window/emu_window_sdl2_gl.cpp emu_window/emu_window_sdl3_gl.cpp
emu_window/emu_window_sdl2_gl.h emu_window/emu_window_sdl3_gl.h
emu_window/emu_window_sdl2_null.cpp emu_window/emu_window_sdl3_null.cpp
emu_window/emu_window_sdl2_null.h emu_window/emu_window_sdl3_null.h
emu_window/emu_window_sdl2_vk.cpp emu_window/emu_window_sdl3_vk.cpp
emu_window/emu_window_sdl2_vk.h emu_window/emu_window_sdl3_vk.h
precompiled_headers.h precompiled_headers.h
sdl_config.cpp sdl_config.cpp
sdl_config.h sdl_config.h
@ -41,7 +41,7 @@ target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2) target_link_libraries(yuzu-cmd PRIVATE SDL3::SDL3)
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd) install(TARGETS yuzu-cmd)

View file

@ -1,95 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <memory>
#include <string>
#include <fmt/ranges.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
#include <SDL.h>
#include <SDL_syswm.h>
EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
: EmuWindow_SDL2{input_subsystem_, system_} {
const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)",
Common::g_build_name,
Common::g_scm_branch,
Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SysWMinfo wm;
SDL_VERSION(&wm.version);
if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) {
LOG_CRITICAL(Frontend, "Failed to get information from the window manager: {}",
SDL_GetError());
std::exit(EXIT_FAILURE);
}
SetWindowIcon();
if (fullscreen) {
Fullscreen();
ShowCursor(false);
}
switch (wm.subsystem) {
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS:
window_info.type = Core::Frontend::WindowSystemType::Windows;
window_info.render_surface = reinterpret_cast<void*>(wm.info.win.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_TYPE::SDL_SYSWM_X11:
window_info.type = Core::Frontend::WindowSystemType::X11;
window_info.display_connection = wm.info.x11.display;
window_info.render_surface = reinterpret_cast<void*>(wm.info.x11.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND
case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND:
window_info.type = Core::Frontend::WindowSystemType::Wayland;
window_info.display_connection = wm.info.wl.display;
window_info.render_surface = wm.info.wl.surface;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA:
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
window_info.render_surface = SDL_Metal_CreateView(render_window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_ANDROID
case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID:
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(wm.info.android.window);
break;
#endif
default:
LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem);
std::exit(EXIT_FAILURE);
break;
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "Eden Version: {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
}
EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}

View file

@ -3,8 +3,10 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL.h> #include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_mouse.h>
#include "SDL3/SDL_video.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/settings.h" #include "common/settings.h"
@ -15,26 +17,26 @@
#include "input_common/drivers/mouse.h" #include "input_common/drivers/mouse.h"
#include "input_common/drivers/touch_screen.h" #include "input_common/drivers/touch_screen.h"
#include "input_common/main.h" #include "input_common/main.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#include "yuzu_cmd/yuzu_icon.h" #include "yuzu_cmd/yuzu_icon.h"
EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
: input_subsystem{input_subsystem_}, system{system_} { : input_subsystem{input_subsystem_}, system{system_} {
input_subsystem->Initialize(); input_subsystem->Initialize();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}, Exiting...", SDL_GetError()); LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError());
exit(1); exit(1);
} }
SDL_SetMainReady(); SDL_SetMainReady();
} }
EmuWindow_SDL2::~EmuWindow_SDL2() { EmuWindow_SDL3::~EmuWindow_SDL3() {
system.HIDCore().UnloadInputDevices(); system.HIDCore().UnloadInputDevices();
input_subsystem->Shutdown(); input_subsystem->Shutdown();
SDL_Quit(); SDL_Quit();
} }
InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const { InputCommon::MouseButton EmuWindow_SDL3::SDLButtonToMouseButton(u32 button) const {
switch (button) { switch (button) {
case SDL_BUTTON_LEFT: case SDL_BUTTON_LEFT:
return InputCommon::MouseButton::Left; return InputCommon::MouseButton::Left;
@ -52,21 +54,17 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons
} }
/// @brief Translates pixel position to float position /// @brief Translates pixel position to float position
EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const { EmuWindow_SDL3::FloatPairNonHFA EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const {
int w = 0, h = 0; int w = 0, h = 0;
SDL_GetWindowSize(render_window, &w, &h); SDL_GetWindowSize(render_window, &w, &h);
const float fx = float(touch_x) / w; const float fx = float(touch_x) / w;
const float fy = float(touch_y) / h; const float fy = float(touch_y) / h;
return { return {std::clamp<float>(fx, 0.0f, 1.0f), std::clamp<float>(fy, 0.0f, 1.0f), 0};
std::clamp<float>(fx, 0.0f, 1.0f),
std::clamp<float>(fy, 0.0f, 1.0f),
0
};
} }
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { void EmuWindow_SDL3::OnMouseButton(u32 button, bool pressed, s32 x, s32 y) {
const auto mouse_button = SDLButtonToMouseButton(button); const auto mouse_button = SDLButtonToMouseButton(button);
if (state == SDL_PRESSED) { if (pressed) {
auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y); auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y);
input_subsystem->GetMouse()->PressButton(x, y, mouse_button); input_subsystem->GetMouse()->PressButton(x, y, mouse_button);
input_subsystem->GetMouse()->PressMouseButton(mouse_button); input_subsystem->GetMouse()->PressMouseButton(mouse_button);
@ -76,64 +74,65 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
} }
} }
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { void EmuWindow_SDL3::OnMouseMotion(s32 x, s32 y) {
auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y); auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y);
input_subsystem->GetMouse()->Move(x, y, 0, 0); input_subsystem->GetMouse()->Move(x, y, 0, 0);
input_subsystem->GetMouse()->MouseMove(touch_x, touch_y); input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
} }
void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) { void EmuWindow_SDL3::OnFingerDown(float x, float y, std::size_t id) {
input_subsystem->GetTouchScreen()->TouchPressed(x, y, id); input_subsystem->GetTouchScreen()->TouchPressed(x, y, id);
} }
void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) { void EmuWindow_SDL3::OnFingerMotion(float x, float y, std::size_t id) {
input_subsystem->GetTouchScreen()->TouchMoved(x, y, id); input_subsystem->GetTouchScreen()->TouchMoved(x, y, id);
} }
void EmuWindow_SDL2::OnFingerUp() { void EmuWindow_SDL3::OnFingerUp() {
input_subsystem->GetTouchScreen()->ReleaseAllTouch(); input_subsystem->GetTouchScreen()->ReleaseAllTouch();
} }
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { void EmuWindow_SDL3::OnKeyEvent(int key, bool pressed) {
if (state == SDL_PRESSED) { if (pressed) {
input_subsystem->GetKeyboard()->PressKey(static_cast<std::size_t>(key)); input_subsystem->GetKeyboard()->PressKey(static_cast<std::size_t>(key));
} else if (state == SDL_RELEASED) { } else {
input_subsystem->GetKeyboard()->ReleaseKey(static_cast<std::size_t>(key)); input_subsystem->GetKeyboard()->ReleaseKey(static_cast<std::size_t>(key));
} }
} }
bool EmuWindow_SDL2::IsOpen() const { bool EmuWindow_SDL3::IsOpen() const {
return is_open; return is_open;
} }
bool EmuWindow_SDL2::IsShown() const { bool EmuWindow_SDL3::IsShown() const {
return is_shown; return is_shown;
} }
void EmuWindow_SDL2::OnResize() { void EmuWindow_SDL3::OnResize() {
int width, height; int width, height;
SDL_GL_GetDrawableSize(render_window, &width, &height); SDL_GetWindowSizeInPixels(render_window, &width, &height);
UpdateCurrentFramebufferLayout(width, height); UpdateCurrentFramebufferLayout(width, height);
} }
void EmuWindow_SDL2::ShowCursor(bool show_cursor) { void EmuWindow_SDL3::ShowCursor(bool show_cursor) {
SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE); show_cursor ? SDL_ShowCursor() : SDL_HideCursor();
} }
void EmuWindow_SDL2::Fullscreen() { void EmuWindow_SDL3::Fullscreen() {
SDL_DisplayMode display_mode; const SDL_DisplayMode* display_mode;
switch (Settings::values.fullscreen_mode.GetValue()) { switch (Settings::values.fullscreen_mode.GetValue()) {
case Settings::FullscreenMode::Exclusive: case Settings::FullscreenMode::Exclusive:
// Set window size to render size before entering fullscreen -- SDL2 does not resize window // Set window size to render size before entering fullscreen -- SDL3 does not resize window
// to display dimensions automatically in this mode. // to display dimensions automatically in this mode.
if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) { display_mode = SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window));
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h); if (display_mode) {
SDL_SetWindowSize(render_window, display_mode->w, display_mode->h);
} else { } else {
LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError()); LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError());
} }
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) { if (!SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN)) {
return; return;
} }
@ -141,12 +140,13 @@ void EmuWindow_SDL2::Fullscreen() {
LOG_INFO(Frontend, "Attempting to use borderless fullscreen..."); LOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
[[fallthrough]]; [[fallthrough]];
case Settings::FullscreenMode::Borderless: case Settings::FullscreenMode::Borderless:
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { SDL_SetWindowFullscreenMode(render_window, NULL);
if (!SDL_SetWindowFullscreen(render_window, true)) {
LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
[[fallthrough]];
} else {
return; return;
} }
LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
[[fallthrough]];
default: default:
// Fallback algorithm: Maximise window. // Fallback algorithm: Maximise window.
// Works on all systems (unless something is seriously wrong), so no fallback for this one. // Works on all systems (unless something is seriously wrong), so no fallback for this one.
@ -156,11 +156,13 @@ void EmuWindow_SDL2::Fullscreen() {
} }
} }
void EmuWindow_SDL2::WaitEvent() { void EmuWindow_SDL3::WaitEvent() {
// Called on main thread // Called on main thread
SDL_Event event; SDL_Event event;
if (!SDL_WaitEvent(&event)) { if (SDL_WaitEvent(&event)) {
// Event received successfully
} else {
const char* error = SDL_GetError(); const char* error = SDL_GetError();
if (!error || strcmp(error, "") == 0) { if (!error || strcmp(error, "") == 0) {
// https://github.com/libsdl-org/SDL/issues/5780 // https://github.com/libsdl-org/SDL/issues/5780
@ -174,52 +176,49 @@ void EmuWindow_SDL2::WaitEvent() {
} }
switch (event.type) { switch (event.type) {
case SDL_WINDOWEVENT: case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
switch (event.window.event) { case SDL_EVENT_WINDOW_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_WINDOWEVENT_RESIZED: case SDL_EVENT_WINDOW_RESTORED:
case SDL_WINDOWEVENT_MAXIMIZED: OnResize();
case SDL_WINDOWEVENT_RESTORED:
OnResize();
break;
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_EXPOSED:
is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED;
OnResize();
break;
case SDL_WINDOWEVENT_CLOSE:
is_open = false;
break;
}
break; break;
case SDL_KEYDOWN: case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_KEYUP: case SDL_EVENT_WINDOW_EXPOSED:
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state); is_shown = event.type == SDL_EVENT_WINDOW_EXPOSED;
OnResize();
break; break;
case SDL_MOUSEMOTION: case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
is_open = false;
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
OnKeyEvent(static_cast<int>(event.key.scancode), event.type == SDL_EVENT_KEY_DOWN);
break;
case SDL_EVENT_MOUSE_MOTION:
// ignore if it came from touch // ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID) if (event.button.which != SDL_TOUCH_MOUSEID)
OnMouseMotion(event.motion.x, event.motion.y); OnMouseMotion(event.motion.x, event.motion.y);
break; break;
case SDL_MOUSEBUTTONDOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_MOUSEBUTTONUP: case SDL_EVENT_MOUSE_BUTTON_UP:
// ignore if it came from touch // ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID) { if (event.button.which != SDL_TOUCH_MOUSEID) {
OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y); OnMouseButton(event.button.button, event.type == SDL_EVENT_MOUSE_BUTTON_DOWN,
event.button.x, event.button.y);
} }
break; break;
case SDL_FINGERDOWN: case SDL_EVENT_FINGER_DOWN:
OnFingerDown(event.tfinger.x, event.tfinger.y, OnFingerDown(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchId)); static_cast<std::size_t>(event.tfinger.touchID));
break; break;
case SDL_FINGERMOTION: case SDL_EVENT_FINGER_MOTION:
OnFingerMotion(event.tfinger.x, event.tfinger.y, OnFingerMotion(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchId)); static_cast<std::size_t>(event.tfinger.touchID));
break; break;
case SDL_FINGERUP: case SDL_EVENT_FINGER_UP:
OnFingerUp(); OnFingerUp();
break; break;
case SDL_QUIT: case SDL_EVENT_QUIT:
is_open = false; is_open = false;
break; break;
default: default:
@ -229,34 +228,31 @@ void EmuWindow_SDL2::WaitEvent() {
const u32 current_time = SDL_GetTicks(); const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) { if (current_time > last_time + 2000) {
const auto results = system.GetAndResetPerfStats(); const auto results = system.GetAndResetPerfStats();
const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", const auto title = fmt::format(
Common::g_build_fullname, "{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0);
Common::g_scm_desc,
results.average_game_fps,
results.emulation_speed * 100.0);
SDL_SetWindowTitle(render_window, title.c_str()); SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time; last_time = current_time;
} }
} }
// Credits to Samantas5855 and others for this function. // Credits to Samantas5855 and others for this function.
void EmuWindow_SDL2::SetWindowIcon() { void EmuWindow_SDL3::SetWindowIcon() {
SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
if (yuzu_icon_stream == nullptr) { if (yuzu_icon_stream == nullptr) {
LOG_WARNING(Frontend, "Failed to create Eden icon stream."); LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
return; return;
} }
SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1); SDL_Surface* const window_icon = SDL_LoadBMP_IO(yuzu_icon_stream, 1);
if (window_icon == nullptr) { if (window_icon == nullptr) {
LOG_WARNING(Frontend, "Failed to read BMP from stream."); LOG_WARNING(Frontend, "Failed to read BMP from stream.");
return; return;
} }
// The icon is attached to the window pointer // The icon is attached to the window pointer
SDL_SetWindowIcon(render_window, window_icon); SDL_SetWindowIcon(render_window, window_icon);
SDL_FreeSurface(window_icon); SDL_DestroySurface(window_icon);
} }
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
} }

View file

@ -22,10 +22,10 @@ class InputSubsystem;
enum class MouseButton; enum class MouseButton;
} // namespace InputCommon } // namespace InputCommon
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { class EmuWindow_SDL3 : public Core::Frontend::EmuWindow {
public: public:
explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_); explicit EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_);
~EmuWindow_SDL2(); ~EmuWindow_SDL3();
/// Whether the window is still open, and a close request hasn't yet been sent /// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const; bool IsOpen() const;
@ -41,7 +41,7 @@ public:
protected: protected:
/// Called by WaitEvent when a key is pressed or released. /// Called by WaitEvent when a key is pressed or released.
void OnKeyEvent(int key, u8 state); void OnKeyEvent(int key, bool pressed);
/// Converts a SDL mouse button into MouseInput mouse button /// Converts a SDL mouse button into MouseInput mouse button
InputCommon::MouseButton SDLButtonToMouseButton(u32 button) const; InputCommon::MouseButton SDLButtonToMouseButton(u32 button) const;
@ -53,7 +53,7 @@ protected:
FloatPairNonHFA MouseToTouchPos(s32 touch_x, s32 touch_y) const; FloatPairNonHFA MouseToTouchPos(s32 touch_x, s32 touch_y) const;
/// Called by WaitEvent when a mouse button is pressed or released /// Called by WaitEvent when a mouse button is pressed or released
void OnMouseButton(u32 button, u8 state, s32 x, s32 y); void OnMouseButton(u32 button, bool pressed, s32 x, s32 y);
/// Called by WaitEvent when the mouse moves. /// Called by WaitEvent when the mouse moves.
void OnMouseMotion(s32 x, s32 y); void OnMouseMotion(s32 x, s32 y);
@ -85,7 +85,7 @@ protected:
/// Is the window being shown? /// Is the window being shown?
bool is_shown = true; bool is_shown = true;
/// Internal SDL2 render window /// Internal SDL3 render window
SDL_Window* render_window{}; SDL_Window* render_window{};
/// Keeps track of how often to update the title bar during gameplay /// Keeps track of how often to update the title bar during gameplay

View file

@ -4,23 +4,21 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <string> #include <string>
#define SDL_MAIN_HANDLED #define SDL_MAIN_HANDLED
#include <SDL.h> #include <SDL3/SDL.h>
#include <fmt/ranges.h> #include <fmt/ranges.h>
#include <glad/glad.h> #include <glad/glad.h>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h" #include "core/core.h"
#include "input_common/main.h" #include "input_common/main.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" #include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
class SDLGLContext : public Core::Frontend::GraphicsContext { class SDLGLContext : public Core::Frontend::GraphicsContext {
public: public:
@ -30,7 +28,7 @@ public:
~SDLGLContext() { ~SDLGLContext() {
DoneCurrent(); DoneCurrent();
SDL_GL_DeleteContext(context); SDL_GL_DestroyContext(context);
} }
void SwapBuffers() override { void SwapBuffers() override {
@ -58,7 +56,7 @@ private:
bool is_current = false; bool is_current = false;
}; };
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() {
std::vector<std::string_view> unsupported_ext; std::vector<std::string_view> unsupported_ext;
// Extensions required to support some texture formats. // Extensions required to support some texture formats.
@ -76,9 +74,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
return unsupported_ext.empty(); return unsupported_ext.empty();
} }
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, EmuWindow_SDL3_GL::EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen) Core::System& system_, bool fullscreen)
: EmuWindow_SDL2{input_subsystem_, system_} { : EmuWindow_SDL3{input_subsystem_, system_} {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
@ -95,15 +93,12 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
std::string window_title = fmt::format("{} | {}-{}", Common::g_build_fullname, std::string window_title = fmt::format("{} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc); Common::g_scm_branch, Common::g_scm_desc);
render_window = render_window = SDL_CreateWindow(
SDL_CreateWindow(window_title.c_str(), window_title.c_str(), Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOWPOS_UNDEFINED, // x position SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
SDL_WINDOWPOS_UNDEFINED, // y position
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (render_window == nullptr) { if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError()); LOG_CRITICAL(Frontend, "Failed to create SDL3 window! {}", SDL_GetError());
exit(1); exit(1);
} }
@ -120,15 +115,17 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
core_context = CreateSharedContext(); core_context = CreateSharedContext();
if (window_context == nullptr) { if (window_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); LOG_CRITICAL(Frontend, "Failed to create SDL3 GL context: {}", SDL_GetError());
exit(1); exit(1);
} }
if (core_context == nullptr) { if (core_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError()); LOG_CRITICAL(Frontend, "Failed to create shared SDL3 GL context: {}", SDL_GetError());
exit(1); exit(1);
} }
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { if (!gladLoadGLLoader([](const char* name) -> void* {
return reinterpret_cast<void*>(SDL_GL_GetProcAddress(name));
})) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
exit(1); exit(1);
} }
@ -146,11 +143,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
Settings::LogSettings(); Settings::LogSettings();
} }
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() {
core_context.reset(); core_context.reset();
SDL_GL_DeleteContext(window_context); SDL_GL_DestroyContext(window_context);
} }
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL3_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>(render_window); return std::make_unique<SDLGLContext>(render_window);
} }

View file

@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <memory> #include <memory>
#include <SDL3/SDL.h>
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/emu_window/emu_window_sdl3.h"
namespace Core { namespace Core {
class System; class System;
@ -15,11 +18,11 @@ namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { class EmuWindow_SDL3_GL final : public EmuWindow_SDL3 {
public: public:
explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, explicit EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_,
bool fullscreen); bool fullscreen);
~EmuWindow_SDL2_GL(); ~EmuWindow_SDL3_GL();
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
@ -27,8 +30,6 @@ private:
/// Whether the GPU and driver supports the OpenGL extension required /// Whether the GPU and driver supports the OpenGL extension required
bool SupportsRequiredGLExtensions(); bool SupportsRequiredGLExtensions();
using SDL_GLContext = void*;
/// The OpenGL context associated with the window /// The OpenGL context associated with the window
SDL_GLContext window_context; SDL_GLContext window_context;

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -10,25 +12,24 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "video_core/renderer_null/renderer_null.h" #include "video_core/renderer_null/renderer_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h" #include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
#ifdef YUZU_USE_EXTERNAL_SDL2 #ifdef YUZU_USE_EXTERNAL_SDL3
// Include this before SDL.h to prevent the external from including a dummy // Include this before SDL.h to prevent the external from including a dummy
#define USING_GENERATED_CONFIG_H #define USING_GENERATED_CONFIG_H
#include <SDL_config.h> #include <SDL3/SDL_config.h>
#endif #endif
#include <SDL.h> #include <SDL3/SDL.h>
EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_, EmuWindow_SDL3_Null::EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen) Core::System& system_, bool fullscreen)
: EmuWindow_SDL2{input_subsystem_, system_} { : EmuWindow_SDL3{input_subsystem_, system_} {
const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", Common::g_build_name, const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc); Common::g_scm_branch, Common::g_scm_desc);
render_window = render_window = SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Layout::ScreenUndocked::Height,
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SetWindowIcon(); SetWindowIcon();
@ -44,8 +45,8 @@ EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subs
Common::g_scm_branch, Common::g_scm_desc); Common::g_scm_branch, Common::g_scm_desc);
} }
EmuWindow_SDL2_Null::~EmuWindow_SDL2_Null() = default; EmuWindow_SDL3_Null::~EmuWindow_SDL3_Null() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_Null::CreateSharedContext() const { std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL3_Null::CreateSharedContext() const {
return std::make_unique<DummyContext>(); return std::make_unique<DummyContext>();
} }

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -6,7 +8,7 @@
#include <memory> #include <memory>
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/emu_window/emu_window_sdl3.h"
namespace Core { namespace Core {
class System; class System;
@ -16,11 +18,11 @@ namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
class EmuWindow_SDL2_Null final : public EmuWindow_SDL2 { class EmuWindow_SDL3_Null final : public EmuWindow_SDL3 {
public: public:
explicit EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_, explicit EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system, bool fullscreen); Core::System& system, bool fullscreen);
~EmuWindow_SDL2_Null() override; ~EmuWindow_SDL3_Null() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
}; };

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <memory>
#include <string>
#include <fmt/ranges.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
#include <SDL3/SDL.h>
EmuWindow_SDL3_VK::EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
: EmuWindow_SDL3{input_subsystem_, system_} {
const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
render_window = SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
SetWindowIcon();
if (fullscreen) {
Fullscreen();
ShowCursor(false);
}
{
SDL_PropertiesID props = SDL_GetWindowProperties(render_window);
#if defined(SDL_PLATFORM_WIN32)
window_info.type = Core::Frontend::WindowSystemType::Windows;
window_info.render_surface =
SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
#elif defined(SDL_PLATFORM_MACOS)
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
window_info.render_surface = SDL_Metal_CreateView(render_window);
#elif defined(SDL_PLATFORM_LINUX)
const char* video_driver = SDL_GetCurrentVideoDriver();
if (video_driver && SDL_strcmp(video_driver, "x11") == 0) {
window_info.type = Core::Frontend::WindowSystemType::X11;
window_info.display_connection =
SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
window_info.render_surface = reinterpret_cast<void*>(
SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0));
} else if (video_driver && SDL_strcmp(video_driver, "wayland") == 0) {
window_info.type = Core::Frontend::WindowSystemType::Wayland;
window_info.display_connection =
SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
window_info.render_surface =
SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
} else {
LOG_CRITICAL(Frontend, "Unsupported SDL video driver: {}",
video_driver ? video_driver : "(null)");
std::exit(EXIT_FAILURE);
}
#elif defined(SDL_PLATFORM_ANDROID)
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface =
SDL_GetPointerProperty(props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr);
#else
LOG_CRITICAL(Frontend, "Unsupported platform for SDL window properties");
std::exit(EXIT_FAILURE);
#endif
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "Eden Version: {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
}
EmuWindow_SDL3_VK::~EmuWindow_SDL3_VK() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL3_VK::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -6,7 +8,7 @@
#include <memory> #include <memory>
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/emu_window/emu_window_sdl3.h"
namespace Core { namespace Core {
class System; class System;
@ -16,11 +18,11 @@ namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { class EmuWindow_SDL3_VK final : public EmuWindow_SDL3 {
public: public:
explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, explicit EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system,
bool fullscreen); bool fullscreen);
~EmuWindow_SDL2_VK() override; ~EmuWindow_SDL3_VK() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
}; };

View file

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h // SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h
#define SDL_MAIN_HANDLED #define SDL_MAIN_HANDLED
#include <SDL.h> #include <SDL3/SDL.h>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "input_common/main.h" #include "input_common/main.h"

View file

@ -34,10 +34,10 @@
#include "network/network.h" #include "network/network.h"
#include "sdl_config.h" #include "sdl_config.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" #include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h" #include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" #include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
#ifdef _WIN32 #ifdef _WIN32
// windows.h needs to be included before shellapi.h // windows.h needs to be included before shellapi.h
@ -346,16 +346,16 @@ int main(int argc, char** argv) {
// Apply the command line arguments // Apply the command line arguments
system.ApplySettings(); system.ApplySettings();
std::unique_ptr<EmuWindow_SDL2> emu_window; std::unique_ptr<EmuWindow_SDL3> emu_window;
switch (Settings::values.renderer_backend.GetValue()) { switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL: case Settings::RendererBackend::OpenGL:
emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, system, fullscreen); emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, system, fullscreen);
break; break;
case Settings::RendererBackend::Vulkan: case Settings::RendererBackend::Vulkan:
emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem, system, fullscreen); emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, system, fullscreen);
break; break;
case Settings::RendererBackend::Null: case Settings::RendererBackend::Null:
emu_window = std::make_unique<EmuWindow_SDL2_Null>(&input_subsystem, system, fullscreen); emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, system, fullscreen);
break; break;
} }