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_C_FLAGS="$ARCH_FLAGS" \
-DYUZU_USE_BUNDLED_QT=OFF \
-DYUZU_USE_BUNDLED_SDL2=OFF \
-DYUZU_USE_EXTERNAL_SDL2=ON \
-DYUZU_USE_BUNDLED_SDL3=OFF \
-DYUZU_USE_EXTERNAL_SDL3=ON \
-DYUZU_TESTS=OFF \
-DYUZU_USE_QT_MULTIMEDIA=$MULTIMEDIA \
-DYUZU_USE_QT_WEB_ENGINE=$WEBENGINE \

View file

@ -24,7 +24,7 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE:-Release}" \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
-DYUZU_USE_BUNDLED_SDL2=ON \
-DYUZU_USE_BUNDLED_SDL3=ON \
-DBUILD_TESTING=OFF \
-DYUZU_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")
endif()
# Set bundled sdl2/qt as dependent options.
# On Linux system SDL2 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)
# Set bundled sdl3/qt as dependent options.
# On Linux system SDL3 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
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
cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF)
option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}")
cmake_dependent_option(YUZU_USE_EXTERNAL_SDL3 "Compile external SDL3" OFF "NOT MSVC" OFF)
option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" "${MSVC}")
endif()
# 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_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)
@ -384,9 +384,6 @@ if (PLATFORM_LINUX OR CXX_CLANG)
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
# ===========================
@ -578,8 +575,8 @@ if (ARCHITECTURE_arm64 OR DYNARMIC_TESTS)
find_package(oaknut)
endif()
if (ENABLE_SDL2)
find_package(SDL2)
if (ENABLE_SDL3)
find_package(SDL3)
endif()
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-License-Identifier: GPL-2.0-or-later
function(copy_yuzu_SDL_deps target_dir)
include(WindowsCopyFiles)
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)

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-License-Identifier: GPL-3.0-or-later
@ -6,7 +8,7 @@ set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
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-)
# 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-License-Identifier: GPL-2.0-or-later
@ -9,7 +11,7 @@ set(CMAKE_HOST_WIN32 TRUE)
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-)
# Specify the cross compiler

View file

