[externals] Update to SDL3 (#3952)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run

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:
Kaydax 2026-05-18 19:07:41 +02:00 committed by crueter
parent 02521882e7
commit ad2e1cc554
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
34 changed files with 768 additions and 537 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

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

View file

@ -92,11 +92,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},
};
}

View file

@ -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)

View file

@ -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"

View file

@ -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;

View file

@ -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,

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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>

View file

@ -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)

View file

@ -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>();
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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;

View file

@ -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>();
}

View file

@ -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;
};

View 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>();
}

View file

@ -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;
};

View file

@ -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"

View file

@ -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");