diff --git a/CMakeLists.txt b/CMakeLists.txt index 15e05c2..0174a62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,12 +12,12 @@ include(DownloadExternals) include(CMakeDependentOption) include(CTest) -# Set bundled sdl2/qt as dependent options. -# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON -option(ENABLE_SDL2 "Enable the SDL2 frontend" ON) -CMAKE_DEPENDENT_OPTION(SUDACHI_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON "ENABLE_SDL2;MSVC" OFF) -# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion -CMAKE_DEPENDENT_OPTION(SUDACHI_USE_EXTERNAL_SDL2 "Compile external SDL2" ON "ENABLE_SDL2;NOT MSVC" OFF) +# Set bundled sdl3/qt as dependent options. +# OFF by default, but if ENABLE_SDL3 and MSVC are true then ON +option(ENABLE_SDL3 "Enable the SDL3 frontend" ON) +CMAKE_DEPENDENT_OPTION(SUDACHI_USE_BUNDLED_SDL3 "Download bundled SDL3 binaries" ON "ENABLE_SDL3;MSVC" OFF) +# On Linux system SDL3 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion +CMAKE_DEPENDENT_OPTION(SUDACHI_USE_EXTERNAL_SDL3 "Compile external SDL3" ON "ENABLE_SDL3;NOT MSVC" OFF) cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF) @@ -527,32 +527,32 @@ if(ENABLE_QT) endif() -# find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the sudachi_find_package -if (ENABLE_SDL2) - if (SUDACHI_USE_BUNDLED_SDL2) +# find SDL3 exports a bunch of variables that are needed, so its easier to do this outside of the sudachi_find_package +if (ENABLE_SDL3) + if (SUDACHI_USE_BUNDLED_SDL3) # Detect toolchain and platform if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64) - set(SDL2_VER "SDL2-2.28.2") + set(SDL3_VER "SDL3-3.1.2") else() - message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable SUDACHI_USE_BUNDLED_SDL2 and provide your own.") + message(FATAL_ERROR "No bundled SDL3 binaries for your toolchain. Disable SUDACHI_USE_BUNDLED_SDL3 and provide your own.") endif() - if (DEFINED SDL2_VER) - download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX) + if (DEFINED SDL3_VER) + download_bundled_external("sdl3/" ${SDL3_VER} SDL3_PREFIX) endif() - set(SDL2_FOUND YES) - set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers") - set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library") - set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll") + set(SDL3_FOUND YES) + set(SDL3_INCLUDE_DIR "${SDL3_PREFIX}/include" CACHE PATH "Path to SDL3 headers") + set(SDL3_LIBRARY "${SDL3_PREFIX}/lib/x64/SDL3.lib" CACHE PATH "Path to SDL3 library") + set(SDL3_DLL_DIR "${SDL3_PREFIX}/lib/x64/" CACHE PATH "Path to SDL3.dll") - add_library(SDL2::SDL2 INTERFACE IMPORTED) - target_link_libraries(SDL2::SDL2 INTERFACE "${SDL2_LIBRARY}") - target_include_directories(SDL2::SDL2 INTERFACE "${SDL2_INCLUDE_DIR}") - elseif (SUDACHI_USE_EXTERNAL_SDL2) - message(STATUS "Using SDL2 from externals.") + add_library(SDL3::SDL3 INTERFACE IMPORTED) + target_link_libraries(SDL3::SDL3 INTERFACE "${SDL3_LIBRARY}") + target_include_directories(SDL3::SDL3 INTERFACE "${SDL3_INCLUDE_DIR}") + elseif (SUDACHI_USE_EXTERNAL_SDL3) + message(STATUS "Using SDL3 from externals.") else() - find_package(SDL2 2.26.4 REQUIRED) + find_package(SDL3 3.1.2 REQUIRED) endif() endif() diff --git a/CMakeModules/CopySudachiSDLDeps.cmake b/CMakeModules/CopySudachiSDLDeps.cmake index 71d4952..4ab5874 100644 --- a/CMakeModules/CopySudachiSDLDeps.cmake +++ b/CMakeModules/CopySudachiSDLDeps.cmake @@ -4,5 +4,5 @@ function(copy_sudachi_SDL_deps target_dir) include(WindowsCopyFiles) set(DLL_DEST "$/") - windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll) + windows_copy_files(${target_dir} ${SDL3_DLL_DIR} ${DLL_DEST} SDL3.dll) endfunction(copy_sudachi_SDL_deps) diff --git a/CMakeModules/MinGWClangCross.cmake b/CMakeModules/MinGWClangCross.cmake index 5d3dae2..3cc3417 100644 --- a/CMakeModules/MinGWClangCross.cmake +++ b/CMakeModules/MinGWClangCross.cmake @@ -6,7 +6,7 @@ set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX}) -set(SDL2_PATH ${MINGW_PREFIX}) +set(SDL3_PATH ${MINGW_PREFIX}) set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-) # Specify the cross compiler diff --git a/CMakeModules/MinGWCross.cmake b/CMakeModules/MinGWCross.cmake index 61464f7..5cebe81 100644 --- a/CMakeModules/MinGWCross.cmake +++ b/CMakeModules/MinGWCross.cmake @@ -9,7 +9,7 @@ set(CMAKE_HOST_WIN32 TRUE) set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX}) -set(SDL2_PATH ${MINGW_PREFIX}) +set(SDL3_PATH ${MINGW_PREFIX}) set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-) # Specify the cross compiler diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 3fc53c2..f306de1 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -62,8 +62,8 @@ if (ENABLE_LIBUSB AND NOT TARGET libusb::usb) add_subdirectory(libusb) endif() -# SDL2 -if (SUDACHI_USE_EXTERNAL_SDL2) +# SDL3 +if (SUDACHI_USE_EXTERNAL_SDL3) if (NOT WIN32) # Sudachi 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) diff --git a/externals/SDL b/externals/SDL index cc016b0..84cb065 160000 --- a/externals/SDL +++ b/externals/SDL @@ -1 +1 @@ -Subproject commit cc016b0046d563287f0aa9f09b958b5e70d43696 +Subproject commit 84cb065da2fcae9482d2ab7702d8bc4218ff80ed diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb47b69..98df0d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -201,7 +201,7 @@ if (SUDACHI_TESTS) add_subdirectory(tests) endif() -if (ENABLE_SDL2) +if (ENABLE_SDL3) add_subdirectory(sudachi_cmd) endif() diff --git a/src/android/sudachi/build.gradle.kts b/src/android/sudachi/build.gradle.kts index b72192c..aa9cdd6 100644 --- a/src/android/sudachi/build.gradle.kts +++ b/src/android/sudachi/build.gradle.kts @@ -169,7 +169,7 @@ android { cmake { arguments( "-DENABLE_QT=0", // Don't use QT - "-DENABLE_SDL2=0", // Don't use SDL + "-DENABLE_SDL3=0", // Don't use SDL "-DENABLE_WEB_SERVICE=0", // Don't use telemetry "-DBUNDLE_SPEEX=ON", "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work diff --git a/src/android/sudachi/src/main/jni/default_ini.h b/src/android/sudachi/src/main/jni/default_ini.h index 23f6436..d9be273 100644 --- a/src/android/sudachi/src/main/jni/default_ini.h +++ b/src/android/sudachi/src/main/jni/default_ini.h @@ -382,7 +382,7 @@ bg_green = # Which audio output engine to use. # auto (default): Auto-select # cubeb: Cubeb audio engine (if available) -# sdl2: SDL2 audio engine (if available) +# sdl3: SDL3 audio engine (if available) # null: No audio output output_engine = diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 3d25dba..6d68b27 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -241,14 +241,14 @@ if (ENABLE_CUBEB) target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1) endif() -if (ENABLE_SDL2) +if (ENABLE_SDL3) target_sources(audio_core PRIVATE - sink/sdl2_sink.cpp - sink/sdl2_sink.h + sink/sdl3_sink.cpp + sink/sdl3_sink.h ) - 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() if (ANDROID) diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl3_sink.cpp similarity index 78% rename from src/audio_core/sink/sdl2_sink.cpp rename to src/audio_core/sink/sdl3_sink.cpp index 59b4943..e9e97c6 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl3_sink.cpp @@ -6,7 +6,7 @@ #include #include "audio_core/common/common.h" -#include "audio_core/sink/sdl2_sink.h" +#include "audio_core/sink/sdl3_sink.h" #include "audio_core/sink/sink_stream.h" #include "common/logging/log.h" #include "common/scope_exit.h" @@ -35,6 +35,29 @@ public: system_channels = system_channels_; device_channels = device_channels_; + SDL_AudioSpec spec{.format = SDL_AUDIO_S16, + .channels = static_cast(device_channels), + .freq = TargetSampleRate}; + + std::string device_name{output_device}; + SDL_AudioDeviceID devid = SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; + if (type == StreamType::In) { + device_name = input_device; + devid = SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; + } + + device = SDL_OpenAudioDevice(devid, &spec); + + if (device == 0) { + LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); + return; + } + + LOG_INFO(Service_Audio, "Opening SDL stream for: {}", SDL_GetAudioDeviceName(device)); + + stream = SDL_OpenAudioDeviceStream(device, &spec, &SDLSinkStream::DataCallback, this); + + /* SDL_AudioSpec spec; spec.freq = TargetSampleRate; spec.channels = static_cast(device_channels); @@ -66,6 +89,7 @@ public: "Opening SDL stream {} with: rate {} channels {} (system channels {}) " " samples {}", device, obtained.freq, obtained.channels, system_channels, obtained.samples); + */ } /** @@ -85,7 +109,7 @@ public: } Stop(); - SDL_ClearQueuedAudio(device); + SDL_ClearAudioStream(stream); SDL_CloseAudioDevice(device); } @@ -101,7 +125,7 @@ public: } paused = false; - SDL_PauseAudioDevice(device, 0); + SDL_ResumeAudioDevice(device); } /** @@ -112,7 +136,7 @@ public: return; } SignalPause(); - SDL_PauseAudioDevice(device, 1); + SDL_PauseAudioDevice(device); } private: @@ -124,7 +148,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(userdata); if (!impl) { @@ -133,7 +158,7 @@ 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)}; + const std::size_t num_frames{total_amount / num_channels / sizeof(s16)}; if (impl->type == StreamType::In) { std::span input_buffer{reinterpret_cast(stream), @@ -147,6 +172,7 @@ private: /// SDL device id of the opened input/output device SDL_AudioDeviceID device{}; + SDL_AudioStream* stream = nullptr; }; SDLSink::SDLSink(std::string_view target_device_name) { @@ -220,18 +246,34 @@ std::vector ListSDLSinkDevices(bool capture) { } } - const int device_count = SDL_GetNumAudioDevices(capture); - for (int i = 0; i < device_count; ++i) { - if (const char* name = SDL_GetAudioDeviceName(i, capture)) { - device_list.emplace_back(name); + switch (capture) { + case false: { + int count = 0; + SDL_GetAudioOutputDevices(&count); + for (int i = 0; i < count; ++i) { + if (const char* name = SDL_GetAudioDeviceName(i)) { + device_list.emplace_back(name); + } } + break; + } + case true: { + int count = 0; + SDL_GetAudioCaptureDevices(&count); + for (int i = 0; i < count; ++i) { + if (const char* name = SDL_GetAudioDeviceName(i)) { + device_list.emplace_back(name); + } + } + break; + } } return device_list; } bool IsSDLSuitable() { -#if !defined(HAVE_SDL2) +#if !defined(HAVE_SDL3) return false; #else // Check SDL can init @@ -246,6 +288,7 @@ bool IsSDLSuitable() { // We can set any latency frequency we want with SDL, so no need to check that. // Check we can open a device with standard parameters + /* SDL_AudioSpec spec; spec.freq = TargetSampleRate; spec.channels = 2u; @@ -256,6 +299,10 @@ bool IsSDLSuitable() { SDL_AudioSpec obtained; auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false); + */ + + SDL_AudioSpec spec{.format = SDL_AUDIO_S16, .channels = 2, .freq = TargetSampleRate}; + const auto device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec); if (device == 0) { LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}", diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl3_sink.h similarity index 100% rename from src/audio_core/sink/sdl2_sink.h rename to src/audio_core/sink/sdl3_sink.h diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 2c8d001..3b4b352 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -13,8 +13,8 @@ #ifdef HAVE_CUBEB #include "audio_core/sink/cubeb_sink.h" #endif -#ifdef HAVE_SDL2 -#include "audio_core/sink/sdl2_sink.h" +#ifdef HAVE_SDL3 +#include "audio_core/sink/sdl3_sink.h" #endif #include "audio_core/sink/null_sink.h" #include "common/logging/log.h" @@ -59,9 +59,9 @@ constexpr SinkDetails sink_details[] = { &IsCubebSuitable, }, #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 SinkDetails{ - Settings::AudioEngine::Sdl2, + Settings::AudioEngine::Sdl3, [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(device_id); }, diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index ae80f65..fb84b44 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -80,7 +80,7 @@ struct EnumMetadata { enum class AudioEngine : u32 { Auto, Cubeb, - Sdl2, + Sdl3, Null, Oboe, }; @@ -89,7 +89,7 @@ template <> inline std::vector> EnumMetadata::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}, }; } diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index 116ca38..00e40a9 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -51,7 +51,7 @@ void Config::Initialize(const std::string& config_name) { void Config::Initialize(const std::optional config_path) { const std::filesystem::path default_sdl_config_path = - FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl2-config.ini"; + FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl3-config.ini"; config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path)); void(FS::CreateParentDir(config_loc)); SetUpIni(); diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 43909af..941e7f6 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -47,7 +47,7 @@ else() ) endif() -if (ENABLE_SDL2) +if (ENABLE_SDL3) target_sources(input_common PRIVATE drivers/joycon.cpp drivers/joycon.h @@ -73,8 +73,8 @@ if (ENABLE_SDL2) helpers/joycon_protocol/rumble.cpp helpers/joycon_protocol/rumble.h ) - target_link_libraries(input_common PRIVATE SDL2::SDL2) - target_compile_definitions(input_common PRIVATE HAVE_SDL2) + target_link_libraries(input_common PRIVATE SDL3::SDL3) + target_compile_definitions(input_common PRIVATE HAVE_SDL3) endif() if (ENABLE_LIBUSB) diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 076c651..1271fea 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -13,7 +13,16 @@ namespace InputCommon { namespace { Common::UUID GetGUID(SDL_Joystick* joystick) { - const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + const SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick); + std::array 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}; +} + +Common::UUID GetGamepadGUID(SDL_Gamepad* gamepad) { + const auto guid = SDL_GetGamepadInstanceGUID(SDL_GetGamepadInstanceID(gamepad)); std::array data{}; std::memcpy(data.data(), guid.data, sizeof(data)); // Clear controller name crc @@ -22,20 +31,27 @@ Common::UUID GetGUID(SDL_Joystick* joystick) { } } // Anonymous namespace -static int SDLEventWatcher(void* user_data, SDL_Event* event) { +// static int SDLEventWatcher(void* user_data, SDL_Event* event) { +// auto* const sdl_state = static_cast(user_data); +// +// sdl_state->HandleGameControllerEvent(*event); +// +// return 0; +// } + +static int SDLGamepadEventWatcher(void* user_data, SDL_Event* event) { auto* const sdl_state = static_cast(user_data); - sdl_state->HandleGameControllerEvent(*event); + sdl_state->HandleGamepadEvent(*event); return 0; } class SDLJoystick { public: - SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, - SDL_GameController* game_controller) - : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, - sdl_controller{game_controller, &SDL_GameControllerClose} { + SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, SDL_Gamepad* gamepad) + : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_CloseJoystick}, + sdl_controller{gamepad, &SDL_CloseGamepad} { EnableMotion(); } @@ -43,18 +59,18 @@ public: if (!sdl_controller) { return; } - SDL_GameController* controller = sdl_controller.get(); + SDL_Gamepad* gamepad = sdl_controller.get(); if (HasMotion()) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, SDL_FALSE); + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, SDL_FALSE); } - has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; - has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; + has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL) == SDL_TRUE; + has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO) == SDL_TRUE; if (has_accel) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, SDL_TRUE); } if (has_gyro) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, SDL_TRUE); } } @@ -62,7 +78,7 @@ public: return has_gyro || has_accel; } - bool UpdateMotion(SDL_ControllerSensorEvent event) { + bool UpdateMotion(SDL_GamepadSensorEvent event) { constexpr float gravity_constant = 9.80665f; std::scoped_lock lock{mutex}; const u64 time_difference = event.timestamp - last_motion_update; @@ -134,11 +150,11 @@ public: f32 high_amplitude = vibration.high_amplitude * high_frequency_scale; if (sdl_controller) { - return SDL_GameControllerRumble(sdl_controller.get(), static_cast(low_amplitude), - static_cast(high_amplitude), - rumble_max_duration_ms) != -1; + return SDL_RumbleGamepad(sdl_controller.get(), static_cast(low_amplitude), + static_cast(high_amplitude), + rumble_max_duration_ms) != -1; } else if (sdl_joystick) { - return SDL_JoystickRumble(sdl_joystick.get(), static_cast(low_amplitude), + return SDL_RumbleJoystick(sdl_joystick.get(), static_cast(low_amplitude), static_cast(high_amplitude), rumble_max_duration_ms) != -1; } @@ -148,11 +164,11 @@ public: bool HasHDRumble() const { if (sdl_controller) { - const auto type = SDL_GameControllerGetType(sdl_controller.get()); - return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) || - (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || - (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || - (type == SDL_CONTROLLER_TYPE_PS5); + const auto type = SDL_GetGamepadType(sdl_controller.get()); + return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) || + (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || + (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || + (type == SDL_GAMEPAD_TYPE_PS5); } return false; } @@ -199,13 +215,13 @@ public: return sdl_joystick.get(); } - SDL_GameController* GetSDLGameController() const { + SDL_Gamepad* GetSDLGamepad() const { return sdl_controller.get(); } - void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + void SetSDLJoystick(SDL_Joystick* joystick, SDL_Gamepad* gamepad) { sdl_joystick.reset(joystick); - sdl_controller.reset(controller); + sdl_controller.reset(gamepad); } bool IsJoyconLeft() const { @@ -230,20 +246,29 @@ public: return false; } - Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) { - switch (battery_level) { - case SDL_JOYSTICK_POWER_EMPTY: + Common::Input::BatteryLevel GetBatteryLevel(int battery_level) { + int level = -1; + if (battery_level <= 5) + level = 0; + else if (battery_level <= 33) + level = 1; + else if (battery_level <= 66) + level = 2; + else + level = 3; + + switch (level) { + case 0: return Common::Input::BatteryLevel::Empty; - case SDL_JOYSTICK_POWER_LOW: + case 1: return Common::Input::BatteryLevel::Low; - case SDL_JOYSTICK_POWER_MEDIUM: + case 2: return Common::Input::BatteryLevel::Medium; - case SDL_JOYSTICK_POWER_FULL: - case SDL_JOYSTICK_POWER_MAX: + case 3: return Common::Input::BatteryLevel::Full; - case SDL_JOYSTICK_POWER_WIRED: - return Common::Input::BatteryLevel::Charging; - case SDL_JOYSTICK_POWER_UNKNOWN: + // case SDL_JOYSTICK_POWER_WIRED: + // return Common::Input::BatteryLevel::Charging; + case -1: default: return Common::Input::BatteryLevel::None; } @@ -251,28 +276,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; } @@ -284,8 +309,262 @@ public: private: Common::UUID guid; int port; - std::unique_ptr sdl_joystick; - std::unique_ptr sdl_controller; + std::unique_ptr sdl_joystick; + std::unique_ptr sdl_controller; + mutable std::mutex mutex; + + u64 last_motion_update{}; + std::size_t motion_error_count{}; + bool has_gyro{false}; + bool has_accel{false}; + bool has_vibration{false}; + bool is_vibration_tested{false}; + BasicMotion motion; +}; + +class SDLGamepad { +public: + SDLGamepad(Common::UUID guid_, int port_, SDL_Gamepad* gamepad) + : guid{guid_}, port{port_}, sdl_gamepad{gamepad, &SDL_CloseGamepad} { + EnableMotion(); + } + + void EnableMotion() { + if (!sdl_gamepad) + return; + + SDL_Gamepad* gamepad = sdl_gamepad.get(); + if (HasMotion()) { + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, SDL_FALSE); + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, SDL_FALSE); + } + + has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL) == SDL_TRUE; + has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO) == SDL_TRUE; + + if (has_accel) + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, SDL_TRUE); + + if (has_gyro) + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, SDL_TRUE); + } + + bool HasMotion() const { + return has_gyro || has_accel; + } + + 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; + switch (event.sensor) { + case SDL_SENSOR_ACCEL: { + motion.accel_x = -event.data[0] / gravity_constant; + motion.accel_y = event.data[2] / gravity_constant; + motion.accel_z = -event.data[1] / gravity_constant; + break; + } + case SDL_SENSOR_GYRO: { + motion.gyro_x = event.data[0] / (Common::PI * 2); + motion.gyro_y = -event.data[2] / (Common::PI * 2); + motion.gyro_z = event.data[1] / (Common::PI * 2); + break; + } + } + + // Ignore duplicated timestamps + if (time_difference == 0) + return false; + + // Motion data is invalid + if (motion.accel_x == 0 && motion.gyro_x == 0 && motion.accel_y == 0 && + motion.gyro_y == 0 && motion.accel_z == 0 && motion.gyro_z == 0) { + if (motion_error_count++ < 200) { + return false; + } + // Try restarting the sensor + motion_error_count = 0; + EnableMotion(); + return false; + } + + motion_error_count = 0; + motion.delta_timestamp = time_difference * 1000; + return true; + } + + const BasicMotion& GetMotion() const { + return motion; + } + + bool RumblePlay(const Common::Input::VibrationStatus vibration) { + constexpr u32 rumble_max_duration_ms = 2000; + constexpr f32 low_start_sensitivity_limit = 140.0; + constexpr f32 low_width_sensitivity_limit = 400.0; + constexpr f32 high_start_sensitivity_limit = 200.0; + constexpr f32 high_width_sensitivity_limit = 700.0; + // Try to provide some feeling of the frequency by reducing the amplitude depending on it. + f32 low_frequency_scale = 1.0; + if (vibration.low_frequency > low_start_sensitivity_limit) + low_frequency_scale = + std::max(1.0f - (vibration.low_frequency - low_start_sensitivity_limit) / + low_width_sensitivity_limit, + 0.3f); + + f32 low_amplitude = vibration.low_amplitude * low_frequency_scale; + + f32 high_frequency_scale = 1.0; + if (vibration.high_frequency > high_start_sensitivity_limit) + high_frequency_scale = + std::max(1.0f - (vibration.high_frequency - high_start_sensitivity_limit) / + high_width_sensitivity_limit, + 0.3f); + + f32 high_amplitude = vibration.high_amplitude * high_frequency_scale; + + if (sdl_gamepad) + return SDL_RumbleGamepad(sdl_gamepad.get(), static_cast(low_amplitude), + static_cast(high_amplitude), + rumble_max_duration_ms) != -1; + + return false; + } + + bool HasHDRumble() const { + if (sdl_gamepad) { + const auto type = SDL_GetGamepadType(sdl_gamepad.get()); + return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) || + (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || + (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || + (type == SDL_GAMEPAD_TYPE_PS5); + } + return false; + } + + void EnableVibration(bool is_enabled) { + has_vibration = is_enabled; + is_vibration_tested = true; + } + + bool HasVibration() const { + return has_vibration; + } + + bool IsVibrationTested() const { + return is_vibration_tested; + } + + /** + * The Pad identifier of the joystick + */ + const PadIdentifier GetPadIdentifier() const { + return { + .guid = guid, + .port = static_cast(port), + .pad = 0, + }; + } + + /** + * The guid of the joystick + */ + const Common::UUID& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Gamepad* GetSDLGamepad() const { + return sdl_gamepad.get(); + } + + void SetSDLGamepad(SDL_Gamepad* gamepad) { + sdl_gamepad.reset(gamepad); + } + + bool IsJoyconLeft() const { + const std::string controller_name = GetControllerName(); + if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) + return true; + + if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) + return true; + return false; + } + + bool IsJoyconRight() const { + const std::string controller_name = GetControllerName(); + if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) + return true; + + if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) + return true; + + return false; + } + + Common::Input::BatteryLevel GetBatteryLevel(int battery_level) { + int level = -1; + if (battery_level <= 5) + level = 0; + else if (battery_level <= 33) + level = 1; + else if (battery_level <= 66) + level = 2; + else + level = 3; + + switch (level) { + case 0: + return Common::Input::BatteryLevel::Empty; + case 1: + return Common::Input::BatteryLevel::Low; + case 2: + return Common::Input::BatteryLevel::Medium; + case 3: + return Common::Input::BatteryLevel::Full; + // case SDL_JOYSTICK_POWER_WIRED: + // return Common::Input::BatteryLevel::Charging; + case -1: + default: + return Common::Input::BatteryLevel::None; + } + } + + std::string GetControllerName() const { + if (sdl_gamepad) { + switch (SDL_GetGamepadType(sdl_gamepad.get())) { + case SDL_GAMEPAD_TYPE_XBOX360: + return "Xbox 360 Controller"; + case SDL_GAMEPAD_TYPE_XBOXONE: + return "Xbox One Controller"; + case SDL_GAMEPAD_TYPE_PS3: + return "DualShock 3 Controller"; + case SDL_GAMEPAD_TYPE_PS4: + return "DualShock 4 Controller"; + case SDL_GAMEPAD_TYPE_PS5: + return "DualSense Controller"; + default: + break; + } + const auto name = SDL_GetGamepadName(sdl_gamepad.get()); + if (name) + return name; + } + + return "Unknown"; + } + +private: + Common::UUID guid; + int port; + std::unique_ptr sdl_gamepad; mutable std::mutex mutex; u64 last_motion_update{}; @@ -316,12 +595,35 @@ std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const Common::UUID& return joystick_map[guid].emplace_back(std::move(joystick)); } +std::shared_ptr SDLDriver::GetSDLGamepadByGUID(const Common::UUID& guid, int port) { + std::scoped_lock lock{gamepad_map_mutex}; + const auto it = gamepad_map.find(guid); + + if (it != gamepad_map.end()) { + while (it->second.size() <= static_cast(port)) { + auto gamepad = + std::make_shared(guid, static_cast(it->second.size()), nullptr); + it->second.emplace_back(std::move(gamepad)); + } + + return it->second[static_cast(port)]; + } + + auto gamepad = std::make_shared(guid, 0, nullptr); + + return gamepad_map[guid].emplace_back(std::move(gamepad)); +} + std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { return GetSDLJoystickByGUID(Common::UUID{guid}, port); } +std::shared_ptr SDLDriver::GetSDLGamepadByGUID(const std::string& guid, int port) { + return GetSDLGamepadByGUID(Common::UUID{guid}, port); +} + std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + auto sdl_joystick = SDL_GetJoystickFromInstanceID(sdl_id); const auto guid = GetGUID(sdl_joystick); std::scoped_lock lock{joystick_map_mutex}; @@ -343,26 +645,50 @@ std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl return *vec_it; } +std::shared_ptr SDLDriver::GetSDLGamepadBySDLID(SDL_JoystickID sdl_id) { + auto sdl_gamepad = SDL_GetGamepadFromInstanceID(sdl_id); + const auto guid = GetGamepadGUID(sdl_gamepad); + + std::scoped_lock lock{gamepad_map_mutex}; + const auto map_it = gamepad_map.find(guid); + + if (map_it == gamepad_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if( + map_it->second.begin(), map_it->second.end(), + [&sdl_gamepad](const auto& gamepad) { return gamepad->GetSDLGamepad() == sdl_gamepad; }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; +} + void SDLDriver::InitJoystick(int joystick_index) { - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - SDL_GameController* sdl_gamecontroller = nullptr; + // SDL_Joystick* sdl_joystick = SDL_OpenJoystick(joystick_index); + // LOG_ERROR(Input, "error={}", SDL_GetError()); + SDL_Gamepad* sdl_gamepad = nullptr; - if (SDL_IsGameController(joystick_index)) { - sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + if (SDL_IsGamepad(joystick_index)) { + sdl_gamepad = SDL_OpenGamepad(joystick_index); } - if (!sdl_joystick) { - LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); - return; - } + // if (!sdl_joystick) { + // LOG_ERROR(Input, "Failed to open joystick {}, error={}", joystick_index, SDL_GetError()); + // return; + // } - const auto guid = GetGUID(sdl_joystick); + auto gamepad = SDL_GetGamepadJoystick(sdl_gamepad); + const auto guid = GetGUID(gamepad); 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); + CloseJoystick(gamepad); return; } } @@ -370,14 +696,14 @@ void SDLDriver::InitJoystick(int joystick_index) { 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); + CloseJoystick(gamepad); return; } } std::scoped_lock lock{joystick_map_mutex}; if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); + auto joystick = std::make_shared(guid, 0, gamepad, sdl_gamepad); PreSetController(joystick->GetPadIdentifier()); joystick->EnableMotion(); joystick_map[guid].emplace_back(std::move(joystick)); @@ -390,18 +716,77 @@ void SDLDriver::InitJoystick(int joystick_index) { [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); if (joystick_it != joystick_guid_list.end()) { - (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + (*joystick_it)->SetSDLJoystick(gamepad, sdl_gamepad); (*joystick_it)->EnableMotion(); return; } const int port = static_cast(joystick_guid_list.size()); - auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); + auto joystick = std::make_shared(guid, port, gamepad, sdl_gamepad); PreSetController(joystick->GetPadIdentifier()); joystick->EnableMotion(); joystick_guid_list.emplace_back(std::move(joystick)); } +void SDLDriver::InitGamepad(int gamepad_index) { + SDL_Gamepad* sdl_gamepad = nullptr; + // if (SDL_IsGamepad(gamepad_index)) { + sdl_gamepad = SDL_OpenGamepad(gamepad_index); + LOG_ERROR(Input, "SDL_OpenGamepad.error={}, name={}", SDL_GetError(), + SDL_GetGamepadName(sdl_gamepad)); + //} + + if (!sdl_gamepad) { + LOG_ERROR(Input, "Failed to open gamepad {}, error={}", gamepad_index, SDL_GetError()); + return; + } + + const auto guid = GetGamepadGUID(sdl_gamepad); + + 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 {}", gamepad_index); + CloseGamepad(sdl_gamepad); + 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 {}", gamepad_index); + CloseGamepad(sdl_gamepad); + return; + } + } + + std::scoped_lock lock{gamepad_map_mutex}; + if (gamepad_map.find(guid) == gamepad_map.end()) { + auto gamepad = std::make_shared(guid, 0, sdl_gamepad); + PreSetController(gamepad->GetPadIdentifier()); + gamepad->EnableMotion(); + gamepad_map[guid].emplace_back(std::move(gamepad)); + return; + } + + auto& gamepad_guid_list = gamepad_map[guid]; + const auto gamepad_it = + std::find_if(gamepad_guid_list.begin(), gamepad_guid_list.end(), + [](const auto& gamepad) { return !gamepad->GetSDLGamepad(); }); + + if (gamepad_it != gamepad_guid_list.end()) { + (*gamepad_it)->SetSDLGamepad(sdl_gamepad); + (*gamepad_it)->EnableMotion(); + return; + } + + const int port = static_cast(gamepad_guid_list.size()); + auto gamepad = std::make_shared(guid, port, sdl_gamepad); + PreSetController(gamepad->GetPadIdentifier()); + gamepad->EnableMotion(); + gamepad_guid_list.emplace_back(std::move(gamepad)); +} + void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { const auto guid = GetGUID(sdl_joystick); @@ -418,6 +803,21 @@ void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { } } +void SDLDriver::CloseGamepad(SDL_Gamepad* sdl_gamepad) { + const auto guid = GetGamepadGUID(sdl_gamepad); + + std::scoped_lock lock{gamepad_map_mutex}; + // This call to guid is safe since the joystick is guaranteed to be in the map + const auto& gamepad_guid_list = gamepad_map[guid]; + const auto gamepad_it = std::find_if( + gamepad_guid_list.begin(), gamepad_guid_list.end(), + [&sdl_gamepad](const auto& gamepad) { return gamepad->GetSDLGamepad() == sdl_gamepad; }); + + if (gamepad_it != gamepad_guid_list.end()) { + (*gamepad_it)->SetSDLGamepad(nullptr); + } +} + void SDLDriver::PumpEvents() const { if (initialized) { SDL_PumpEvents(); @@ -426,57 +826,131 @@ 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: { + if (auto joystick = GetSDLJoystickBySDLID(event.gsensor.which)) { + if (joystick->UpdateMotion(event.gsensor)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetMotion(identifier, 0, joystick->GetMotion()); } } break; } - case SDL_JOYBATTERYUPDATED: { + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: { if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); - SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level)); + int level = -1; + SDL_GetJoystickPowerInfo(joystick.get()->GetSDLJoystick(), &level); + SetBattery(identifier, joystick->GetBatteryLevel(level)); } break; } - case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + case SDL_EVENT_JOYSTICK_REMOVED: + case SDL_EVENT_GAMEPAD_REMOVED: + if (event.type == SDL_EVENT_GAMEPAD_REMOVED) { + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.gdevice.which); + CloseJoystick( + SDL_GetGamepadJoystick(SDL_GetGamepadFromInstanceID(event.gdevice.which))); + } else { + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_GetJoystickFromInstanceID(event.jdevice.which)); + } break; - case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); - InitJoystick(event.jdevice.which); + case SDL_EVENT_JOYSTICK_ADDED: + case SDL_EVENT_GAMEPAD_ADDED: + if (event.type == SDL_EVENT_GAMEPAD_ADDED) { + LOG_DEBUG(Input, "Controller connected with device index {}", event.gdevice.which); + InitJoystick(event.gdevice.which); + } else { + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + } + break; + } +} + +void SDLDriver::HandleGamepadEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_EVENT_GAMEPAD_BUTTON_UP: { + if (const auto gamepad = GetSDLGamepadBySDLID(event.gbutton.which)) { + const PadIdentifier identifier = gamepad->GetPadIdentifier(); + SetButton(identifier, event.gbutton.button, false); + } + break; + } + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { + if (const auto gamepad = GetSDLGamepadBySDLID(event.gbutton.which)) { + const PadIdentifier identifier = gamepad->GetPadIdentifier(); + SetButton(identifier, event.gbutton.button, true); + } + break; + } + case SDL_EVENT_JOYSTICK_HAT_MOTION: { + if (const auto gamepad = GetSDLGamepadBySDLID(event.gdevice.which)) { + const PadIdentifier identifier = gamepad->GetPadIdentifier(); + SetHatButton(identifier, event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_EVENT_JOYSTICK_AXIS_MOTION: { + if (const auto gamepad = GetSDLGamepadBySDLID(event.gdevice.which)) { + const PadIdentifier identifier = gamepad->GetPadIdentifier(); + SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); + } + break; + } + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: { + if (auto gamepad = GetSDLGamepadBySDLID(event.gsensor.which)) { + if (gamepad->UpdateMotion(event.gsensor)) { + const PadIdentifier identifier = gamepad->GetPadIdentifier(); + SetMotion(identifier, 0, gamepad->GetMotion()); + } + } + break; + } + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: { + if (auto gamepad = GetSDLGamepadBySDLID(event.jbattery.which)) { + const PadIdentifier identifier = gamepad->GetPadIdentifier(); + int level = -1; + SDL_GetGamepadPowerInfo(gamepad.get()->GetSDLGamepad(), &level); + SetBattery(identifier, gamepad->GetBatteryLevel(level)); + } + break; + } + case SDL_EVENT_GAMEPAD_REMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.gdevice.which); + CloseGamepad(SDL_GetGamepadFromInstanceID(event.gdevice.which)); + break; + case SDL_EVENT_GAMEPAD_ADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.gdevice.which); + InitGamepad(event.gdevice.which); break; } } @@ -486,6 +960,11 @@ void SDLDriver::CloseJoysticks() { joystick_map.clear(); } +void SDLDriver::CloseGamepads() { + std::scoped_lock lock{gamepad_map_mutex}; + gamepad_map.clear(); +} + SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { // Set our application name. Currently passed to DBus by SDL and visible to the user through // their desktop environment. @@ -497,7 +976,7 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en } // Prevent SDL from adding undesired axis - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); + SDL_SetHint("SDL_HINT_ACCELEROMETER_AS_JOYSTICK", "0"); // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); @@ -524,20 +1003,21 @@ 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"); + 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) < 0) { LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); return; } - SDL_AddEventWatch(&SDLEventWatcher, this); + // SDL_AddEventWatch(&SDLEventWatcher, this); + SDL_AddEventWatch(&SDLGamepadEventWatcher, this); initialized = true; if (start_thread) { @@ -552,19 +1032,30 @@ 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 count = 0; + // SDL_GetJoysticks(&count); + // for (int i = 0; i < count; ++i) { + // InitJoystick(i); + // } + + // int count = 0; + // SDL_GetGamepads(&count); + // LOG_ERROR(Input, "SDL_Gamepads.count={}", count); + // for (int i = 0; i < count; i++) { + // InitGamepad(i); + // } } SDLDriver::~SDLDriver() { - CloseJoysticks(); - SDL_DelEventWatch(&SDLEventWatcher, this); + // CloseJoysticks(); + CloseGamepads(); + // SDL_DelEventWatch(&SDLEventWatcher, this); + SDL_DelEventWatch(&SDLGamepadEventWatcher, 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 +1249,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(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(binding.input.hat.hat_mask)); } return {}; } @@ -795,12 +1286,12 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p } const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { + auto* gamepad = joystick->GetSDLGamepad(); + if (gamepad == nullptr) { return {}; } - // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. + // This list is missing ZL/ZR since those are not considered buttons in SDL Gamepad. // We will add those afterwards ButtonBindings switch_to_sdl_button; @@ -808,15 +1299,15 @@ 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 if (params.Has("guid2")) { const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - if (joystick2->GetSDLGameController() != nullptr) { + if (joystick2->GetSDLGamepad() != nullptr) { return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, switch_to_sdl_axis); } @@ -828,41 +1319,82 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p ButtonBindings SDLDriver::GetDefaultButtonBinding( const std::shared_ptr& 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}, + }; +} + +ButtonBindings SDLDriver::GetDefaultButtonBinding( + const std::shared_ptr& gamepad) const { + // Default SL/SR mapping for other controllers + 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 (gamepad->IsJoyconLeft()) { + sll_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE1; + srl_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; + } + if (gamepad->IsJoyconRight()) { + slr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2; + srr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; + } + + return { + 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_GAMEPAD_BUTTON_GUIDE}, + {Settings::NativeButton::Screenshot, SDL_GAMEPAD_BUTTON_MISC1}, }; } @@ -871,19 +1403,54 @@ ButtonMapping SDLDriver::GetSingleControllerMapping( const ZButtonBindings& switch_to_sdl_axis) const { ButtonMapping mapping; mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); - auto* controller = joystick->GetSDLGameController(); + auto* controller = joystick->GetSDLGamepad(); for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + + // const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); mapping.insert_or_assign( switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), **binding)); } for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + + // const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); mapping.insert_or_assign( switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), **binding)); + } + + return mapping; +} + +ButtonMapping SDLDriver::GetSingleControllerMapping( + const std::shared_ptr& gamepad, const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = gamepad->GetSDLGamepad(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + + // const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(gamepad->GetPort(), gamepad->GetGUID(), **binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + + // const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(gamepad->GetPort(), gamepad->GetGUID(), **binding)); } return mapping; @@ -895,34 +1462,91 @@ ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptrGetSDLGameController(); - auto* controller2 = joystick2->GetSDLGameController(); + auto* controller = joystick->GetSDLGamepad(); + auto* controller2 = joystick2->GetSDLGamepad(); for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller2, &count); + + // const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); mapping.insert_or_assign( switch_button, - BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), **binding)); continue; } - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + // const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); mapping.insert_or_assign( switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), **binding)); } for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller2, &count); + // const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); mapping.insert_or_assign( switch_button, - BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), **binding)); continue; } - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + // const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); mapping.insert_or_assign( switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), **binding)); + } + + return mapping; +} + +ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr& gamepad, + const std::shared_ptr& gamepad2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = gamepad->GetSDLGamepad(); + auto* controller2 = gamepad2->GetSDLGamepad(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + if (IsButtonOnLeftSide(switch_button)) { + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller2, &count); + + // const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(gamepad2->GetPort(), gamepad2->GetGUID(), **binding)); + continue; + } + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + // const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(gamepad->GetPort(), gamepad->GetGUID(), **binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + if (IsButtonOnLeftSide(switch_button)) { + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller2, &count); + // const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(gamepad2->GetPort(), gamepad2->GetGUID(), **binding)); + continue; + } + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + // const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(gamepad->GetPort(), gamepad->GetGUID(), **binding)); } return mapping; @@ -951,52 +1575,56 @@ AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& p } const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); + auto* controller = joystick->GetSDLGamepad(); if (controller == nullptr) { return {}; } AnalogMapping mapping = {}; - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + + // const auto& binding_left_x = + // SDL_GameControllerGetBindForAxis(controller, SDL_GAMEPAD_AXIS_LEFTX); + // const auto& binding_left_y = + // SDL_GameControllerGetBindForAxis(controller, 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)->input.axis.axis); + PreSetAxis(identifier, (*binding)->input.axis.axis); + const auto left_offset_x = -GetAxis(identifier, (*binding)->input.axis.axis); + const auto left_offset_y = GetAxis(identifier, (*binding)->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)->input.axis.axis, + (*binding)->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)->input.axis.axis); + PreSetAxis(identifier, (*binding)->input.axis.axis); + const auto left_offset_x = -GetAxis(identifier, (*binding)->input.axis.axis); + const auto left_offset_y = GetAxis(identifier, (*binding)->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)->input.axis.axis, + (*binding)->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 = + // SDL_GameControllerGetBindForAxis(controller, SDL_GAMEPAD_AXIS_RIGHTX); + // const auto& binding_right_y = + // SDL_GameControllerGetBindForAxis(controller, 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)->input.axis.axis); + PreSetAxis(identifier, (*binding)->input.axis.axis); + const auto right_offset_x = -GetAxis(identifier, (*binding)->input.axis.axis); + const auto right_offset_y = GetAxis(identifier, (*binding)->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)->input.axis.axis, + (*binding)->input.axis.axis, right_offset_x, right_offset_y)); return mapping; } @@ -1007,7 +1635,7 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p } const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); + auto* controller = joystick->GetSDLGamepad(); if (controller == nullptr) { return {}; } @@ -1095,26 +1723,30 @@ bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) { if (joystick == nullptr) { return false; } - auto* controller = joystick->GetSDLGameController(); + auto* controller = joystick->GetSDLGamepad(); if (controller == nullptr) { return false; } const auto& axis_x = params.Get("axis_x", 0); const auto& axis_y = params.Get("axis_y", 0); - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); - if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) { + int count = 0; + const auto binding = SDL_GetGamepadBindings(controller, &count); + + // const auto& binding_left_x = + // SDL_GameControllerGetBindForAxis(controller, SDL_GAMEPAD_AXIS_LEFTX); + // const auto& binding_right_x = + // SDL_GameControllerGetBindForAxis(controller, SDL_GAMEPAD_AXIS_RIGHTX); + // const auto& binding_left_y = + // SDL_GameControllerGetBindForAxis(controller, SDL_GAMEPAD_AXIS_LEFTY); + // const auto& binding_right_y = + // SDL_GameControllerGetBindForAxis(controller, SDL_GAMEPAD_AXIS_RIGHTY); + + if (axis_x != (*binding)->input.axis.axis && axis_x != (*binding)->input.axis.axis) { return false; } - if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) { + if (axis_y != (*binding)->input.axis.axis && axis_y != (*binding)->input.axis.axis) { return false; } return true; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index a140ad0..8980144 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -15,18 +15,14 @@ #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; +class SDLGamepad; -using ButtonBindings = - std::array, 20>; -using ZButtonBindings = - std::array, 2>; +using ButtonBindings = std::array, 20>; +using ZButtonBindings = std::array, 2>; class SDLDriver : public InputEngine { public: @@ -38,11 +34,13 @@ public: void PumpEvents() const; - /// Handle SDL_Events for joysticks from SDL_PollEvent + /// Handle SDL_Events for gamepads from SDL_PollEvent void HandleGameControllerEvent(const SDL_Event& event); + void HandleGamepadEvent(const SDL_Event& event); /// Get the nth joystick with the corresponding GUID std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + std::shared_ptr GetSDLGamepadBySDLID(SDL_JoystickID sdl_id); /** * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so @@ -50,6 +48,8 @@ public: */ std::shared_ptr GetSDLJoystickByGUID(const Common::UUID& guid, int port); std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); + std::shared_ptr GetSDLGamepadByGUID(const Common::UUID& guid, int port); + std::shared_ptr GetSDLGamepadByGUID(const std::string& guid, int port); std::vector GetInputDevices() const override; @@ -71,9 +71,12 @@ public: private: void InitJoystick(int joystick_index); void CloseJoystick(SDL_Joystick* sdl_joystick); + void InitGamepad(int gamepad_index); + void CloseGamepad(SDL_Gamepad* sdl_gamepad); /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); + void CloseGamepads(); /// Takes all vibrations from the queue and sends the command to the controller void SendVibrations(); @@ -88,8 +91,8 @@ 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; + Common::ParamPackage BuildParamPackageForBinding(int port, const Common::UUID& guid, + const SDL_GamepadBinding& binding) const; Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, int axis_y, float offset_x, @@ -97,17 +100,25 @@ private: /// Returns the default button bindings list ButtonBindings GetDefaultButtonBinding(const std::shared_ptr& joystick) const; + ButtonBindings GetDefaultButtonBinding(const std::shared_ptr& gamepad) const; /// Returns the button mappings from a single controller ButtonMapping GetSingleControllerMapping(const std::shared_ptr& joystick, const ButtonBindings& switch_to_sdl_button, const ZButtonBindings& switch_to_sdl_axis) const; + ButtonMapping GetSingleControllerMapping(const std::shared_ptr& gamepad, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const; /// Returns the button mappings from two different controllers ButtonMapping GetDualControllerMapping(const std::shared_ptr& joystick, const std::shared_ptr& joystick2, const ButtonBindings& switch_to_sdl_button, const ZButtonBindings& switch_to_sdl_axis) const; + ButtonMapping GetDualControllerMapping(const std::shared_ptr& gamepad, + const std::shared_ptr& gamepad2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const; /// Returns true if the button is on the left joycon bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; @@ -118,6 +129,8 @@ private: /// Map of GUID of a list of corresponding virtual Joysticks std::unordered_map>> joystick_map; std::mutex joystick_map_mutex; + std::unordered_map>> gamepad_map; + std::mutex gamepad_map_mutex; bool start_thread = false; std::atomic initialized = false; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 62a7ae4..97a86a7 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -22,7 +22,7 @@ #ifdef HAVE_LIBUSB #include "input_common/drivers/gc_adapter.h" #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 #include "input_common/drivers/joycon.h" #include "input_common/drivers/sdl_driver.h" #endif @@ -87,7 +87,7 @@ struct InputSubsystem::Impl { #endif RegisterEngine("virtual_amiibo", virtual_amiibo); RegisterEngine("virtual_gamepad", virtual_gamepad); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 RegisterEngine("sdl", sdl); RegisterEngine("joycon", joycon); #endif @@ -121,7 +121,7 @@ struct InputSubsystem::Impl { #endif UnregisterEngine(virtual_amiibo); UnregisterEngine(virtual_gamepad); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 UnregisterEngine(sdl); UnregisterEngine(joycon); #endif @@ -151,7 +151,7 @@ struct InputSubsystem::Impl { #endif auto udp_devices = udp_client->GetInputDevices(); devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 auto joycon_devices = joycon->GetInputDevices(); devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); auto sdl_devices = sdl->GetInputDevices(); @@ -186,7 +186,7 @@ struct InputSubsystem::Impl { if (engine == udp_client->GetEngineName()) { return udp_client; } -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return sdl; } @@ -277,7 +277,7 @@ struct InputSubsystem::Impl { if (engine == virtual_gamepad->GetEngineName()) { return true; } -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return true; } @@ -298,7 +298,7 @@ struct InputSubsystem::Impl { gcadapter->BeginConfiguration(); #endif udp_client->BeginConfiguration(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->BeginConfiguration(); joycon->BeginConfiguration(); #endif @@ -314,7 +314,7 @@ struct InputSubsystem::Impl { gcadapter->EndConfiguration(); #endif udp_client->EndConfiguration(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->EndConfiguration(); joycon->EndConfiguration(); #endif @@ -322,7 +322,7 @@ struct InputSubsystem::Impl { void PumpEvents() const { update_engine->PumpEvents(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->PumpEvents(); #endif } @@ -347,7 +347,7 @@ struct InputSubsystem::Impl { std::shared_ptr gcadapter; #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 std::shared_ptr sdl; std::shared_ptr joycon; #endif diff --git a/src/sudachi/CMakeLists.txt b/src/sudachi/CMakeLists.txt index a86f902..5d72f09 100644 --- a/src/sudachi/CMakeLists.txt +++ b/src/sudachi/CMakeLists.txt @@ -453,9 +453,9 @@ if (SUDACHI_USE_BUNDLED_QT AND QT_VERSION VERSION_LESS 6) copy_sudachi_Qt5_deps(sudachi) endif() -if (ENABLE_SDL2) - target_link_libraries(sudachi PRIVATE SDL2::SDL2) - target_compile_definitions(sudachi PRIVATE HAVE_SDL2) +if (ENABLE_SDL3) + target_link_libraries(sudachi PRIVATE SDL3::SDL3) + target_compile_definitions(sudachi PRIVATE HAVE_SDL3) endif() if (MSVC) diff --git a/src/sudachi/main.cpp b/src/sudachi/main.cpp index 178e038..2dcc898 100644 --- a/src/sudachi/main.cpp +++ b/src/sudachi/main.cpp @@ -87,7 +87,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include #include -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 #include // For SDL ScreenSaver functions #endif @@ -468,7 +468,7 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulk VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle()); } -#if defined(HAVE_SDL2) && !defined(_WIN32) +#if defined(HAVE_SDL3) && !defined(_WIN32) SDL_InitSubSystem(SDL_INIT_VIDEO); // Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to @@ -1760,7 +1760,7 @@ void GMainWindow::OnSigInterruptNotifierActivated() { void GMainWindow::PreventOSSleep() { #ifdef _WIN32 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); -#elif defined(HAVE_SDL2) +#elif defined(HAVE_SDL3) SDL_DisableScreenSaver(); #endif } @@ -1768,7 +1768,7 @@ void GMainWindow::PreventOSSleep() { void GMainWindow::AllowOSSleep() { #ifdef _WIN32 SetThreadExecutionState(ES_CONTINUOUS); -#elif defined(HAVE_SDL2) +#elif defined(HAVE_SDL3) SDL_EnableScreenSaver(); #endif } diff --git a/src/sudachi_cmd/CMakeLists.txt b/src/sudachi_cmd/CMakeLists.txt index 9be012c..b3c9eeb 100644 --- a/src/sudachi_cmd/CMakeLists.txt +++ b/src/sudachi_cmd/CMakeLists.txt @@ -13,14 +13,14 @@ function(create_resource file output filename) endfunction() add_executable(sudachi-cmd - emu_window/emu_window_sdl2.cpp - emu_window/emu_window_sdl2.h - emu_window/emu_window_sdl2_gl.cpp - emu_window/emu_window_sdl2_gl.h - emu_window/emu_window_sdl2_null.cpp - emu_window/emu_window_sdl2_null.h - emu_window/emu_window_sdl2_vk.cpp - emu_window/emu_window_sdl2_vk.h + emu_window/emu_window_sdl3.cpp + emu_window/emu_window_sdl3.h + emu_window/emu_window_sdl3_gl.cpp + emu_window/emu_window_sdl3_gl.h + emu_window/emu_window_sdl3_null.cpp + emu_window/emu_window_sdl3_null.h + emu_window/emu_window_sdl3_vk.cpp + emu_window/emu_window_sdl3_vk.h precompiled_headers.h sdl_config.cpp sdl_config.h @@ -38,7 +38,7 @@ target_link_libraries(sudachi-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads create_resource("../../dist/sudachi.bmp" "sudachi_cmd/sudachi_icon.h" "sudachi_icon") target_include_directories(sudachi-cmd PRIVATE ${RESOURCES_DIR}) -target_link_libraries(sudachi-cmd PRIVATE SDL2::SDL2 Vulkan::Headers) +target_link_libraries(sudachi-cmd PRIVATE SDL3::SDL3 Vulkan::Headers) if(UNIX AND NOT APPLE) install(TARGETS sudachi-cmd) diff --git a/src/sudachi_cmd/config.cpp b/src/sudachi_cmd/config.cpp index 646f0a8..f6730c0 100644 --- a/src/sudachi_cmd/config.cpp +++ b/src/sudachi_cmd/config.cpp @@ -19,27 +19,27 @@ namespace FS = Common::FS; const std::filesystem::path default_config_path = - FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl2-config.ini"; + FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl3-config.ini"; Config::Config(std::optional config_path) - : sdl2_config_loc{config_path.value_or(default_config_path)}, - sdl2_config{std::make_unique(FS::PathToUTF8String(sdl2_config_loc))} { + : sdl3_config_loc{config_path.value_or(default_config_path)}, + sdl3_config{std::make_unique(FS::PathToUTF8String(sdl3_config_loc))} { Reload(); } Config::~Config() = default; bool Config::LoadINI(const std::string& default_contents, bool retry) { - const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc); - if (sdl2_config->ParseError() < 0) { + const auto config_loc_str = FS::PathToUTF8String(sdl3_config_loc); + if (sdl3_config->ParseError() < 0) { if (retry) { LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", config_loc_str); - void(FS::CreateParentDir(sdl2_config_loc)); - void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents)); + void(FS::CreateParentDir(sdl3_config_loc)); + void(FS::WriteStringToFile(sdl3_config_loc, FS::FileType::TextFile, default_contents)); - sdl2_config = std::make_unique(config_loc_str); + sdl3_config = std::make_unique(config_loc_str); return LoadINI(default_contents, false); } @@ -80,7 +80,7 @@ static const std::array, Settings::NativeAnalog::NumAnalogs> template <> void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { - std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault()); + std::string setting_value = sdl3_config->Get(group, setting.GetLabel(), setting.GetDefault()); if (setting_value.empty()) { setting_value = setting.GetDefault(); } @@ -89,12 +89,12 @@ void Config::ReadSetting(const std::string& group, Settings::Setting void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { - setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); + setting = sdl3_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); } template void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { - setting = static_cast(sdl2_config->GetInteger(group, setting.GetLabel(), + setting = static_cast(sdl3_config->GetInteger(group, setting.GetLabel(), static_cast(setting.GetDefault()))); } @@ -109,7 +109,7 @@ void Config::ReadCategory(Settings::Category category) { } }(); std::string setting_value = - sdl2_config->Get(category_name, setting->GetLabel(), setting->DefaultToString()); + sdl3_config->Get(category_name, setting->GetLabel(), setting->DefaultToString()); setting->LoadString(setting_value); } } @@ -125,7 +125,7 @@ void Config::ReadValues() { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); player.buttons[i] = - sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param); + sdl3_config->Get(group, Settings::NativeButton::mapping[i], default_param); if (player.buttons[i].empty()) { player.buttons[i] = default_param; } @@ -136,7 +136,7 @@ void Config::ReadValues() { default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); player.analogs[i] = - sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param); + sdl3_config->Get(group, Settings::NativeAnalog::mapping[i], default_param); if (player.analogs[i].empty()) { player.analogs[i] = default_param; } @@ -148,18 +148,18 @@ void Config::ReadValues() { auto& player_motions = player.motions[i]; player_motions = - sdl2_config->Get(group, Settings::NativeMotion::mapping[i], default_param); + sdl3_config->Get(group, Settings::NativeMotion::mapping[i], default_param); if (player_motions.empty()) { player_motions = default_param; } } - player.connected = sdl2_config->GetBoolean(group, "connected", false); + player.connected = sdl3_config->GetBoolean(group, "connected", false); } for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - Settings::values.debug_pad_buttons[i] = sdl2_config->Get( + Settings::values.debug_pad_buttons[i] = sdl3_config->Get( "ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i], default_param); if (Settings::values.debug_pad_buttons[i].empty()) @@ -170,7 +170,7 @@ void Config::ReadValues() { std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); - Settings::values.debug_pad_analogs[i] = sdl2_config->Get( + Settings::values.debug_pad_analogs[i] = sdl3_config->Get( "ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i], default_param); if (Settings::values.debug_pad_analogs[i].empty()) @@ -178,24 +178,24 @@ void Config::ReadValues() { } Settings::values.touchscreen.enabled = - sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); + sdl3_config->GetBoolean("ControlsGeneral", "touch_enabled", true); Settings::values.touchscreen.rotation_angle = - sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0); + sdl3_config->GetInteger("ControlsGeneral", "touch_angle", 0); Settings::values.touchscreen.diameter_x = - sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); + sdl3_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); Settings::values.touchscreen.diameter_y = - sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); + sdl3_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); int num_touch_from_button_maps = - sdl2_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); + sdl3_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); if (num_touch_from_button_maps > 0) { for (int i = 0; i < num_touch_from_button_maps; ++i) { Settings::TouchFromButtonMap map; - map.name = sdl2_config->Get("ControlsGeneral", + map.name = sdl3_config->Get("ControlsGeneral", std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_name"), "default"); - const int num_touch_maps = sdl2_config->GetInteger( + const int num_touch_maps = sdl3_config->GetInteger( "ControlsGeneral", std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"), 0); @@ -203,7 +203,7 @@ void Config::ReadValues() { for (int j = 0; j < num_touch_maps; ++j) { std::string touch_mapping = - sdl2_config->Get("ControlsGeneral", + sdl3_config->Get("ControlsGeneral", std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_bind_") + std::to_string(j), ""); @@ -239,28 +239,28 @@ void Config::ReadValues() { // Data Storage FS::SetSudachiPath(FS::SudachiPath::NANDDir, - sdl2_config->Get("Data Storage", "nand_directory", + sdl3_config->Get("Data Storage", "nand_directory", FS::GetSudachiPathString(FS::SudachiPath::NANDDir))); FS::SetSudachiPath(FS::SudachiPath::SDMCDir, - sdl2_config->Get("Data Storage", "sdmc_directory", + sdl3_config->Get("Data Storage", "sdmc_directory", FS::GetSudachiPathString(FS::SudachiPath::SDMCDir))); FS::SetSudachiPath(FS::SudachiPath::LoadDir, - sdl2_config->Get("Data Storage", "load_directory", + sdl3_config->Get("Data Storage", "load_directory", FS::GetSudachiPathString(FS::SudachiPath::LoadDir))); FS::SetSudachiPath(FS::SudachiPath::DumpDir, - sdl2_config->Get("Data Storage", "dump_directory", + sdl3_config->Get("Data Storage", "dump_directory", FS::GetSudachiPathString(FS::SudachiPath::DumpDir))); // Debugging Settings::values.record_frame_times = - sdl2_config->GetBoolean("Debugging", "record_frame_times", false); + sdl3_config->GetBoolean("Debugging", "record_frame_times", false); - const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); + const auto title_list = sdl3_config->Get("AddOns", "title_ids", ""); std::stringstream ss(title_list); std::string line; while (std::getline(ss, line, '|')) { const auto title_id = std::strtoul(line.c_str(), nullptr, 16); - const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); + const auto disabled_list = sdl3_config->Get("AddOns", "disabled_" + line, ""); std::stringstream inner_ss(disabled_list); std::string inner_line; @@ -274,6 +274,6 @@ void Config::ReadValues() { } void Config::Reload() { - LoadINI(DefaultINI::sdl2_config_file); + LoadINI(DefaultINI::sdl3_config_file); ReadValues(); } diff --git a/src/sudachi_cmd/config.h b/src/sudachi_cmd/config.h index cbcfb55..5e00b70 100644 --- a/src/sudachi_cmd/config.h +++ b/src/sudachi_cmd/config.h @@ -13,8 +13,8 @@ class INIReader; class Config { - std::filesystem::path sdl2_config_loc; - std::unique_ptr sdl2_config; + std::filesystem::path sdl3_config_loc; + std::unique_ptr sdl3_config; bool LoadINI(const std::string& default_contents = "", bool retry = true); void ReadValues(); @@ -27,7 +27,7 @@ public: private: /** - * Applies a value read from the sdl2_config to a Setting. + * Applies a value read from the sdl3_config to a Setting. * * @param group The name of the INI group * @param setting The sudachi setting to modify diff --git a/src/sudachi_cmd/default_ini.h b/src/sudachi_cmd/default_ini.h index 6c412c0..7dc38cd 100644 --- a/src/sudachi_cmd/default_ini.h +++ b/src/sudachi_cmd/default_ini.h @@ -5,7 +5,7 @@ namespace DefaultINI { -const char* sdl2_config_file = +const char* sdl3_config_file = R"( [ControlsP0] # The input devices and parameters for each Switch native input @@ -424,7 +424,7 @@ bg_green = # Which audio output engine to use. # auto (default): Auto-select # cubeb: Cubeb audio engine (if available) -# sdl2: SDL2 audio engine (if available) +# sdl3: SDL3 audio engine (if available) # null: No audio output output_engine = diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/sudachi_cmd/emu_window/emu_window_sdl2_vk.cpp deleted file mode 100644 index 5639c13..0000000 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2_vk.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 sudachi Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include - -#include "common/logging/log.h" -#include "common/scm_rev.h" -#include "video_core/renderer_vulkan/renderer_vulkan.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2_vk.h" - -#include -#include - -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("sudachi {} | {}-{} (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(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(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(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, "sudachi Version: {} | {}-{} (Vulkan)", Common::g_build_name, - Common::g_scm_branch, Common::g_scm_desc); -} - -EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default; - -std::unique_ptr EmuWindow_SDL2_VK::CreateSharedContext() const { - return std::make_unique(); -} diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2.cpp b/src/sudachi_cmd/emu_window/emu_window_sdl3.cpp similarity index 70% rename from src/sudachi_cmd/emu_window/emu_window_sdl2.cpp rename to src/sudachi_cmd/emu_window/emu_window_sdl3.cpp index 0bc387b..e9772f3 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3.cpp @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#define SDL_MAIN_HANDLED #include +#include #include "common/logging/log.h" #include "common/scm_rev.h" @@ -13,26 +15,27 @@ #include "input_common/drivers/mouse.h" #include "input_common/drivers/touch_screen.h" #include "input_common/main.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3.h" #include "sudachi_cmd/sudachi_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) < 0) { + 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; @@ -49,7 +52,7 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons } } -std::pair EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const { +std::pair EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const { int w, h; SDL_GetWindowSize(render_window, &w, &h); const float fx = static_cast(touch_x) / w; @@ -58,7 +61,7 @@ std::pair EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y return {std::clamp(fx, 0.0f, 1.0f), std::clamp(fy, 0.0f, 1.0f)}; } -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) { const auto [touch_x, touch_y] = MouseToTouchPos(x, y); @@ -70,26 +73,26 @@ 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) { const auto [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) { +void EmuWindow_SDL3::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { input_subsystem->GetKeyboard()->PressKey(static_cast(key)); } else if (state == SDL_RELEASED) { @@ -97,32 +100,33 @@ void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { } } -bool EmuWindow_SDL2::IsOpen() const { +bool EmuWindow_SDL3::IsOpen() const { return is_open; } -bool EmuWindow_SDL2::IsShown() const { +bool EmuWindow_SDL3::IsShown() const { return is_shown; } -void EmuWindow_SDL2::OnResize() { +void EmuWindow_SDL3::OnResize() { int width, height; - SDL_GL_GetDrawableSize(render_window, &width, &height); + SDL_GetWindowSizeInPixels(render_window, &width, &height); UpdateCurrentFramebufferLayout(width, height); } -void EmuWindow_SDL2::ShowCursor(bool show_cursor) { - SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE); +void EmuWindow_SDL3::ShowCursor(bool show_cursor) { + show_cursor ? SDL_ShowCursor() : SDL_HideCursor(); } -void EmuWindow_SDL2::Fullscreen() { - SDL_DisplayMode display_mode; +void EmuWindow_SDL3::Fullscreen() { + const SDL_DisplayMode* display_mode = nullptr; switch (Settings::values.fullscreen_mode.GetValue()) { case Settings::FullscreenMode::Exclusive: - // Set window size to render size before entering fullscreen -- SDL2 does not resize window + // Set window size to render size before entering fullscreen -- SDL3 does not resize window // to display dimensions automatically in this mode. - if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) { - SDL_SetWindowSize(render_window, display_mode.w, display_mode.h); + display_mode = SDL_GetDesktopDisplayMode(0); + if (display_mode == NULL) { + SDL_SetWindowSize(render_window, display_mode->w, display_mode->h); } else { LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError()); } @@ -135,7 +139,7 @@ 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) { + if (SDL_SetWindowFullscreen(render_window, SDL_TRUE) == 0) { return; } @@ -150,7 +154,7 @@ void EmuWindow_SDL2::Fullscreen() { } } -void EmuWindow_SDL2::WaitEvent() { +void EmuWindow_SDL3::WaitEvent() { // Called on main thread SDL_Event event; @@ -168,59 +172,55 @@ 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_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + OnResize(); break; - case SDL_KEYDOWN: - case SDL_KEYUP: + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_EXPOSED: + is_shown = event.window.type == SDL_EVENT_WINDOW_EXPOSED; + 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(event.key.keysym.scancode), event.key.state); break; - case SDL_MOUSEMOTION: + 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); + OnMouseMotion(static_cast(event.motion.x), static_cast(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.state, static_cast(event.button.x), + static_cast(event.button.y)); } break; - case SDL_FINGERDOWN: + case SDL_EVENT_FINGER_DOWN: OnFingerDown(event.tfinger.x, event.tfinger.y, - static_cast(event.tfinger.touchId)); + static_cast(event.tfinger.touchID)); break; - case SDL_FINGERMOTION: + case SDL_EVENT_FINGER_MOTION: OnFingerMotion(event.tfinger.x, event.tfinger.y, - static_cast(event.tfinger.touchId)); + static_cast(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: break; } - const u32 current_time = SDL_GetTicks(); + const u64 current_time = SDL_GetTicks(); if (current_time > last_time + 2000) { const auto results = system.GetAndResetPerfStats(); const auto title = @@ -233,22 +233,23 @@ void EmuWindow_SDL2::WaitEvent() { } // Credits to Samantas5855 and others for this function. -void EmuWindow_SDL2::SetWindowIcon() { - SDL_RWops* const sudachi_icon_stream = SDL_RWFromConstMem((void*)sudachi_icon, sudachi_icon_size); +void EmuWindow_SDL3::SetWindowIcon() { + SDL_IOStream* const sudachi_icon_stream = + SDL_IOFromConstMem((void*)sudachi_icon, sudachi_icon_size); if (sudachi_icon_stream == nullptr) { LOG_WARNING(Frontend, "Failed to create sudachi icon stream."); return; } - SDL_Surface* const window_icon = SDL_LoadBMP_RW(sudachi_icon_stream, 1); + SDL_Surface* const window_icon = SDL_LoadBMP_IO(sudachi_icon_stream, 1); if (window_icon == nullptr) { LOG_WARNING(Frontend, "Failed to read BMP from stream."); return; } // The icon is attached to the window pointer SDL_SetWindowIcon(render_window, window_icon); - SDL_FreeSurface(window_icon); + SDL_DestroySurface(window_icon); } -void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { +void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); } diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2.h b/src/sudachi_cmd/emu_window/emu_window_sdl3.h similarity index 92% rename from src/sudachi_cmd/emu_window/emu_window_sdl2.h rename to src/sudachi_cmd/emu_window/emu_window_sdl3.h index 0c9d057..54b9254 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2.h +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3.h @@ -19,10 +19,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; @@ -79,11 +79,11 @@ 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 - u32 last_time = 0; + u64 last_time = 0; /// Input subsystem to use with this window. InputCommon::InputSubsystem* input_subsystem; diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/sudachi_cmd/emu_window/emu_window_sdl3_gl.cpp similarity index 79% rename from src/sudachi_cmd/emu_window/emu_window_sdl2_gl.cpp rename to src/sudachi_cmd/emu_window/emu_window_sdl3_gl.cpp index ce37318..b7b5d98 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3_gl.cpp @@ -16,8 +16,8 @@ #include "common/string_util.h" #include "core/core.h" #include "input_common/main.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3_gl.h" #include "video_core/renderer_base.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2_gl.h" class SDLGLContext : public Core::Frontend::GraphicsContext { public: @@ -55,7 +55,7 @@ private: bool is_current = false; }; -bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { +bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() { std::vector unsupported_ext; // Extensions required to support some texture formats. @@ -73,9 +73,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); @@ -93,14 +93,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste std::string window_title = fmt::format("sudachi {} | {}-{}", 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); 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); } @@ -117,15 +114,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(SDL_GL_GetProcAddress))) { + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); exit(1); } @@ -138,16 +135,16 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste OnResize(); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); SDL_PumpEvents(); - LOG_INFO(Frontend, "sudachi Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, - Common::g_scm_desc); + LOG_INFO(Frontend, "sudachi Version: {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); Settings::LogSettings(); } -EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { +EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() { core_context.reset(); SDL_GL_DeleteContext(window_context); } -std::unique_ptr EmuWindow_SDL2_GL::CreateSharedContext() const { +std::unique_ptr EmuWindow_SDL3_GL::CreateSharedContext() const { return std::make_unique(render_window); } diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2_gl.h b/src/sudachi_cmd/emu_window/emu_window_sdl3_gl.h similarity index 80% rename from src/sudachi_cmd/emu_window/emu_window_sdl2_gl.h rename to src/sudachi_cmd/emu_window/emu_window_sdl3_gl.h index da5ffc4..3f409ea 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3_gl.h @@ -5,7 +5,7 @@ #include #include "core/frontend/emu_window.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -15,11 +15,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 CreateSharedContext() const override; diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2_null.cpp b/src/sudachi_cmd/emu_window/emu_window_sdl3_null.cpp similarity index 66% rename from src/sudachi_cmd/emu_window/emu_window_sdl2_null.cpp rename to src/sudachi_cmd/emu_window/emu_window_sdl3_null.cpp index e1db857..e8d1517 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2_null.cpp +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3_null.cpp @@ -9,10 +9,10 @@ #include "common/logging/log.h" #include "common/scm_rev.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2_null.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3_null.h" #include "video_core/renderer_null/renderer_null.h" -#ifdef SUDACHI_USE_EXTERNAL_SDL2 +#ifdef SUDACHI_USE_EXTERNAL_SDL3 // Include this before SDL.h to prevent the external from including a dummy #define USING_GENERATED_CONFIG_H #include @@ -20,16 +20,14 @@ #include -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("sudachi {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); - render_window = - SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + render_window = SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, SDL_WINDOW_RESIZABLE); SetWindowIcon(); @@ -45,8 +43,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 EmuWindow_SDL2_Null::CreateSharedContext() const { +std::unique_ptr EmuWindow_SDL3_Null::CreateSharedContext() const { return std::make_unique(); } diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2_null.h b/src/sudachi_cmd/emu_window/emu_window_sdl3_null.h similarity index 68% rename from src/sudachi_cmd/emu_window/emu_window_sdl2_null.h rename to src/sudachi_cmd/emu_window/emu_window_sdl3_null.h index cc9c709..8eaccf6 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2_null.h +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3_null.h @@ -6,7 +6,7 @@ #include #include "core/frontend/emu_window.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -16,11 +16,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 CreateSharedContext() const override; }; diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl3_vk.cpp b/src/sudachi_cmd/emu_window/emu_window_sdl3_vk.cpp new file mode 100644 index 0000000..9814273 --- /dev/null +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3_vk.cpp @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2018 sudachi Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3_vk.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" + +#include + +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("sudachi {} | {}-{} (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_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); + } + +#if defined(SDL_PLATFORM_WIN32) + HWND hwnd = (HWND)SDL_GetProperty(SDL_GetWindowProperties(render_window), + SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + window_info.type = Core::Frontend::WindowSystemType::Windows; + window_info.render_surface = reinterpret_cast(hwnd); +#endif + + /* + 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(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(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(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, "sudachi Version: {} | {}-{} (Vulkan)", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); +} + +EmuWindow_SDL3_VK::~EmuWindow_SDL3_VK() = default; + +std::unique_ptr EmuWindow_SDL3_VK::CreateSharedContext() const { + return std::make_unique(); +} diff --git a/src/sudachi_cmd/emu_window/emu_window_sdl2_vk.h b/src/sudachi_cmd/emu_window/emu_window_sdl3_vk.h similarity index 68% rename from src/sudachi_cmd/emu_window/emu_window_sdl2_vk.h rename to src/sudachi_cmd/emu_window/emu_window_sdl3_vk.h index 266c38e..41fffeb 100644 --- a/src/sudachi_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/sudachi_cmd/emu_window/emu_window_sdl3_vk.h @@ -6,7 +6,7 @@ #include #include "core/frontend/emu_window.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -16,11 +16,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 CreateSharedContext() const override; }; diff --git a/src/sudachi_cmd/sudachi.cpp b/src/sudachi_cmd/sudachi.cpp index 851c8fe..6c0bc0d 100644 --- a/src/sudachi_cmd/sudachi.cpp +++ b/src/sudachi_cmd/sudachi.cpp @@ -34,11 +34,11 @@ #include "input_common/main.h" #include "network/network.h" #include "sdl_config.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3_gl.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3_null.h" +#include "sudachi_cmd/emu_window/emu_window_sdl3_vk.h" #include "video_core/renderer_base.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2_gl.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2_null.h" -#include "sudachi_cmd/emu_window/emu_window_sdl2_vk.h" #ifdef _WIN32 // windows.h needs to be included before shellapi.h @@ -346,16 +346,16 @@ int main(int argc, char** argv) { // Apply the command line arguments system.ApplySettings(); - std::unique_ptr emu_window; + std::unique_ptr emu_window; switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; case Settings::RendererBackend::Vulkan: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; case Settings::RendererBackend::Null: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; } @@ -394,11 +394,12 @@ int main(int argc, char** argv) { static_cast(Core::SystemResultStatus::ErrorLoader)) { const u16 loader_id = static_cast(Core::SystemResultStatus::ErrorLoader); const u16 error_id = static_cast(load_result) - loader_id; - LOG_CRITICAL(Frontend, - "While attempting to load the ROM requested, an error occurred. Please " - "refer to the sudachi wiki for more information or the sudachi discord for " - "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", - loader_id, error_id, static_cast(error_id)); + LOG_CRITICAL( + Frontend, + "While attempting to load the ROM requested, an error occurred. Please " + "refer to the sudachi wiki for more information or the sudachi discord for " + "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", + loader_id, error_id, static_cast(error_id)); } break; }