@ -87,6 +87,18 @@
"hash": "d1dece16f3b209109de02123c537bfe1adf07a62b16c166367e7e5d62e0f7c323bf804c89b3192dd6871bc58a9d879d25a1cc3f7b9da0e497cf266f165816e2a",
"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": {
"repo": "misc/llvm-mingw",
"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`).
- 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.
## 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:
* [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):
@ -90,7 +90,7 @@ Click on the arrows to expand.
<summary>Arch Linux</summary>
```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.
@ -117,7 +117,7 @@ sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel
```
* 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`
* [RPM Fusion](https://rpmfusion.org/) is required for `ffmpeg-devel`
* 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/)**
```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`.
@ -147,7 +147,7 @@ brew install molten-vk vulkan-loader
```
devel/cmake
devel/sdl20
devel/sdl30
devel/boost-libs
devel/catch2
devel/libfmt
@ -181,7 +181,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
```sh
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>
@ -195,7 +195,7 @@ Run the usual update + install of essential toolings: `sudo pkg update && sudo p
- **gcc**: `sudo pkg install developer/gcc-14`.
- **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>
@ -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`)
* 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:
* `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc`
* Add VulkanSDK to the PATH:

View file

@ -40,12 +40,12 @@ Notes:
* Unavailable on OpenBSD
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
- `YUZU_USE_EXTERNAL_SDL2` (ON for non-UNIX) Compiles SDL2 from source
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2
- `YUZU_USE_EXTERNAL_SDL3` (ON for non-UNIX) Compiles SDL3 from source
- `YUZU_USE_BUNDLED_SDL3` (ON for MSVC) Download a prebuilt SDL3
* 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_OPENGL` (ON) Enable the OpenGL graphics frontend
* 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_STANDALONE` (ON) Enable standalone room executable (eden-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"
* Currently only available on Windows and Linux

View file

@ -109,10 +109,10 @@ if(ENABLE_CUBEB)
endif()
endif()
# find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the YUZU_find_package
if (ENABLE_SDL2)
if (YUZU_USE_EXTERNAL_SDL2)
message(STATUS "Using SDL2 from externals.")
# find SDL3 exports a bunch of variables that are needed, so its easier to do this outside of the YUZU_find_package
if (ENABLE_SDL3)
if (YUZU_USE_EXTERNAL_SDL3)
message(STATUS "Using SDL3 from externals.")
if (NOT WIN32)
# 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)
@ -133,18 +133,16 @@ if (ENABLE_SDL2)
set(SDL_FILE ON)
endif()
if ("${YUZU_SYSTEM_PROFILE}" STREQUAL "steamdeck")
set(SDL_PIPEWIRE OFF) # build errors out with this on
AddJsonPackage("sdl2_steamdeck")
else()
AddJsonPackage("sdl2_generic")
endif()
elseif (YUZU_USE_BUNDLED_SDL2)
message(STATUS "Using bundled SDL2")
AddJsonPackage(sdl2)
AddJsonPackage(sdl3)
# annoying
target_include_directories(SDL3_Headers INTERFACE ${SDL3_SOURCE_DIR}/include/SDL3)
elseif (YUZU_USE_BUNDLED_SDL3)
message(STATUS "Using bundled SDL3")
AddJsonPackage(sdl3-ci)
endif()
find_package(SDL2 2.26.4 REQUIRED)
find_package(SDL3 3.2.12 REQUIRED)
endif()
# SPIRV Headers

View file

@ -146,13 +146,21 @@
"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,
"package": "SDL2",
"name": "SDL2",
"repo": "crueter-ci/SDL2",
"version": "2.32.10",
"min_version": "2.26.4",
"package": "SDL3",
"name": "SDL3",
"repo": "crueter-ci/SDL3",
"version": "3.2.24",
"min_version": "3.2.12",
"disabled_platforms": [
"macos-universal"
]
@ -179,24 +187,5 @@
"hash": "6c198636816a0018adbf7f735d402c64245c6fcd540b7360d4388d46f007f3a520686cdaec4705cb8cb31401b2cb4797a80b42ea5d08a6a5807c0848386f7ca1",
"find_args": "MODULE",
"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
@ -89,7 +91,7 @@ static char internal_gamemode_client_error_string[512] = { 0 };
/**
* Load libgamemode dynamically to dislodge us from most dependencies.
* 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.
*/
static volatile int internal_libgamemode_loaded = 1;

View file

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

View file

@ -77,7 +77,7 @@ android {
cmake {
arguments.addAll(listOf(
"-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_OPENSSL=ON",
"-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)
endif()
if (ENABLE_SDL2)
if (ENABLE_SDL3)
target_sources(audio_core PRIVATE
sink/sdl2_sink.cpp
sink/sdl2_sink.h
sink/sdl3_sink.cpp
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_compile_definitions(audio_core PRIVATE HAVE_SDL2)
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
if (TARGET SDL3::Headers)
target_link_libraries(audio_core PRIVATE SDL3::Headers)
endif()
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
endif()
if(ANDROID)

View file

@ -4,16 +4,16 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <span>
#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/sink/sdl2_sink.h"
#include "audio_core/sink/sdl3_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/core.h"
namespace AudioCore::Sink {
@ -39,37 +39,53 @@ public:
system_channels = system_channels_;
device_channels = device_channels_;
SDL_AudioSpec spec;
SDL_AudioSpec spec{};
spec.freq = TargetSampleRate;
spec.channels = static_cast<u8>(device_channels);
spec.format = AUDIO_S16SYS;
spec.samples = TargetSampleCount * 2;
spec.callback = &SDLSinkStream::DataCallback;
spec.userdata = this;
spec.channels = static_cast<int>(device_channels);
spec.format = SDL_AUDIO_S16;
SDL_AudioDeviceID device_id = 0;
std::string device_name{output_device};
bool capture{false};
bool is_capture{false};
if (type == StreamType::In) {
device_name = input_device;
capture = true;
is_capture = true;
}
SDL_AudioSpec obtained;
if (device_name.empty()) {
device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false);
} else {
device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false);
if (!device_name.empty()) {
int count = 0;
SDL_AudioDeviceID* devices = is_capture ? SDL_GetAudioRecordingDevices(&count)
: SDL_GetAudioPlaybackDevices(&count);
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) {
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
if (device_id == 0) {
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;
}
LOG_INFO(Service_Audio,
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
" samples {}",
device, obtained.freq, obtained.channels, system_channels, obtained.samples);
LOG_INFO(Service_Audio, "Opening SDL stream with: rate {} channels {} (system channels {})",
spec.freq, spec.channels, system_channels);
}
/**
@ -84,13 +100,14 @@ public:
* Finalize the sink stream.
*/
void Finalize() override {
if (device == 0) {
if (!stream) {
return;
}
Stop();
SDL_ClearQueuedAudio(device);
SDL_CloseAudioDevice(device);
SDL_ClearAudioStream(stream);
SDL_DestroyAudioStream(stream);
stream = nullptr;
}
/**
@ -100,62 +117,80 @@ public:
* Default false.
*/
void Start(bool resume = false) override {
if (device == 0 || !paused) {
if (!stream || !paused) {
return;
}
paused = false;
SDL_PauseAudioDevice(device, 0);
SDL_ResumeAudioStreamDevice(stream);
}
/**
* Stop the sink stream.
*/
void Stop() override {
if (device == 0 || paused) {
if (!stream || paused) {
return;
}
SignalPause();
SDL_PauseAudioDevice(device, 1);
SDL_PauseAudioStreamDevice(stream);
paused = true;
}
private:
/**
* Main callback from SDL. Either expects samples from us (audio render/audio out), or will
* 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) {
static void PlaybackCallback(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 num_frames{len / num_channels / sizeof(s16)};
const std::size_t num_frames = additional_amount / (sizeof(s16) * frame_size);
if (impl->type == StreamType::In) {
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
num_frames * frame_size};
impl->ProcessAudioIn(input_buffer, num_frames);
} else {
std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
if (num_frames == 0) {
return;
}
std::vector<s16> buffer(num_frames * frame_size);
impl->ProcessAudioOutAndRender(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_AudioDeviceID device{};
SDL_AudioStream* stream{nullptr};
};
SDLSink::SDLSink(std::string_view target_device_name) {
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());
return;
}
@ -218,66 +253,31 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
std::vector<std::string> device_list;
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());
return {};
}
}
const int device_count = SDL_GetNumAudioDevices(capture);
for (int i = 0; i < device_count; ++i) {
if (const char* name = SDL_GetAudioDeviceName(i, capture)) {
device_list.emplace_back(name);
int count = 0;
SDL_AudioDeviceID* devices =
capture ? SDL_GetAudioRecordingDevices(&count) : SDL_GetAudioPlaybackDevices(&count);
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;
}
/* REVERSION to 3833 - function GetSDLLatency() REINTRODUCED FROM 3833 - DIABLO 3 FIX */
u32 GetSDLLatency() {
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

View file

@ -16,8 +16,8 @@
#ifdef HAVE_CUBEB
#include "audio_core/sink/cubeb_sink.h"
#endif
#ifdef HAVE_SDL2
#include "audio_core/sink/sdl2_sink.h"
#ifdef HAVE_SDL3
#include "audio_core/sink/sdl3_sink.h"
#endif
#include "audio_core/sink/null_sink.h"
#include "common/logging/log.h"
@ -71,9 +71,9 @@ constexpr SinkDetails sink_details[] = {
&GetCubebLatency,
},
#endif
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
SinkDetails{
Settings::AudioEngine::Sdl2,
Settings::AudioEngine::Sdl3,
[](std::string_view device_id) -> std::unique_ptr<Sink> {
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
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
// 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);
if (iter->latency() > TargetSampleCount * 3) {
iter = find_backend(Settings::AudioEngine::Sdl2);
iter = find_backend(Settings::AudioEngine::Sdl3);
}
#else
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
// canonicalizations
// 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<>
inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
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},
};
}

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-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) {
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));
void(FS::CreateParentDir(config_loc));
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-License-Identifier: GPL-2.0-or-later
@ -47,7 +49,7 @@ else()
)
endif()
if (ENABLE_SDL2)
if (ENABLE_SDL3)
target_sources(input_common PRIVATE
drivers/joycon.cpp
drivers/joycon.h
@ -73,8 +75,8 @@ if (ENABLE_SDL2)
helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h
)
target_link_libraries(input_common PRIVATE SDL2::SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
target_link_libraries(input_common PRIVATE SDL3::SDL3)
target_compile_definitions(input_common PRIVATE HAVE_SDL3)
endif()
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-License-Identifier: GPL-2.0-or-later
@ -6,7 +8,7 @@
#include <array>
#include <span>
#include <thread>
#include <SDL_hidapi.h>
#include <SDL3/SDL_hidapi.h>
#include "input_common/input_engine.h"

View file

@ -8,55 +8,50 @@
#include "common/param_package.h"
#include "common/settings.h"
#include "common/thread.h"
#include "common/vector_math.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 {
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::memcpy(data.data(), guid.data, sizeof(data));
// Clear controller name crc
std::memset(data.data() + 2, 0, sizeof(u16));
return Common::UUID{data};
}
} // 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);
sdl_state->HandleGameControllerEvent(*event);
return 0;
return false;
}
class SDLJoystick {
public:
SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick,
SDL_GameController* game_controller)
: guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
sdl_controller{game_controller, &SDL_GameControllerClose} {
SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, SDL_Gamepad* gamepad)
: guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_CloseJoystick},
sdl_gamepad{gamepad, &SDL_CloseGamepad} {
EnableMotion();
}
void EnableMotion() {
if (!sdl_controller) {
if (!sdl_gamepad) {
return;
}
SDL_GameController* controller = sdl_controller.get();
SDL_Gamepad* gamepad = sdl_gamepad.get();
if (HasMotion()) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE);
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE);
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, false);
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, false);
}
has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;
has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
if (has_accel) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, true);
}
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;
}
bool UpdateMotion(SDL_ControllerSensorEvent event) {
bool UpdateMotion(SDL_GamepadSensorEvent event) {
constexpr float gravity_constant = 9.80665f;
std::scoped_lock lock{mutex};
const u64 time_difference = event.timestamp - last_motion_update;
@ -84,25 +79,22 @@ public:
}
}
// Ignore duplicated timestamps
if (time_difference == 0) {
return false;
}
// Motion data is invalid
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) {
if (motion_error_count++ < 200) {
return false;
}
// Try restarting the sensor
motion_error_count = 0;
EnableMotion();
return false;
}
motion_error_count = 0;
motion.delta_timestamp = time_difference * 1000;
motion.delta_timestamp = time_difference;
return true;
}
@ -116,13 +108,13 @@ public:
constexpr f32 low_width_sensitivity_limit = 400.0f;
constexpr f32 high_start_sensitivity_limit = 200.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;
if (vibration.low_frequency > low_start_sensitivity_limit) {
low_frequency_scale =
(std::max)(1.0f - (vibration.low_frequency - low_start_sensitivity_limit) /
low_width_sensitivity_limit,
0.3f);
low_width_sensitivity_limit,
0.3f);
}
f32 low_amplitude = vibration.low_amplitude * low_frequency_scale;
@ -130,31 +122,29 @@ public:
if (vibration.high_frequency > high_start_sensitivity_limit) {
high_frequency_scale =
(std::max)(1.0f - (vibration.high_frequency - high_start_sensitivity_limit) /
high_width_sensitivity_limit,
0.3f);
high_width_sensitivity_limit,
0.3f);
}
f32 high_amplitude = vibration.high_amplitude * high_frequency_scale;
if (sdl_controller) {
return SDL_GameControllerRumble(sdl_controller.get(), static_cast<u16>(low_amplitude),
static_cast<u16>(high_amplitude),
rumble_max_duration_ms) != -1;
if (sdl_gamepad) {
return SDL_RumbleGamepad(sdl_gamepad.get(), static_cast<u16>(low_amplitude),
static_cast<u16>(high_amplitude), rumble_max_duration_ms);
} else if (sdl_joystick) {
return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(low_amplitude),
static_cast<u16>(high_amplitude),
rumble_max_duration_ms) != -1;
return SDL_RumbleJoystick(sdl_joystick.get(), static_cast<u16>(low_amplitude),
static_cast<u16>(high_amplitude), rumble_max_duration_ms);
}
return false;
}
bool HasHDRumble() const {
if (sdl_controller) {
const auto type = SDL_GameControllerGetType(sdl_controller.get());
return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) ||
(type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) ||
(type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) ||
(type == SDL_CONTROLLER_TYPE_PS5);
if (sdl_gamepad) {
const auto type = SDL_GetGamepadType(sdl_gamepad.get());
return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) ||
(type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) ||
(type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) ||
(type == SDL_GAMEPAD_TYPE_PS5);
}
return false;
}
@ -172,9 +162,6 @@ public:
return is_vibration_tested;
}
/**
* The Pad identifier of the joystick
*/
const PadIdentifier GetPadIdentifier() const {
return {
.guid = guid,
@ -183,16 +170,10 @@ public:
};
}
/**
* The guid of the joystick
*/
const Common::UUID& GetGUID() const {
return guid;
}
/**
* The number of joystick from the same type that were connected before this joystick
*/
int GetPort() const {
return port;
}
@ -201,13 +182,13 @@ public:
return sdl_joystick.get();
}
SDL_GameController* GetSDLGameController() const {
return sdl_controller.get();
SDL_Gamepad* GetSDLGamepad() const {
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_controller.reset(controller);
sdl_gamepad.reset(gamepad);
}
bool IsJoyconLeft() const {
@ -232,49 +213,48 @@ public:
return false;
}
Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) {
Common::Input::BatteryLevel GetBatteryLevel(SDL_PowerState battery_level) {
switch (battery_level) {
case SDL_JOYSTICK_POWER_EMPTY:
return Common::Input::BatteryLevel::Empty;
case SDL_JOYSTICK_POWER_LOW:
case SDL_POWERSTATE_ERROR:
case SDL_POWERSTATE_UNKNOWN:
return Common::Input::BatteryLevel::None;
case SDL_POWERSTATE_ON_BATTERY:
return Common::Input::BatteryLevel::Low;
case SDL_JOYSTICK_POWER_MEDIUM:
return Common::Input::BatteryLevel::Medium;
case SDL_JOYSTICK_POWER_FULL:
case SDL_JOYSTICK_POWER_MAX:
return Common::Input::BatteryLevel::Full;
case SDL_JOYSTICK_POWER_WIRED:
case SDL_POWERSTATE_NO_BATTERY:
return Common::Input::BatteryLevel::None;
case SDL_POWERSTATE_CHARGING:
return Common::Input::BatteryLevel::Charging;
case SDL_JOYSTICK_POWER_UNKNOWN:
case SDL_POWERSTATE_CHARGED:
return Common::Input::BatteryLevel::Full;
default:
return Common::Input::BatteryLevel::None;
}
}
std::string GetControllerName() const {
if (sdl_controller) {
switch (SDL_GameControllerGetType(sdl_controller.get())) {
case SDL_CONTROLLER_TYPE_XBOX360:
if (sdl_gamepad) {
switch (SDL_GetGamepadType(sdl_gamepad.get())) {
case SDL_GAMEPAD_TYPE_XBOX360:
return "Xbox 360 Controller";
case SDL_CONTROLLER_TYPE_XBOXONE:
case SDL_GAMEPAD_TYPE_XBOXONE:
return "Xbox One Controller";
case SDL_CONTROLLER_TYPE_PS3:
case SDL_GAMEPAD_TYPE_PS3:
return "DualShock 3 Controller";
case SDL_CONTROLLER_TYPE_PS4:
case SDL_GAMEPAD_TYPE_PS4:
return "DualShock 4 Controller";
case SDL_CONTROLLER_TYPE_PS5:
case SDL_GAMEPAD_TYPE_PS5:
return "DualSense Controller";
default:
break;
}
const auto name = SDL_GameControllerName(sdl_controller.get());
const auto name = SDL_GetGamepadName(sdl_gamepad.get());
if (name) {
return name;
}
}
if (sdl_joystick) {
const auto name = SDL_JoystickName(sdl_joystick.get());
const auto name = SDL_GetJoystickName(sdl_joystick.get());
if (name) {
return name;
}
@ -286,8 +266,8 @@ public:
private:
Common::UUID guid;
int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
std::unique_ptr<SDL_Joystick, decltype(&SDL_CloseJoystick)> sdl_joystick;
std::unique_ptr<SDL_Gamepad, decltype(&SDL_CloseGamepad)> sdl_gamepad;
mutable std::mutex mutex;
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) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
auto sdl_joystick = SDL_GetJoystickFromID(sdl_id);
const auto guid = GetGUID(sdl_joystick);
std::scoped_lock lock{joystick_map_mutex};
@ -345,16 +325,16 @@ std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl
return *vec_it;
}
void SDLDriver::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
SDL_GameController* sdl_gamecontroller = nullptr;
void SDLDriver::InitJoystick(SDL_JoystickID joystick_id) {
SDL_Joystick* sdl_joystick = SDL_OpenJoystick(joystick_id);
SDL_Gamepad* sdl_gamepad = nullptr;
if (SDL_IsGameController(joystick_index)) {
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
if (SDL_IsGamepad(joystick_id)) {
sdl_gamepad = SDL_OpenGamepad(joystick_id);
}
if (!sdl_joystick) {
LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
LOG_ERROR(Input, "Failed to open joystick {}", joystick_id);
return;
}
@ -363,23 +343,23 @@ void SDLDriver::InitJoystick(int joystick_index) {
if (Settings::values.enable_joycon_driver) {
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
(guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
SDL_JoystickClose(sdl_joystick);
LOG_WARNING(Input, "Preferring joycon driver for device ID {}", joystick_id);
SDL_CloseJoystick(sdl_joystick);
return;
}
}
if (Settings::values.enable_procon_driver) {
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) {
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
SDL_JoystickClose(sdl_joystick);
LOG_WARNING(Input, "Preferring joycon driver for device ID {}", joystick_id);
SDL_CloseJoystick(sdl_joystick);
return;
}
}
std::scoped_lock lock{joystick_map_mutex};
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());
joystick->EnableMotion();
joystick_map[guid].emplace_back(std::move(joystick));
@ -392,13 +372,13 @@ void SDLDriver::InitJoystick(int joystick_index) {
[](const auto& joystick) { return !joystick->GetSDLJoystick(); });
if (joystick_it != joystick_guid_list.end()) {
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamepad);
(*joystick_it)->EnableMotion();
return;
}
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());
joystick->EnableMotion();
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);
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_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
[&sdl_joystick](const auto& joystick) {
@ -428,56 +407,85 @@ void SDLDriver::PumpEvents() const {
void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
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)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, false);
}
break;
}
case SDL_JOYBUTTONDOWN: {
case SDL_EVENT_JOYSTICK_BUTTON_DOWN: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, true);
}
break;
}
case SDL_JOYHATMOTION: {
case SDL_EVENT_JOYSTICK_HAT_MOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetHatButton(identifier, event.jhat.hat, event.jhat.value);
}
break;
}
case SDL_JOYAXISMOTION: {
case SDL_EVENT_JOYSTICK_AXIS_MOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
}
break;
}
case SDL_CONTROLLERSENSORUPDATE: {
if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
if (joystick->UpdateMotion(event.csensor)) {
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
if (auto joystick = GetSDLJoystickBySDLID(event.gsensor.which)) {
if (joystick->UpdateMotion(event.gsensor)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetMotion(identifier, 0, joystick->GetMotion());
}
}
break;
}
case SDL_JOYBATTERYUPDATED: {
case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: {
if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level));
SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.state));
}
break;
}
case SDL_JOYDEVICEREMOVED:
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
case SDL_EVENT_GAMEPAD_REMOVED:
LOG_DEBUG(Input, "Gamepad removed with Instance_ID {}", event.gdevice.which);
CloseJoystick(SDL_GetJoystickFromID(event.gdevice.which));
break;
case SDL_JOYDEVICEADDED:
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
case SDL_EVENT_GAMEPAD_ADDED:
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);
break;
}
@ -489,24 +497,17 @@ void SDLDriver::CloseJoysticks() {
}
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");
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");
}
// Prevent SDL from adding undesired axis
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
// 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_HINT_ACCELEROMETER_AS_JOYSTICK was removed in SDL3
// 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
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) {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
} 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");
}
// Disable hidapi drivers for pro controllers when the custom joycon driver is enabled
if (Settings::values.enable_procon_driver) {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
} 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");
// Share the same button mapping with non-Nintendo controllers
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_HINT_GAMECONTROLLER_USE_BUTTON_LABELS was removed in SDL3
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_GAMECONTROLLER) == 0;
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD);
if (start_thread && !SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
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
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
InitJoystick(i);
int num_joysticks;
SDL_JoystickID* joysticks = SDL_GetJoysticks(&num_joysticks);
if (joysticks) {
for (int i = 0; i < num_joysticks; ++i) {
InitJoystick(joysticks[i]);
}
SDL_free(joysticks);
}
}
SDLDriver::~SDLDriver() {
CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this);
SDL_RemoveEventWatch(&SDLEventWatcher, this);
initialized = false;
if (start_thread) {
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& joystick : value) {
if (joystick->IsJoyconRight()) {
@ -624,15 +622,12 @@ Common::Input::DriverResult SDLDriver::SetVibration(
return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
};
// Default exponential curve for rumble
f32 factor = 0.35f;
// If vibration is set as a linear output use a flatter value
if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
factor = 0.5f;
}
// Amplitude for HD rumble needs no modification
if (joystick->HasHDRumble()) {
factor = 1.0f;
}
@ -677,10 +672,7 @@ bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
return joystick->HasVibration();
}
// First vibration might fail
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));
if (!joystick->RumblePlay(zero_vibration)) {
@ -760,17 +752,17 @@ Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& g
}
Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const {
switch (binding.bindType) {
case SDL_CONTROLLER_BINDTYPE_NONE:
int port, const Common::UUID& guid, const SDL_GamepadBinding& binding) const {
switch (binding.input_type) {
case SDL_GAMEPAD_BINDTYPE_NONE:
break;
case SDL_CONTROLLER_BINDTYPE_AXIS:
return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
case SDL_CONTROLLER_BINDTYPE_BUTTON:
return BuildButtonParamPackageForButton(port, guid, binding.value.button);
case SDL_CONTROLLER_BINDTYPE_HAT:
return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
static_cast<u8>(binding.value.hat.hat_mask));
case SDL_GAMEPAD_BINDTYPE_AXIS:
return BuildAnalogParamPackageForButton(port, guid, binding.input.axis.axis);
case SDL_GAMEPAD_BINDTYPE_BUTTON:
return BuildButtonParamPackageForButton(port, guid, binding.input.button);
case SDL_GAMEPAD_BINDTYPE_HAT:
return BuildHatParamPackageForButton(port, guid, binding.input.hat.hat,
static_cast<u8>(binding.input.hat.hat_mask));
}
return {};
}
@ -797,28 +789,23 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
}
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
auto* gamepad = joystick->GetSDLGamepad();
if (gamepad == nullptr) {
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;
switch_to_sdl_button = GetDefaultButtonBinding(joystick);
// Add the missing bindings for ZL/ZR
static constexpr ZButtonBindings switch_to_sdl_axis{{
{Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
{Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
{Settings::NativeButton::ZL, SDL_GAMEPAD_AXIS_LEFT_TRIGGER},
{Settings::NativeButton::ZR, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER},
}};
// Parameters contain two joysticks return dual
if (params.Has("guid2")) {
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,
switch_to_sdl_axis);
}
@ -829,42 +816,41 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
ButtonBindings SDLDriver::GetDefaultButtonBinding(
const std::shared_ptr<SDLJoystick>& joystick) const {
// Default SL/SR mapping for other controllers
auto sll_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
auto srl_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
auto slr_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
auto srr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
auto sll_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
auto srl_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
auto slr_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
auto srr_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
if (joystick->IsJoyconLeft()) {
sll_button = SDL_CONTROLLER_BUTTON_PADDLE2;
srl_button = SDL_CONTROLLER_BUTTON_PADDLE4;
sll_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE1;
srl_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE2;
}
if (joystick->IsJoyconRight()) {
slr_button = SDL_CONTROLLER_BUTTON_PADDLE3;
srr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
slr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2;
srr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1;
}
return {
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
std::pair{Settings::NativeButton::A, SDL_GAMEPAD_BUTTON_EAST},
{Settings::NativeButton::B, SDL_GAMEPAD_BUTTON_SOUTH},
{Settings::NativeButton::X, SDL_GAMEPAD_BUTTON_NORTH},
{Settings::NativeButton::Y, SDL_GAMEPAD_BUTTON_WEST},
{Settings::NativeButton::LStick, SDL_GAMEPAD_BUTTON_LEFT_STICK},
{Settings::NativeButton::RStick, SDL_GAMEPAD_BUTTON_RIGHT_STICK},
{Settings::NativeButton::L, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
{Settings::NativeButton::R, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
{Settings::NativeButton::Plus, SDL_GAMEPAD_BUTTON_START},
{Settings::NativeButton::Minus, SDL_GAMEPAD_BUTTON_BACK},
{Settings::NativeButton::DLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT},
{Settings::NativeButton::DUp, SDL_GAMEPAD_BUTTON_DPAD_UP},
{Settings::NativeButton::DRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
{Settings::NativeButton::DDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN},
{Settings::NativeButton::SLLeft, sll_button},
{Settings::NativeButton::SRLeft, srl_button},
{Settings::NativeButton::SLRight, slr_button},
{Settings::NativeButton::SRRight, srr_button},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
{Settings::NativeButton::Home, SDL_GAMEPAD_BUTTON_GUIDE},
{Settings::NativeButton::Screenshot, SDL_GAMEPAD_BUTTON_MISC1},
};
}
@ -873,16 +859,25 @@ ButtonMapping SDLDriver::GetSingleControllerMapping(
const ZButtonBindings& switch_to_sdl_axis) const {
ButtonMapping mapping;
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) {
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(
switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
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(
switch_button,
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 {
ButtonMapping mapping;
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) {
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(
switch_button,
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
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(
switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
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(
switch_button,
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
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(
switch_button,
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 joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
auto* gamepad = joystick->GetSDLGamepad();
if (gamepad == nullptr) {
return {};
}
AnalogMapping mapping = {};
const auto& binding_left_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
const auto& binding_left_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
// SDL_GetGamepadBindForAxis was removed in SDL3
// We need to work with the underlying joystick directly
SDL_Joystick* sdl_joystick = SDL_GetGamepadJoystick(gamepad);
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")) {
const auto identifier = joystick2->GetPadIdentifier();
PreSetController(identifier);
PreSetAxis(identifier, binding_left_x.value.axis);
PreSetAxis(identifier, binding_left_y.value.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis);
PreSetAxis(identifier, binding_left_x.input.axis.axis);
PreSetAxis(identifier, binding_left_y.input.axis.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis);
const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis);
mapping.insert_or_assign(Settings::NativeAnalog::LStick,
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
binding_left_y.value.axis,
left_offset_x, left_offset_y));
BuildParamPackageForAnalog(
identifier, binding_left_x.input.axis.axis,
binding_left_y.input.axis.axis, left_offset_x, left_offset_y));
} else {
const auto identifier = joystick->GetPadIdentifier();
PreSetController(identifier);
PreSetAxis(identifier, binding_left_x.value.axis);
PreSetAxis(identifier, binding_left_y.value.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis);
PreSetAxis(identifier, binding_left_x.input.axis.axis);
PreSetAxis(identifier, binding_left_y.input.axis.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis);
const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis);
mapping.insert_or_assign(Settings::NativeAnalog::LStick,
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
binding_left_y.value.axis,
left_offset_x, left_offset_y));
BuildParamPackageForAnalog(
identifier, binding_left_x.input.axis.axis,
binding_left_y.input.axis.axis, left_offset_x, left_offset_y));
}
const auto& binding_right_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
const auto& binding_right_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
// For right stick
SDL_GamepadBinding binding_right_x{};
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();
PreSetController(identifier);
PreSetAxis(identifier, binding_right_x.value.axis);
PreSetAxis(identifier, binding_right_y.value.axis);
const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
const auto right_offset_y = GetAxis(identifier, binding_right_y.value.axis);
PreSetAxis(identifier, binding_right_x.input.axis.axis);
PreSetAxis(identifier, binding_right_y.input.axis.axis);
const auto right_offset_x = -GetAxis(identifier, binding_right_x.input.axis.axis);
const auto right_offset_y = GetAxis(identifier, binding_right_y.input.axis.axis);
mapping.insert_or_assign(Settings::NativeAnalog::RStick,
BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
binding_right_y.value.axis, right_offset_x,
right_offset_y));
BuildParamPackageForAnalog(identifier, binding_right_x.input.axis.axis,
binding_right_y.input.axis.axis,
right_offset_x, right_offset_y));
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 joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
auto* gamepad = joystick->GetSDLGamepad();
if (gamepad == nullptr) {
return {};
}
@ -1039,7 +1061,6 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p
Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
// TODO(German77): Find how to substitute the values for real button names
return Common::Input::ButtonNames::Value;
}
if (params.Has("hat")) {
@ -1097,29 +1118,27 @@ bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) {
if (joystick == nullptr) {
return false;
}
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
auto* gamepad = joystick->GetSDLGamepad();
if (gamepad == nullptr) {
return false;
}
const auto& axis_x = params.Get("axis_x", 0);
const auto& axis_y = params.Get("axis_y", 0);
const auto& binding_left_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
const auto& binding_right_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
const auto& binding_left_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
const auto& binding_right_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
// SDL_GetGamepadBindForAxis was removed in SDL3
// Use hardcoded axis mappings for now
const int binding_left_x = 0; // Left stick X
const int binding_right_x = 2; // Right stick X
const int binding_left_y = 1; // Left stick Y
const int binding_right_y = 3; // Right stick Y
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;
}
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 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-License-Identifier: GPL-2.0-or-later
@ -8,46 +10,33 @@
#include <thread>
#include <unordered_map>
#include <SDL.h>
#include <SDL3/SDL.h>
#include "common/common_types.h"
#include "common/threadsafe_queue.h"
#include "input_common/input_engine.h"
union SDL_Event;
using SDL_GameController = struct _SDL_GameController;
using SDL_Joystick = struct _SDL_Joystick;
using SDL_JoystickID = s32;
using SDL_Gamepad = struct SDL_Gamepad;
using SDL_Joystick = struct SDL_Joystick;
using SDL_JoystickID = Uint32;
namespace InputCommon {
class SDLJoystick;
using ButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 20>;
using ZButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
using ButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadButton>, 20>;
using ZButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadAxis>, 2>;
class SDLDriver : public InputEngine {
public:
/// Initializes and registers SDL device factories
explicit SDLDriver(std::string input_engine_);
/// Unregisters SDL device factories and shut them down.
~SDLDriver() override;
void PumpEvents() const;
/// Handle SDL_Events for joysticks from SDL_PollEvent
void HandleGameControllerEvent(const SDL_Event& event);
/// Get the nth joystick with the corresponding GUID
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 std::string& guid, int port);
@ -69,59 +58,40 @@ public:
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
private:
void InitJoystick(int joystick_index);
void InitJoystick(SDL_JoystickID joystick_id);
void CloseJoystick(SDL_Joystick* sdl_joystick);
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
/// Takes all vibrations from the queue and sends the command to the controller
void SendVibrations();
Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid,
s32 axis, float value = 0.1f) const;
Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid,
s32 button) const;
Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat,
u8 value) const;
Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const;
Common::ParamPackage BuildParamPackageForBinding(
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const;
Common::ParamPackage BuildParamPackageForBinding(int port, const Common::UUID& guid,
const SDL_GamepadBinding& binding) const;
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
int axis_y, float offset_x,
float offset_y) const;
/// Returns the default button bindings list
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,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const;
/// Returns the button mappings from two different controllers
ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
const std::shared_ptr<SDLJoystick>& joystick2,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const;
/// Returns true if the button is on the left joycon
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
/// Queue of vibration request to controllers
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::mutex joystick_map_mutex;
bool start_thread = false;
std::atomic<bool> initialized = false;
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-License-Identifier: GPL-2.0-or-later
@ -10,7 +12,7 @@
#include <array>
#include <functional>
#include <SDL_hidapi.h>
#include <SDL3/SDL_hidapi.h>
#include "common/bit_field.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-License-Identifier: GPL-2.0-or-later
@ -22,7 +24,7 @@
#ifdef HAVE_LIBUSB
#include "input_common/drivers/gc_adapter.h"
#endif
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
#include "input_common/drivers/joycon.h"
#include "input_common/drivers/sdl_driver.h"
#endif
@ -87,7 +89,7 @@ struct InputSubsystem::Impl {
#endif
RegisterEngine("virtual_amiibo", virtual_amiibo);
RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
RegisterEngine("sdl", sdl);
RegisterEngine("joycon", joycon);
#endif
@ -121,7 +123,7 @@ struct InputSubsystem::Impl {
#endif
UnregisterEngine(virtual_amiibo);
UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
UnregisterEngine(sdl);
UnregisterEngine(joycon);
#endif
@ -151,7 +153,7 @@ struct InputSubsystem::Impl {
#endif
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
auto sdl_devices = sdl->GetInputDevices();
@ -186,7 +188,7 @@ struct InputSubsystem::Impl {
if (engine == udp_client->GetEngineName()) {
return udp_client;
}
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) {
return sdl;
}
@ -277,7 +279,7 @@ struct InputSubsystem::Impl {
if (engine == virtual_gamepad->GetEngineName()) {
return true;
}
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) {
return true;
}
@ -298,7 +300,7 @@ struct InputSubsystem::Impl {
gcadapter->BeginConfiguration();
#endif
udp_client->BeginConfiguration();
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
sdl->BeginConfiguration();
joycon->BeginConfiguration();
#endif
@ -314,7 +316,7 @@ struct InputSubsystem::Impl {
gcadapter->EndConfiguration();
#endif
udp_client->EndConfiguration();
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
sdl->EndConfiguration();
joycon->EndConfiguration();
#endif
@ -322,7 +324,7 @@ struct InputSubsystem::Impl {
void PumpEvents() const {
update_engine->PumpEvents();
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
sdl->PumpEvents();
#endif
}
@ -347,7 +349,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<GCAdapter> gcadapter;
#endif
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<Joycons> joycon;
#endif

View file

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

View file

@ -98,8 +98,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QCheckBox>
#include <QStringLiteral>
#ifdef HAVE_SDL2
#include <SDL.h> // For SDL ScreenSaver functions
#ifdef HAVE_SDL3
#include <SDL3/SDL.h> // For SDL ScreenSaver functions
#endif
#include <fmt/ranges.h>
@ -588,7 +588,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
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);
// 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() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#elif defined(HAVE_SDL2)
#elif defined(HAVE_SDL3)
SDL_DisableScreenSaver();
#endif
}
@ -1876,7 +1876,7 @@ void GMainWindow::PreventOSSleep() {
void GMainWindow::AllowOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(HAVE_SDL2)
#elif defined(HAVE_SDL3)
SDL_EnableScreenSaver();
#endif
}

View file

@ -16,14 +16,14 @@ function(create_resource file output filename)
endfunction()
add_executable(yuzu-cmd
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl2_null.cpp
emu_window/emu_window_sdl2_null.h
emu_window/emu_window_sdl2_vk.cpp
emu_window/emu_window_sdl2_vk.h
emu_window/emu_window_sdl3.cpp
emu_window/emu_window_sdl3.h
emu_window/emu_window_sdl3_gl.cpp
emu_window/emu_window_sdl3_gl.h
emu_window/emu_window_sdl3_null.cpp
emu_window/emu_window_sdl3_null.h
emu_window/emu_window_sdl3_vk.cpp
emu_window/emu_window_sdl3_vk.h
precompiled_headers.h
sdl_config.cpp
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")
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)
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-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/scm_rev.h"
#include "common/settings.h"
@ -15,26 +17,26 @@
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/touch_screen.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"
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->Initialize();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}, Exiting...", SDL_GetError());
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError());
exit(1);
}
SDL_SetMainReady();
}
EmuWindow_SDL2::~EmuWindow_SDL2() {
EmuWindow_SDL3::~EmuWindow_SDL3() {
system.HIDCore().UnloadInputDevices();
input_subsystem->Shutdown();
SDL_Quit();
}
InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const {
InputCommon::MouseButton EmuWindow_SDL3::SDLButtonToMouseButton(u32 button) const {
switch (button) {
case SDL_BUTTON_LEFT:
return InputCommon::MouseButton::Left;
@ -52,21 +54,17 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons
}
/// @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;
SDL_GetWindowSize(render_window, &w, &h);
const float fx = float(touch_x) / w;
const float fy = float(touch_y) / h;
return {
std::clamp<float>(fx, 0.0f, 1.0f),
std::clamp<float>(fy, 0.0f, 1.0f),
0
};
return {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);
if (state == SDL_PRESSED) {
if (pressed) {
auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y);
input_subsystem->GetMouse()->PressButton(x, y, 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);
input_subsystem->GetMouse()->Move(x, y, 0, 0);
input_subsystem->GetMouse()->MouseMove(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);
}
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);
}
void EmuWindow_SDL2::OnFingerUp() {
void EmuWindow_SDL3::OnFingerUp() {
input_subsystem->GetTouchScreen()->ReleaseAllTouch();
}
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
void EmuWindow_SDL3::OnKeyEvent(int key, bool pressed) {
if (pressed) {
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));
}
}
bool EmuWindow_SDL2::IsOpen() const {
bool EmuWindow_SDL3::IsOpen() const {
return is_open;
}
bool EmuWindow_SDL2::IsShown() const {
bool EmuWindow_SDL3::IsShown() const {
return is_shown;
}
void EmuWindow_SDL2::OnResize() {
void EmuWindow_SDL3::OnResize() {
int width, height;
SDL_GL_GetDrawableSize(render_window, &width, &height);
SDL_GetWindowSizeInPixels(render_window, &width, &height);
UpdateCurrentFramebufferLayout(width, height);
}
void EmuWindow_SDL2::ShowCursor(bool show_cursor) {
SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE);
void EmuWindow_SDL3::ShowCursor(bool show_cursor) {
show_cursor ? SDL_ShowCursor() : SDL_HideCursor();
}
void EmuWindow_SDL2::Fullscreen() {
SDL_DisplayMode display_mode;
void EmuWindow_SDL3::Fullscreen() {
const SDL_DisplayMode* display_mode;
switch (Settings::values.fullscreen_mode.GetValue()) {
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.
if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) {
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
display_mode = SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window));
if (display_mode) {
SDL_SetWindowSize(render_window, display_mode->w, display_mode->h);
} else {
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;
}
@ -141,12 +140,13 @@ void EmuWindow_SDL2::Fullscreen() {
LOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
[[fallthrough]];
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;
}
LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
[[fallthrough]];
default:
// Fallback algorithm: Maximise window.
// 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
SDL_Event event;
if (!SDL_WaitEvent(&event)) {
if (SDL_WaitEvent(&event)) {
// Event received successfully
} else {
const char* error = SDL_GetError();
if (!error || strcmp(error, "") == 0) {
// https://github.com/libsdl-org/SDL/issues/5780
@ -174,52 +176,49 @@ void EmuWindow_SDL2::WaitEvent() {
}
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
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;
}
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
OnResize();
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_EXPOSED:
is_shown = event.type == SDL_EVENT_WINDOW_EXPOSED;
OnResize();
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
if (event.button.which != SDL_TOUCH_MOUSEID)
OnMouseMotion(event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
// ignore if it came from touch
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;
case SDL_FINGERDOWN:
case SDL_EVENT_FINGER_DOWN:
OnFingerDown(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchId));
static_cast<std::size_t>(event.tfinger.touchID));
break;
case SDL_FINGERMOTION:
case SDL_EVENT_FINGER_MOTION:
OnFingerMotion(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchId));
static_cast<std::size_t>(event.tfinger.touchID));
break;
case SDL_FINGERUP:
case SDL_EVENT_FINGER_UP:
OnFingerUp();
break;
case SDL_QUIT:
case SDL_EVENT_QUIT:
is_open = false;
break;
default:
@ -229,34 +228,31 @@ void EmuWindow_SDL2::WaitEvent() {
const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = system.GetAndResetPerfStats();
const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)",
Common::g_build_fullname,
Common::g_scm_branch,
Common::g_scm_desc,
results.average_game_fps,
results.emulation_speed * 100.0);
const auto title = fmt::format(
"{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0);
SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time;
}
}
// Credits to Samantas5855 and others for this function.
void EmuWindow_SDL2::SetWindowIcon() {
SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size);
void EmuWindow_SDL3::SetWindowIcon() {
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
if (yuzu_icon_stream == nullptr) {
LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
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) {
LOG_WARNING(Frontend, "Failed to read BMP from stream.");
return;
}
// The icon is attached to the window pointer
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);
}

View file

@ -22,10 +22,10 @@ class InputSubsystem;
enum class MouseButton;
} // namespace InputCommon
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
class EmuWindow_SDL3 : public Core::Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_);
~EmuWindow_SDL2();
explicit EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_);
~EmuWindow_SDL3();
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
@ -41,7 +41,7 @@ public:
protected:
/// 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
InputCommon::MouseButton SDLButtonToMouseButton(u32 button) const;
@ -53,7 +53,7 @@ protected:
FloatPairNonHFA MouseToTouchPos(s32 touch_x, s32 touch_y) const;
/// 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.
void OnMouseMotion(s32 x, s32 y);
@ -85,7 +85,7 @@ protected:
/// Is the window being shown?
bool is_shown = true;
/// Internal SDL2 render window
/// Internal SDL3 render window
SDL_Window* render_window{};
/// 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-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstdlib>
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL3/SDL.h>
#include <fmt/ranges.h>
#include <glad/glad.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h"
#include "input_common/main.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 {
public:
@ -30,7 +28,7 @@ public:
~SDLGLContext() {
DoneCurrent();
SDL_GL_DeleteContext(context);
SDL_GL_DestroyContext(context);
}
void SwapBuffers() override {
@ -58,7 +56,7 @@ private:
bool is_current = false;
};
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() {
std::vector<std::string_view> unsupported_ext;
// Extensions required to support some texture formats.
@ -76,9 +74,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
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)
: 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_MINOR_VERSION, 6);
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,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
render_window = SDL_CreateWindow(
window_title.c_str(), Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
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);
}
@ -120,15 +115,17 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
core_context = CreateSharedContext();
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);
}
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);
}
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());
exit(1);
}
@ -146,11 +143,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
Settings::LogSettings();
}
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() {
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);
}

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-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <SDL3/SDL.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 {
class System;
@ -15,11 +18,11 @@ namespace InputCommon {
class InputSubsystem;
}
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
class EmuWindow_SDL3_GL final : public EmuWindow_SDL3 {
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);
~EmuWindow_SDL2_GL();
~EmuWindow_SDL3_GL();
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
@ -27,8 +30,6 @@ private:
/// Whether the GPU and driver supports the OpenGL extension required
bool SupportsRequiredGLExtensions();
using SDL_GLContext = void*;
/// The OpenGL context associated with the window
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-License-Identifier: GPL-2.0-or-later
@ -10,25 +12,24 @@
#include "common/logging/log.h"
#include "common/scm_rev.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
#define USING_GENERATED_CONFIG_H
#include <SDL_config.h>
#include <SDL3/SDL_config.h>
#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)
: EmuWindow_SDL2{input_subsystem_, system_} {
: 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(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
render_window = SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
SetWindowIcon();
@ -44,8 +45,8 @@ EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subs
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>();
}

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-License-Identifier: GPL-2.0-or-later
@ -6,7 +8,7 @@
#include <memory>
#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 {
class System;
@ -16,11 +18,11 @@ namespace InputCommon {
class InputSubsystem;
}
class EmuWindow_SDL2_Null final : public EmuWindow_SDL2 {
class EmuWindow_SDL3_Null final : public EmuWindow_SDL3 {
public:
explicit EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_,
explicit EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system, bool fullscreen);
~EmuWindow_SDL2_Null() override;
~EmuWindow_SDL3_Null() 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-License-Identifier: GPL-2.0-or-later
@ -6,7 +8,7 @@
#include <memory>
#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 {
class System;
@ -16,11 +18,11 @@ namespace InputCommon {
class InputSubsystem;
}
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
class EmuWindow_SDL3_VK final : public EmuWindow_SDL3 {
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);
~EmuWindow_SDL2_VK() override;
~EmuWindow_SDL3_VK() 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-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
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL3/SDL.h>
#include "common/logging/log.h"
#include "input_common/main.h"

View file

@ -34,10 +34,10 @@
#include "network/network.h"
#include "sdl_config.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
@ -346,16 +346,16 @@ int main(int argc, char** argv) {
// Apply the command line arguments
system.ApplySettings();
std::unique_ptr<EmuWindow_SDL2> emu_window;
std::unique_ptr<EmuWindow_SDL3> emu_window;
switch (Settings::values.renderer_backend.GetValue()) {
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;
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;
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;
}