mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-19 18:37:04 +02:00
[externals] Update to SDL3 (#3952)
Since the launch of the steam controller I think it's only best to push towards updating to SDL3 allowing for a wider range of controller support I went ahead and started on getting it working. Everything here should be functional, I've personally tested it all on Arch Linux. Still untested on windows, so looking for feedback on that Any feedback and help would be appreciated! Main changes: - Bump everything to SDL3 - Handle SDL3 audio and input - Add steam controller support, including HD Rumble - Improved battery reporting via the status icon by using real % rather than state alone Co-authored-by: crueter <crueter@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3952 Reviewed-by: crueter <crueter@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: Lizzie <lizzie@eden-emu.dev>
This commit is contained in:
parent
02521882e7
commit
ad2e1cc554
34 changed files with 768 additions and 537 deletions
|
|
@ -119,13 +119,13 @@ if (YUZU_STATIC_BUILD)
|
|||
set(QuaZip-Qt6_FORCE_BUNDLED ON)
|
||||
|
||||
set(YUZU_USE_BUNDLED_FFMPEG ON)
|
||||
set(YUZU_USE_BUNDLED_SDL2 ON)
|
||||
set(YUZU_USE_BUNDLED_SDL3 ON)
|
||||
set(YUZU_USE_BUNDLED_OPENSSL ON)
|
||||
|
||||
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF)
|
||||
elseif(APPLE)
|
||||
set(YUZU_USE_BUNDLED_FFMPEG ON)
|
||||
set(YUZU_USE_BUNDLED_SDL2 ON)
|
||||
set(YUZU_USE_BUNDLED_SDL3 ON)
|
||||
set(YUZU_USE_BUNDLED_OPENSSL ON)
|
||||
|
||||
# these libs do not properly provide static libs/let you do it with cmake
|
||||
|
|
@ -194,8 +194,7 @@ if(MSVC)
|
|||
endif()
|
||||
|
||||
# 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 "Build SDL2 from external source" OFF "NOT MSVC;NOT ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}" "NOT ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" "${MSVC}" "NOT ANDROID" OFF)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
|
|
@ -361,10 +360,6 @@ if (CXX_GCC OR CXX_CLANG)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
# Other presets, e.g. steamdeck
|
||||
# TODO(crueter): Just have every Linux/Windows use old sdl2
|
||||
set(YUZU_SYSTEM_PROFILE "generic" CACHE STRING "CMake and Externals profile to use. One of: generic, steamdeck")
|
||||
|
||||
# Configure C++ standard
|
||||
# ===========================
|
||||
|
||||
|
|
@ -562,7 +557,7 @@ if (NOT YUZU_STATIC_ROOM)
|
|||
endif()
|
||||
|
||||
if (NOT ANDROID)
|
||||
find_package(SDL2)
|
||||
find_package(SDL3)
|
||||
endif()
|
||||
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
|
|
|
|||
|
|
@ -65,14 +65,13 @@ 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.
|
||||
- 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.
|
||||
|
||||
## OmniOS
|
||||
|
||||
Install `developer/gcc14` on OmniOS using pkgsrc.
|
||||
|
||||
Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON -DYUZU_USE_EXTERNAL_SDL2=ON`
|
||||
Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON`
|
||||
|
||||
For OmniOS you are required to build glslang yourself:
|
||||
```sh
|
||||
|
|
|
|||
42
docs/Deps.md
42
docs/Deps.md
|
|
@ -47,7 +47,7 @@ If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go
|
|||
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://www.libsdl.org/download-2.0.php) 3.2.10+ (Use `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time)
|
||||
|
||||
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):
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ sudo emerge -a \
|
|||
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
||||
dev-util/vulkan-utility-libraries dev-util/glslang \
|
||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl2 media-libs/cubeb \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||
net-libs/enet \
|
||||
sys-libs/zlib \
|
||||
dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \
|
||||
|
|
@ -142,7 +142,8 @@ Required USE flags:
|
|||
|
||||
* `dev-qt/qtbase network concurrent dbus gui widgets`
|
||||
* `dev-libs/quazip qt6`
|
||||
* `media-libs/libsdl2 haptic joystick sound video`
|
||||
* `media-libs/libsdl3 haptic joystick sound video`
|
||||
* Adding `X vulkan udev opengl` is recommended but not required
|
||||
* `dev-cpp/cpp-httplib ssl`
|
||||
|
||||
[Caveats](./Caveats.md#gentoo-linux)
|
||||
|
|
@ -153,7 +154,7 @@ Required USE flags:
|
|||
<summary>Arch Linux</summary>
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl2 zlib zstd zip unzip 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 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
||||
```
|
||||
|
||||
* Building with QT Web Engine requires `qt6-webengine` as well.
|
||||
|
|
@ -166,10 +167,10 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
|||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||
|
||||
```sh
|
||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
```
|
||||
|
||||
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
|
||||
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
||||
* To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake.
|
||||
|
||||
</details>
|
||||
|
|
@ -190,13 +191,13 @@ AlmaLinux (use `YUZU_USE_CPM=ON`):
|
|||
sudo dnf install epel-release dnf-utils
|
||||
# (run rpmfusion installation afterwards)
|
||||
# vvv - This will work for most systems
|
||||
sudo dnf install autoconf cmake libtool libudev cmake gcc gcc-c++ qt6-qtbase-devel zlib-devel openssl-devel boost SDL2 ffmpeg-devel libdrm glslang jq patch
|
||||
sudo dnf install autoconf cmake libtool libudev cmake gcc gcc-c++ qt6-qtbase-devel zlib-devel openssl-devel boost SDL3 ffmpeg-devel libdrm glslang jq patch
|
||||
# Qt6 private GUI must be taken from CRB repos
|
||||
sudo dnf config-manager --enable crb
|
||||
sudo dnf install qt6-qtbase-private-devel
|
||||
```
|
||||
|
||||
For systems like OpenEuler or derivates, don't forget to also install: `SDL2-devel pkg-config fmt-dev nlohmann-json-dev`.
|
||||
For systems like OpenEuler or derivates, don't forget to also install: `SDL3-devel pkg-config fmt-dev nlohmann-json-dev`.
|
||||
|
||||
* [RPM Fusion](https://rpmfusion.org/Configuration) is required for `ffmpeg-devel`
|
||||
* Fedora 32 or later is required.
|
||||
|
|
@ -213,7 +214,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
|
|||
# Enable the community repository
|
||||
setup-apkrepos -c
|
||||
# Install
|
||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl2-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
|
||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
|
||||
```
|
||||
|
||||
</details>
|
||||
|
|
@ -221,7 +222,7 @@ apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libqua
|
|||
<summary>Void Linux</summary>
|
||||
|
||||
```sh
|
||||
xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL2-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel
|
||||
xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL3-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel
|
||||
```
|
||||
|
||||
Yes, `nlohmann-json` is just named `json-c++`. Why?
|
||||
|
|
@ -242,7 +243,7 @@ If you're going for a pure build (i.e no downloaded deps), use `-DYUZU_USE_CPM=O
|
|||
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`.
|
||||
|
|
@ -259,7 +260,7 @@ brew install molten-vk
|
|||
<details>
|
||||
<summary>FreeBSD</summary>
|
||||
|
||||
As root run: `pkg install devel/cmake devel/sdl20 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6`
|
||||
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6`
|
||||
|
||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
|
||||
|
|
@ -270,8 +271,9 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
|||
<summary>NetBSD</summary>
|
||||
|
||||
For NetBSD +10.1:
|
||||
|
||||
```sh
|
||||
pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#netbsd).
|
||||
|
|
@ -282,7 +284,7 @@ pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tool
|
|||
|
||||
```sh
|
||||
pkg_add -u
|
||||
pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1-1.0.29 quazip-qt6
|
||||
pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl3 libusb1-1.0.29 quazip-qt6
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#openbsd).
|
||||
|
|
@ -292,7 +294,7 @@ pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gm
|
|||
<summary>DragonFlyBSD</summary>
|
||||
|
||||
```sh
|
||||
pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl2 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver
|
||||
pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#dragonflybsd).
|
||||
|
|
@ -302,7 +304,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
|
|||
<summary>OpenIndiana</summary>
|
||||
|
||||
```sh
|
||||
sudo pkg install git cmake 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
|
||||
sudo pkg install git cmake 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
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#openindiana).
|
||||
|
|
@ -326,7 +328,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
|
|||
|
||||
```sh
|
||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL2"
|
||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL3"
|
||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||
packages="$BASE"
|
||||
for pkg in $MINGW; do
|
||||
|
|
@ -352,7 +354,7 @@ pacman -Syuu --needed --noconfirm $packages
|
|||
<summary>HaikuOS</summary>
|
||||
|
||||
```sh
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#haikuos).
|
||||
|
|
@ -363,9 +365,11 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b
|
|||
|
||||
```sh
|
||||
sudo pkg update
|
||||
sudo pkg install git cmake ffmpeg6 sdl2 zlib llvm18
|
||||
sudo pkg install git cmake ffmpeg6 zlib llvm18
|
||||
```
|
||||
|
||||
RedoxOS currently does not support SDL3. You will have to compile it yourself and pray.
|
||||
|
||||
[Caveats](./Caveats.md#redoxos).
|
||||
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ These options control dependencies.
|
|||
- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo)
|
||||
- `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib
|
||||
- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build
|
||||
- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source
|
||||
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2
|
||||
- `YUZU_USE_BUNDLED_SDL3` (ON for MSVC) Download a prebuilt SDL3
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
|
|
@ -63,7 +62,7 @@ These options control executables and build flavors.
|
|||
|
||||
**Desktop only**:
|
||||
|
||||
- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli)
|
||||
- `YUZU_CMD` (ON) Compile the SDL-based frontend (eden-cli)
|
||||
- `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable
|
||||
- `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality
|
||||
- `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable
|
||||
|
|
@ -99,5 +98,6 @@ The following options were a part of Eden at one point, but have since been reti
|
|||
- `ENABLE_SDL2` - While technically possible to *not* use SDL2 on desktop, this is **NOT** a supported configuration under any means, and adding this matrix to our build system was not worth the effort.
|
||||
- `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now.
|
||||
- If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`.
|
||||
- `YUZU_USE_EXTERNAL_SDL` - This is now handled automatically. It was included even after CPM for purposes that have not applied for a very long time.
|
||||
|
||||
See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Windows/riscv64 doesn't exist, and may never (until corporate greed no longer co
|
|||
|
||||
Android/riscv64 is interesting. While support for it may be added if and when RISC-V phones/handhelds ever go mainstream, arm64 devices will always be preferred due to NCE.
|
||||
|
||||
Only Fedora/riscv64 has been tested, but in theory, every riscv64 distribution that has *at least* the standard build tools, Qt, FFmpeg, and SDL2 should work.
|
||||
Only Fedora/riscv64 has been tested, but in theory, every riscv64 distribution that has *at least* the standard build tools, Qt, FFmpeg, and SDL3 should work.
|
||||
|
||||
## Other
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
# User Handbook - Command Line
|
||||
|
||||
There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
|
||||
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
|
||||
|
||||
## eden
|
||||
|
||||
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behaviour is similar to `eden-cli`; allows dragging and dropping games into the application.
|
||||
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
||||
- `-f`: Use fullscreen.
|
||||
|
|
@ -12,6 +13,7 @@ There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based a
|
|||
- `-setup`: Launch setup applet.
|
||||
|
||||
## eden-cli
|
||||
|
||||
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
||||
- `--config/-c`: Specify alternate configuration file.
|
||||
- `--fullscreen/-f`: Set fullscreen.
|
||||
|
|
|
|||
28
externals/CMakeLists.txt
vendored
28
externals/CMakeLists.txt
vendored
|
|
@ -136,8 +136,7 @@ if(ENABLE_CUBEB)
|
|||
endif()
|
||||
|
||||
if (NOT ANDROID)
|
||||
if (YUZU_USE_EXTERNAL_SDL2)
|
||||
message(STATUS "Using SDL2 from externals.")
|
||||
if (YUZU_USE_BUNDLED_SDL3)
|
||||
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)
|
||||
|
|
@ -158,21 +157,26 @@ if (NOT ANDROID)
|
|||
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(sdl3)
|
||||
else()
|
||||
message(STATUS "Using bundled SDL3")
|
||||
if (PLATFORM_FREEBSD)
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
endif()
|
||||
AddJsonPackage(sdl2)
|
||||
AddJsonPackage(sdl3-ci)
|
||||
endif()
|
||||
|
||||
find_package(SDL2 2.26.4 REQUIRED)
|
||||
# Normalize SDL3 link target across package variants.
|
||||
# Some SDL3 packages export only SDL3::SDL3-shared or SDL3::SDL3-static.
|
||||
if (NOT TARGET SDL3::SDL3)
|
||||
if (TARGET SDL3::SDL3-shared)
|
||||
add_library(SDL3::SDL3 ALIAS SDL3::SDL3-shared)
|
||||
elseif (TARGET SDL3::SDL3-static)
|
||||
add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static)
|
||||
else()
|
||||
message(FATAL_ERROR "SDL3 package found, but no usable SDL3 target was exported")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
|
|
|||
31
externals/cpmfile.json
vendored
31
externals/cpmfile.json
vendored
|
|
@ -122,13 +122,13 @@
|
|||
"BUNDLE_SPEEX ON"
|
||||
]
|
||||
},
|
||||
"sdl2": {
|
||||
"sdl3-ci": {
|
||||
"ci": true,
|
||||
"package": "SDL2",
|
||||
"name": "SDL2",
|
||||
"repo": "crueter-ci/SDL2",
|
||||
"version": "2.32.10-3c28e8ecc0",
|
||||
"min_version": "2.26.4"
|
||||
"package": "SDL3",
|
||||
"name": "SDL3",
|
||||
"repo": "crueter-ci/SDL3",
|
||||
"version": "3.4.8-d57c3b685c",
|
||||
"min_version": "3.2.10"
|
||||
},
|
||||
"catch2": {
|
||||
"package": "Catch2",
|
||||
|
|
@ -156,22 +156,13 @@
|
|||
"find_args": "MODULE",
|
||||
"git_version": "4.25"
|
||||
},
|
||||
"sdl2_generic": {
|
||||
"package": "SDL2",
|
||||
"sdl3": {
|
||||
"package": "SDL3",
|
||||
"repo": "libsdl-org/SDL",
|
||||
"tag": "release-%VERSION%",
|
||||
"hash": "d5622d6bb7266f7942a7b8ad43e8a22524893bf0c2ea1af91204838d9b78d32768843f6faa248757427b8404b8c6443776d4afa6b672cd8571a4e0c03a829383",
|
||||
"bundled": true,
|
||||
"git_version": "2.32.10",
|
||||
"skip_updates": true
|
||||
},
|
||||
"sdl2_steamdeck": {
|
||||
"package": "SDL2",
|
||||
"repo": "libsdl-org/SDL",
|
||||
"sha": "cc016b0046",
|
||||
"hash": "b8d9873446cdb922387471df9968e078714683046674ef0d0edddf8e25da65a539a3bae83d635496b970237f90b07b36a69f8d7855d450de59311d6d6e8c3dbc",
|
||||
"bundled": true,
|
||||
"skip_updates": "true"
|
||||
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
||||
"git_version": "3.4.8",
|
||||
"version": "3.2.10"
|
||||
},
|
||||
"moltenvk": {
|
||||
"repo": "V380-Ori/Ryujinx.MoltenVK",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pkgs.mkShellNoCC {
|
|||
qt6.qtbase qt6.qtmultimedia qt6.qtwayland qt6.qttools
|
||||
qt6.qtwebengine qt6.qt5compat
|
||||
# eden-cli
|
||||
SDL2
|
||||
SDL3
|
||||
# optional components
|
||||
discord-rpc gamemode
|
||||
];
|
||||
|
|
|
|||
|
|
@ -247,11 +247,11 @@ if(ANDROID)
|
|||
target_compile_definitions(audio_core PUBLIC HAVE_OBOE)
|
||||
else()
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/sdl2_sink.cpp
|
||||
sink/sdl2_sink.h)
|
||||
sink/sdl3_sink.cpp
|
||||
sink/sdl3_sink.h)
|
||||
|
||||
target_link_libraries(audio_core PRIVATE SDL2::SDL2)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
|
|
|||
|
|
@ -7,16 +7,40 @@
|
|||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/sdl2_sink.h"
|
||||
#include "audio_core/sink/sdl3_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
|
||||
namespace {
|
||||
SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) {
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count)
|
||||
: SDL_GetAudioPlaybackDevices(&device_count);
|
||||
if (devices == nullptr) {
|
||||
return capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
|
||||
SDL_AudioDeviceID selected = capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
|
||||
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
const char* current_name = SDL_GetAudioDeviceName(devices[i]);
|
||||
if (current_name != nullptr && device_name == current_name) {
|
||||
selected = devices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
return selected;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
/**
|
||||
* SDL sink stream, responsible for sinking samples to hardware.
|
||||
*/
|
||||
|
|
@ -39,13 +63,10 @@ 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.format = SDL_AUDIO_S16;
|
||||
|
||||
std::string device_name{output_device};
|
||||
bool capture{false};
|
||||
|
|
@ -54,22 +75,28 @@ public:
|
|||
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);
|
||||
}
|
||||
const SDL_AudioDeviceID audio_device =
|
||||
device_name.empty() ? (capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
|
||||
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
|
||||
: FindAudioDeviceByName(device_name, capture);
|
||||
|
||||
if (device == 0) {
|
||||
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback,
|
||||
this);
|
||||
|
||||
if (stream == nullptr) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_AudioSpec stream_in{};
|
||||
SDL_AudioSpec stream_out{};
|
||||
static_cast<void>(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
|
||||
|
||||
LOG_INFO(Service_Audio,
|
||||
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
|
||||
" samples {}",
|
||||
device, obtained.freq, obtained.channels, system_channels, obtained.samples);
|
||||
" format {}",
|
||||
static_cast<const void*>(stream), stream_out.freq, stream_out.channels,
|
||||
system_channels, static_cast<int>(stream_out.format));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,13 +111,14 @@ public:
|
|||
* Finalize the sink stream.
|
||||
*/
|
||||
void Finalize() override {
|
||||
if (device == 0) {
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
SDL_ClearQueuedAudio(device);
|
||||
SDL_CloseAudioDevice(device);
|
||||
SDL_ClearAudioStream(stream);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,23 +128,23 @@ public:
|
|||
* Default false.
|
||||
*/
|
||||
void Start(bool resume = false) override {
|
||||
if (device == 0 || !paused) {
|
||||
if (stream == nullptr || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = false;
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
static_cast<void>(SDL_ResumeAudioStreamDevice(stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the sink stream.
|
||||
*/
|
||||
void Stop() override {
|
||||
if (device == 0 || paused) {
|
||||
if (stream == nullptr || paused) {
|
||||
return;
|
||||
}
|
||||
SignalPause();
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
static_cast<void>(SDL_PauseAudioStreamDevice(stream));
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -128,7 +156,8 @@ private:
|
|||
* @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 DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
|
||||
int total_amount) {
|
||||
auto* impl = static_cast<SDLSinkStream*>(userdata);
|
||||
|
||||
if (!impl) {
|
||||
|
|
@ -137,25 +166,46 @@ private:
|
|||
|
||||
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)};
|
||||
|
||||
if (impl->type == StreamType::In) {
|
||||
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
|
||||
num_frames * frame_size};
|
||||
const int bytes_available = SDL_GetAudioStreamAvailable(stream);
|
||||
if (bytes_available <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<s16> input(bytes_available / static_cast<int>(sizeof(s16)));
|
||||
const int bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
|
||||
if (bytes_read <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t num_frames =
|
||||
static_cast<std::size_t>(bytes_read) / sizeof(s16) / frame_size;
|
||||
std::span<const s16> input_buffer{input.data(),
|
||||
static_cast<std::size_t>(bytes_read) / sizeof(s16)};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
} else {
|
||||
std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
|
||||
if (additional_amount <= 0 && total_amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
|
||||
std::vector<s16> output(bytes_requested / static_cast<int>(sizeof(s16)));
|
||||
const std::size_t num_frames =
|
||||
static_cast<std::size_t>(bytes_requested) / sizeof(s16) / frame_size;
|
||||
std::span<s16> output_buffer{output.data(), output.size()};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
static_cast<void>(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
|
||||
}
|
||||
}
|
||||
|
||||
/// SDL device id of the opened input/output device
|
||||
SDL_AudioDeviceID device{};
|
||||
/// SDL stream attached to an opened input/output device
|
||||
SDL_AudioStream* stream{};
|
||||
};
|
||||
|
||||
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,18 +268,26 @@ 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);
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices =
|
||||
capture ? SDL_GetAudioRecordingDevices(&device_count)
|
||||
: SDL_GetAudioPlaybackDevices(&device_count);
|
||||
if (devices == nullptr) {
|
||||
return device_list;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
if (const char* name = SDL_GetAudioDeviceName(i, capture)) {
|
||||
if (const char* name = SDL_GetAudioDeviceName(devices[i])) {
|
||||
device_list.emplace_back(name);
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
|
@ -242,7 +300,7 @@ u32 GetSDLLatency() {
|
|||
// REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX
|
||||
/*
|
||||
bool IsSDLSuitable() {
|
||||
#if !defined(HAVE_SDL2)
|
||||
#if !defined(HAVE_SDL3)
|
||||
return false;
|
||||
#else
|
||||
// Check SDL can init
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -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.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);
|
||||
|
|
|
|||
|
|
@ -92,11 +92,13 @@ 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},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ else()
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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 +9,7 @@
|
|||
#include <array>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <SDL_hidapi.h>
|
||||
#include <SDL3/SDL_hidapi.h>
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -15,29 +15,79 @@ 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};
|
||||
}
|
||||
|
||||
using GamepadBindings = std::vector<SDL_GamepadBinding>;
|
||||
|
||||
SDL_GamepadBinding EmptyBinding() {
|
||||
SDL_GamepadBinding binding{};
|
||||
binding.input_type = SDL_GAMEPAD_BINDTYPE_NONE;
|
||||
return binding;
|
||||
}
|
||||
|
||||
GamepadBindings GetBindings(SDL_Gamepad* controller) {
|
||||
if (controller == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int binding_count = 0;
|
||||
SDL_GamepadBinding** bindings = SDL_GetGamepadBindings(controller, &binding_count);
|
||||
if (bindings == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
GamepadBindings cached_bindings{};
|
||||
cached_bindings.reserve(static_cast<std::size_t>(binding_count));
|
||||
for (int i = 0; i < binding_count; ++i) {
|
||||
if (const auto* current = bindings[i]) {
|
||||
cached_bindings.emplace_back(*current);
|
||||
}
|
||||
}
|
||||
SDL_free(bindings);
|
||||
return cached_bindings;
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
SDL_GamepadBinding FindBinding(const GamepadBindings& bindings, Predicate matches) {
|
||||
const auto it = std::find_if(bindings.begin(), bindings.end(), matches);
|
||||
return it != bindings.end() ? *it : EmptyBinding();
|
||||
}
|
||||
|
||||
SDL_GamepadBinding GetBindingForButton(const GamepadBindings& bindings, SDL_GamepadButton button) {
|
||||
return FindBinding(bindings, [button](const SDL_GamepadBinding& current) {
|
||||
return current.output_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&
|
||||
current.output.button == static_cast<SDL_GamepadButton>(button);
|
||||
});
|
||||
}
|
||||
|
||||
SDL_GamepadBinding GetBindingForAxis(const GamepadBindings& bindings, SDL_GamepadAxis axis) {
|
||||
return FindBinding(bindings, [axis](const SDL_GamepadBinding& current) {
|
||||
return current.output_type == SDL_GAMEPAD_BINDTYPE_AXIS &&
|
||||
current.output.axis.axis == static_cast<SDL_GamepadAxis>(axis);
|
||||
});
|
||||
}
|
||||
} // 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 true;
|
||||
}
|
||||
|
||||
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} {
|
||||
SDL_Gamepad* game_controller)
|
||||
: guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_CloseJoystick},
|
||||
sdl_controller{game_controller, &SDL_CloseGamepad} {
|
||||
EnableMotion();
|
||||
}
|
||||
|
||||
|
|
@ -45,30 +95,49 @@ public:
|
|||
if (!sdl_controller) {
|
||||
return;
|
||||
}
|
||||
SDL_GameController* controller = sdl_controller.get();
|
||||
SDL_Gamepad* controller = sdl_controller.get();
|
||||
if (HasMotion()) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE);
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE);
|
||||
SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_ACCEL, false);
|
||||
SDL_SetGamepadSensorEnabled(controller, 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(controller, SDL_SENSOR_ACCEL);
|
||||
has_gyro = SDL_GamepadHasSensor(controller, SDL_SENSOR_GYRO);
|
||||
if (has_accel) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
if (!SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_ACCEL, true)) {
|
||||
LOG_WARNING(Input, "Failed to enable accelerometer sensor: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
if (has_gyro) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||
if (!SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_GYRO, true)) {
|
||||
LOG_WARNING(Input, "Failed to enable gyroscope sensor: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Input, "Controller motion capabilities: accel={} gyro={}", has_accel, has_gyro);
|
||||
}
|
||||
|
||||
bool HasMotion() const {
|
||||
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;
|
||||
last_motion_update = event.timestamp;
|
||||
const u64 sensor_timestamp = event.sensor_timestamp != 0 ? event.sensor_timestamp
|
||||
: event.timestamp;
|
||||
|
||||
if (last_motion_update == 0) {
|
||||
last_motion_update = sensor_timestamp;
|
||||
return false;
|
||||
}
|
||||
if (sensor_timestamp < last_motion_update) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// SDL3 reports sensor timestamps in nanoseconds, while the input stack expects
|
||||
// delta timestamps in microseconds.
|
||||
const u64 time_difference = (sensor_timestamp - last_motion_update) / 1000;
|
||||
last_motion_update = sensor_timestamp;
|
||||
switch (event.sensor) {
|
||||
case SDL_SENSOR_ACCEL: {
|
||||
motion.accel_x = -event.data[0] / gravity_constant;
|
||||
|
|
@ -102,7 +171,7 @@ public:
|
|||
}
|
||||
|
||||
motion_error_count = 0;
|
||||
motion.delta_timestamp = time_difference * 1000;
|
||||
motion.delta_timestamp = time_difference;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -136,26 +205,41 @@ public:
|
|||
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;
|
||||
return SDL_RumbleGamepad(sdl_controller.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),
|
||||
return SDL_RumbleJoystick(sdl_joystick.get(), static_cast<u16>(low_amplitude),
|
||||
static_cast<u16>(high_amplitude),
|
||||
rumble_max_duration_ms) != -1;
|
||||
rumble_max_duration_ms);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasHDRumble() const {
|
||||
constexpr Uint16 valve_vendor_id = 0x28DE;
|
||||
const auto is_known_hd_type = [](SDL_GamepadType type) {
|
||||
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;
|
||||
};
|
||||
|
||||
// Valve hardware doesn't have any enums in SDL, so we have to support it manually.
|
||||
// Since they have HD rumble, we can assume that all their hardware supports it, even if we can't detect the exact type.
|
||||
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 (is_known_hd_type(SDL_GetGamepadType(sdl_controller.get())) ||
|
||||
SDL_GetGamepadVendor(sdl_controller.get()) == valve_vendor_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sdl_joystick) {
|
||||
if (SDL_GetJoystickVendor(sdl_joystick.get()) == valve_vendor_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -201,11 +285,11 @@ public:
|
|||
return sdl_joystick.get();
|
||||
}
|
||||
|
||||
SDL_GameController* GetSDLGameController() const {
|
||||
SDL_Gamepad* GetSDLGameController() const {
|
||||
return sdl_controller.get();
|
||||
}
|
||||
|
||||
void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
|
||||
void SetSDLJoystick(SDL_Joystick* joystick, SDL_Gamepad* controller) {
|
||||
sdl_joystick.reset(joystick);
|
||||
sdl_controller.reset(controller);
|
||||
}
|
||||
|
|
@ -232,20 +316,36 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) {
|
||||
switch (battery_level) {
|
||||
case SDL_JOYSTICK_POWER_EMPTY:
|
||||
return Common::Input::BatteryLevel::Empty;
|
||||
case SDL_JOYSTICK_POWER_LOW:
|
||||
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:
|
||||
Common::Input::BatteryLevel GetBatteryLevel(SDL_PowerState battery_level, int percent) {
|
||||
if (battery_level == SDL_POWERSTATE_CHARGING) {
|
||||
return Common::Input::BatteryLevel::Charging;
|
||||
case SDL_JOYSTICK_POWER_UNKNOWN:
|
||||
}
|
||||
|
||||
if (percent >= 0 && percent <= 100) {
|
||||
if (percent <= 5) {
|
||||
return Common::Input::BatteryLevel::Empty;
|
||||
}
|
||||
if (percent <= 20) {
|
||||
return Common::Input::BatteryLevel::Critical;
|
||||
}
|
||||
if (percent <= 40) {
|
||||
return Common::Input::BatteryLevel::Low;
|
||||
}
|
||||
if (percent <= 70) {
|
||||
return Common::Input::BatteryLevel::Medium;
|
||||
}
|
||||
return Common::Input::BatteryLevel::Full;
|
||||
}
|
||||
|
||||
switch (battery_level) {
|
||||
case SDL_POWERSTATE_ON_BATTERY:
|
||||
return Common::Input::BatteryLevel::Medium;
|
||||
case SDL_POWERSTATE_NO_BATTERY:
|
||||
return Common::Input::BatteryLevel::None;
|
||||
case SDL_POWERSTATE_CHARGED:
|
||||
return Common::Input::BatteryLevel::Full;
|
||||
case SDL_POWERSTATE_ERROR:
|
||||
case SDL_POWERSTATE_UNKNOWN:
|
||||
default:
|
||||
return Common::Input::BatteryLevel::None;
|
||||
}
|
||||
|
|
@ -253,28 +353,28 @@ public:
|
|||
|
||||
std::string GetControllerName() const {
|
||||
if (sdl_controller) {
|
||||
switch (SDL_GameControllerGetType(sdl_controller.get())) {
|
||||
case SDL_CONTROLLER_TYPE_XBOX360:
|
||||
switch (SDL_GetGamepadType(sdl_controller.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_controller.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 +386,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_controller;
|
||||
mutable std::mutex mutex;
|
||||
|
||||
u64 last_motion_update{};
|
||||
|
|
@ -323,7 +423,10 @@ 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);
|
||||
if (sdl_joystick == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::scoped_lock lock{joystick_map_mutex};
|
||||
|
|
@ -345,34 +448,70 @@ 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;
|
||||
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGamepadID(SDL_JoystickID sdl_id) {
|
||||
auto* const sdl_gamepad = SDL_GetGamepadFromID(sdl_id);
|
||||
if (sdl_gamepad == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (SDL_IsGameController(joystick_index)) {
|
||||
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
|
||||
auto* const sdl_joystick = SDL_GetGamepadJoystick(sdl_gamepad);
|
||||
if (sdl_joystick == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::scoped_lock lock{joystick_map_mutex};
|
||||
const auto map_it = joystick_map.find(guid);
|
||||
if (map_it == joystick_map.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[sdl_joystick, sdl_gamepad](const auto& joystick) {
|
||||
return joystick->GetSDLJoystick() == sdl_joystick ||
|
||||
joystick->GetSDLGameController() == sdl_gamepad;
|
||||
});
|
||||
|
||||
if (vec_it == map_it->second.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *vec_it;
|
||||
}
|
||||
|
||||
void SDLDriver::InitJoystick(SDL_JoystickID joystick_id) {
|
||||
SDL_Joystick* sdl_joystick = SDL_OpenJoystick(joystick_id);
|
||||
SDL_Gamepad* sdl_gamecontroller = nullptr;
|
||||
int battery_percent = -1;
|
||||
SDL_PowerState battery_state = SDL_POWERSTATE_UNKNOWN;
|
||||
|
||||
if (SDL_IsGamepad(joystick_id)) {
|
||||
sdl_gamecontroller = 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;
|
||||
}
|
||||
|
||||
battery_state = SDL_GetJoystickPowerInfo(sdl_joystick, &battery_percent);
|
||||
|
||||
const auto guid = GetGUID(sdl_joystick);
|
||||
|
||||
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 index {}", 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 index {}", joystick_id);
|
||||
SDL_CloseJoystick(sdl_joystick);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -382,6 +521,8 @@ void SDLDriver::InitJoystick(int joystick_index) {
|
|||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
|
||||
PreSetController(joystick->GetPadIdentifier());
|
||||
joystick->EnableMotion();
|
||||
SetBattery(joystick->GetPadIdentifier(),
|
||||
joystick->GetBatteryLevel(battery_state, battery_percent));
|
||||
joystick_map[guid].emplace_back(std::move(joystick));
|
||||
return;
|
||||
}
|
||||
|
|
@ -394,6 +535,8 @@ void SDLDriver::InitJoystick(int joystick_index) {
|
|||
if (joystick_it != joystick_guid_list.end()) {
|
||||
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
|
||||
(*joystick_it)->EnableMotion();
|
||||
SetBattery((*joystick_it)->GetPadIdentifier(),
|
||||
(*joystick_it)->GetBatteryLevel(battery_state, battery_percent));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -401,6 +544,8 @@ void SDLDriver::InitJoystick(int joystick_index) {
|
|||
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
|
||||
PreSetController(joystick->GetPadIdentifier());
|
||||
joystick->EnableMotion();
|
||||
SetBattery(joystick->GetPadIdentifier(),
|
||||
joystick->GetBatteryLevel(battery_state, battery_percent));
|
||||
joystick_guid_list.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
|
|
@ -428,55 +573,60 @@ void SDLDriver::PumpEvents() const {
|
|||
|
||||
void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYBUTTONUP: {
|
||||
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: {
|
||||
auto joystick = GetSDLJoystickByGamepadID(event.gsensor.which);
|
||||
if (!joystick) {
|
||||
joystick = GetSDLJoystickBySDLID(event.gsensor.which);
|
||||
}
|
||||
if (joystick) {
|
||||
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, event.jbattery.percent));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
case SDL_EVENT_JOYSTICK_REMOVED:
|
||||
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
|
||||
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
|
||||
CloseJoystick(SDL_GetJoystickFromID(event.jdevice.which));
|
||||
break;
|
||||
case SDL_JOYDEVICEADDED:
|
||||
case SDL_EVENT_JOYSTICK_ADDED:
|
||||
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
|
||||
InitJoystick(event.jdevice.which);
|
||||
break;
|
||||
|
|
@ -496,12 +646,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
|
|||
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0");
|
||||
|
||||
// Prevent SDL from adding undesired axis
|
||||
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
|
||||
// SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro.
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
|
||||
SDL_SetHint(SDL_HINT_AUTO_UPDATE_SENSORS, "1");
|
||||
|
||||
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
// Disable hidapi drivers for joycon controllers when the custom joycon driver is enabled
|
||||
|
|
@ -523,16 +674,13 @@ 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_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) == 0;
|
||||
if (start_thread && !SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
|
||||
LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
|
@ -552,19 +700,24 @@ 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 joystick_count = 0;
|
||||
SDL_JoystickID* joysticks = SDL_GetJoysticks(&joystick_count);
|
||||
if (joysticks != nullptr) {
|
||||
for (int i = 0; i < joystick_count; ++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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -758,17 +911,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 {};
|
||||
}
|
||||
|
|
@ -808,8 +961,8 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
|
|||
|
||||
// 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
|
||||
|
|
@ -828,41 +981,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},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -872,15 +1025,16 @@ ButtonMapping SDLDriver::GetSingleControllerMapping(
|
|||
ButtonMapping mapping;
|
||||
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
const auto bindings = GetBindings(controller);
|
||||
|
||||
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
|
||||
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
|
||||
const auto binding = GetBindingForButton(bindings, 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);
|
||||
const auto binding = GetBindingForAxis(bindings, sdl_axis);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||
|
|
@ -897,29 +1051,31 @@ ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoyst
|
|||
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
auto* controller2 = joystick2->GetSDLGameController();
|
||||
const auto bindings = GetBindings(controller);
|
||||
const auto bindings2 = GetBindings(controller2);
|
||||
|
||||
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
|
||||
if (IsButtonOnLeftSide(switch_button)) {
|
||||
const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
|
||||
const auto binding = GetBindingForButton(bindings2, sdl_button);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
|
||||
continue;
|
||||
}
|
||||
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
|
||||
const auto binding = GetBindingForButton(bindings, 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);
|
||||
const auto binding = GetBindingForAxis(bindings2, sdl_axis);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
|
||||
continue;
|
||||
}
|
||||
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
|
||||
const auto binding = GetBindingForAxis(bindings, sdl_axis);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||
|
|
@ -957,46 +1113,43 @@ AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& p
|
|||
}
|
||||
|
||||
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);
|
||||
const auto bindings = GetBindings(controller);
|
||||
const auto binding_left_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTX);
|
||||
const auto binding_left_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTY);
|
||||
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,
|
||||
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,
|
||||
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);
|
||||
const auto binding_right_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTX);
|
||||
const auto binding_right_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTY);
|
||||
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,
|
||||
BuildParamPackageForAnalog(identifier, binding_right_x.input.axis.axis,
|
||||
binding_right_y.input.axis.axis, right_offset_x,
|
||||
right_offset_y));
|
||||
return mapping;
|
||||
}
|
||||
|
|
@ -1102,19 +1255,16 @@ bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) {
|
|||
|
||||
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);
|
||||
const auto bindings = GetBindings(controller);
|
||||
const auto binding_left_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTX);
|
||||
const auto binding_right_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTX);
|
||||
const auto binding_left_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTY);
|
||||
const auto binding_right_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTY);
|
||||
|
||||
if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) {
|
||||
if (axis_x != binding_left_y.input.axis.axis && axis_x != binding_right_y.input.axis.axis) {
|
||||
return false;
|
||||
}
|
||||
if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) {
|
||||
if (axis_y != binding_left_x.input.axis.axis && axis_y != binding_right_x.input.axis.axis) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -11,25 +11,22 @@
|
|||
#include <thread>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#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;
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class SDLJoystick;
|
||||
|
||||
using ButtonBindings =
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 20>;
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadButton>, 20>;
|
||||
using ZButtonBindings =
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadAxis>, 2>;
|
||||
|
||||
class SDLDriver : public InputEngine {
|
||||
public:
|
||||
|
|
@ -46,6 +43,7 @@ public:
|
|||
|
||||
/// Get the nth joystick with the corresponding GUID
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickByGamepadID(SDL_JoystickID sdl_id);
|
||||
|
||||
/**
|
||||
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
|
||||
|
|
@ -72,7 +70,7 @@ 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.
|
||||
|
|
@ -92,7 +90,7 @@ private:
|
|||
Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const;
|
||||
|
||||
Common::ParamPackage BuildParamPackageForBinding(
|
||||
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const;
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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 +13,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <SDL_hidapi.h>
|
||||
#include <SDL3/SDL_hidapi.h>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
#ifdef ENABLE_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
|
||||
|
|
@ -90,7 +90,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
|
||||
|
|
@ -124,7 +124,7 @@ struct InputSubsystem::Impl {
|
|||
#endif
|
||||
UnregisterEngine(virtual_amiibo);
|
||||
UnregisterEngine(virtual_gamepad);
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_SDL3
|
||||
UnregisterEngine(sdl);
|
||||
UnregisterEngine(joycon);
|
||||
#endif
|
||||
|
|
@ -154,7 +154,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();
|
||||
|
|
@ -189,7 +189,7 @@ struct InputSubsystem::Impl {
|
|||
if (engine == udp_client->GetEngineName()) {
|
||||
return udp_client;
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_SDL3
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl;
|
||||
}
|
||||
|
|
@ -280,7 +280,7 @@ struct InputSubsystem::Impl {
|
|||
if (engine == virtual_gamepad->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_SDL3
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -301,7 +301,7 @@ struct InputSubsystem::Impl {
|
|||
gcadapter->BeginConfiguration();
|
||||
#endif
|
||||
udp_client->BeginConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_SDL3
|
||||
sdl->BeginConfiguration();
|
||||
joycon->BeginConfiguration();
|
||||
#endif
|
||||
|
|
@ -317,7 +317,7 @@ struct InputSubsystem::Impl {
|
|||
gcadapter->EndConfiguration();
|
||||
#endif
|
||||
udp_client->EndConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_SDL3
|
||||
sdl->EndConfiguration();
|
||||
joycon->EndConfiguration();
|
||||
#endif
|
||||
|
|
@ -325,7 +325,7 @@ struct InputSubsystem::Impl {
|
|||
|
||||
void PumpEvents() const {
|
||||
update_engine->PumpEvents();
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_SDL3
|
||||
sdl->PumpEvents();
|
||||
#endif
|
||||
}
|
||||
|
|
@ -350,7 +350,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
|
||||
|
|
|
|||
|
|
@ -466,6 +466,6 @@ if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD))
|
|||
endif()
|
||||
|
||||
# Remember that the linker is incredibly stupid.
|
||||
target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto SDL2::SDL2)
|
||||
target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto SDL3::SDL3)
|
||||
|
||||
create_target_directory_groups(yuzu)
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,20 +17,20 @@ endfunction()
|
|||
|
||||
if (ENABLE_OPENGL)
|
||||
list(APPEND OPENGL_SOURCES
|
||||
emu_window/emu_window_sdl2_gl.cpp
|
||||
emu_window/emu_window_sdl2_gl.h
|
||||
emu_window/emu_window_sdl3_gl.cpp
|
||||
emu_window/emu_window_sdl3_gl.h
|
||||
)
|
||||
else()
|
||||
set(OPENGL_SOURCES "")
|
||||
endif()
|
||||
|
||||
add_executable(yuzu-cmd
|
||||
emu_window/emu_window_sdl2.cpp
|
||||
emu_window/emu_window_sdl2.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_null.cpp
|
||||
emu_window/emu_window_sdl3_null.h
|
||||
emu_window/emu_window_sdl3_vk.cpp
|
||||
emu_window/emu_window_sdl3_vk.h
|
||||
sdl_config.cpp
|
||||
sdl_config.h
|
||||
yuzu.cpp
|
||||
|
|
@ -50,7 +50,7 @@ target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
|||
create_resource("../../dist/eden.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)
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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.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>();
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
|
|
@ -15,26 +16,25 @@
|
|||
#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,7 +52,7 @@ 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;
|
||||
|
|
@ -64,9 +64,9 @@ EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32
|
|||
};
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||
void EmuWindow_SDL3::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||
const auto mouse_button = SDLButtonToMouseButton(button);
|
||||
if (state == SDL_PRESSED) {
|
||||
if (state != 0) {
|
||||
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 +76,70 @@ 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, u8 state) {
|
||||
if (state != 0) {
|
||||
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) {
|
||||
if (show_cursor) {
|
||||
SDL_ShowCursor();
|
||||
} else {
|
||||
SDL_HideCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::Fullscreen() {
|
||||
void EmuWindow_SDL3::Fullscreen() {
|
||||
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
|
||||
// to display dimensions automatically in this mode.
|
||||
if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) {
|
||||
// Set window size to render size before entering fullscreen in exclusive mode.
|
||||
if (const SDL_DisplayMode* display_mode_ptr =
|
||||
SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
|
||||
display_mode = *display_mode_ptr;
|
||||
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
|
||||
SDL_SetWindowFullscreenMode(render_window, &display_mode);
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
|
||||
if (SDL_SetWindowFullscreen(render_window, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +147,8 @@ 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, nullptr);
|
||||
if (SDL_SetWindowFullscreen(render_window, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +163,7 @@ void EmuWindow_SDL2::Fullscreen() {
|
|||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::WaitEvent() {
|
||||
void EmuWindow_SDL3::WaitEvent() {
|
||||
// Called on main thread
|
||||
SDL_Event event;
|
||||
|
||||
|
|
@ -174,52 +181,52 @@ 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_RESIZED:
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
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:
|
||||
is_shown = false;
|
||||
OnResize();
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
is_shown = true;
|
||||
OnResize();
|
||||
break;
|
||||
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.key.down ? 1 : 0);
|
||||
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.button.down ? 1 : 0,
|
||||
static_cast<s32>(event.button.x), static_cast<s32>(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:
|
||||
|
|
@ -241,22 +248,22 @@ void EmuWindow_SDL2::WaitEvent() {
|
|||
}
|
||||
|
||||
// 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, true);
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 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
|
||||
|
||||
|
|
@ -22,10 +23,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;
|
||||
|
|
@ -85,7 +86,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
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#include <string>
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include <glad/glad.h>
|
||||
|
|
@ -20,7 +20,13 @@
|
|||
#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"
|
||||
|
||||
namespace {
|
||||
void* SDLGLGetProcAddress(const char* proc_name) {
|
||||
return reinterpret_cast<void*>(SDL_GL_GetProcAddress(proc_name));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
class SDLGLContext : public Core::Frontend::GraphicsContext {
|
||||
public:
|
||||
|
|
@ -30,7 +36,7 @@ public:
|
|||
|
||||
~SDLGLContext() {
|
||||
DoneCurrent();
|
||||
SDL_GL_DeleteContext(context);
|
||||
SDL_GL_DestroyContext(context);
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
|
|
@ -58,7 +64,7 @@ private:
|
|||
bool is_current = false;
|
||||
};
|
||||
|
||||
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
|
||||
bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() {
|
||||
std::vector<std::string_view> unsupported_ext{};
|
||||
#ifdef HAS_OPENGL
|
||||
// Extensions required to support some texture formats.
|
||||
|
|
@ -72,9 +78,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);
|
||||
|
|
@ -92,14 +98,13 @@ 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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -116,15 +121,15 @@ 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(static_cast<GLADloadproc>(SDLGLGetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -142,11 +147,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);
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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 +19,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 +31,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;
|
||||
|
||||
|
|
@ -13,25 +13,25 @@
|
|||
#include "common/logging.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);
|
||||
SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
|
||||
Layout::ScreenUndocked::Height,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
|
||||
SetWindowIcon();
|
||||
|
||||
|
|
@ -47,8 +47,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>();
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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 +9,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 +19,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;
|
||||
};
|
||||
99
src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp
Normal file
99
src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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 <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_properties.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);
|
||||
|
||||
const SDL_PropertiesID window_props = SDL_GetWindowProperties(render_window);
|
||||
|
||||
SetWindowIcon();
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
ShowCursor(false);
|
||||
}
|
||||
|
||||
if (void* hwnd =
|
||||
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)) {
|
||||
window_info.type = Core::Frontend::WindowSystemType::Windows;
|
||||
window_info.render_surface = hwnd;
|
||||
} else if (void* wl_display =
|
||||
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER,
|
||||
nullptr);
|
||||
wl_display != nullptr) {
|
||||
void* wl_surface =
|
||||
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
|
||||
if (wl_surface == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Wayland surface is unavailable");
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
window_info.type = Core::Frontend::WindowSystemType::Wayland;
|
||||
window_info.display_connection = wl_display;
|
||||
window_info.render_surface = wl_surface;
|
||||
} else if (void* x11_display =
|
||||
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER,
|
||||
nullptr);
|
||||
x11_display != nullptr) {
|
||||
const auto x11_window =
|
||||
SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
||||
if (x11_window == 0) {
|
||||
LOG_CRITICAL(Frontend, "X11 window handle is unavailable");
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
window_info.type = Core::Frontend::WindowSystemType::X11;
|
||||
window_info.display_connection = x11_display;
|
||||
window_info.render_surface = reinterpret_cast<void*>(static_cast<uintptr_t>(x11_window));
|
||||
} else if (SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER,
|
||||
nullptr) != nullptr) {
|
||||
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
|
||||
window_info.render_surface = SDL_Metal_CreateView(render_window);
|
||||
} else if (void* android_window =
|
||||
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER,
|
||||
nullptr);
|
||||
android_window != nullptr) {
|
||||
window_info.type = Core::Frontend::WindowSystemType::Android;
|
||||
window_info.render_surface = android_window;
|
||||
} else {
|
||||
LOG_CRITICAL(Frontend, "Unable to determine native window backend from SDL properties");
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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 +9,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 +19,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;
|
||||
};
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// 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.h"
|
||||
#include "input_common/main.h"
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@
|
|||
#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_sdl3.h"
|
||||
#ifdef HAS_OPENGL
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
|
||||
#endif
|
||||
#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_null.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
// windows.h needs to be included before shellapi.h
|
||||
|
|
@ -349,20 +349,20 @@ 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()) {
|
||||
#ifdef HAS_OPENGL
|
||||
case Settings::RendererBackend::OpenGL_GLSL:
|
||||
case Settings::RendererBackend::OpenGL_GLASM:
|
||||
case Settings::RendererBackend::OpenGL_SPIRV:
|
||||
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;
|
||||
#endif
|
||||
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;
|
||||
default:
|
||||
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue