From 6cb936d0cf1a57fa461f097473b5b0b5ccaf09a1 Mon Sep 17 00:00:00 2001 From: Jun Bo Bi Date: Fri, 6 Aug 2021 11:10:44 -0400 Subject: [PATCH 1/7] Add SDL motion input and rumble support --- .../ControllerInterface/SDL/SDL.cpp | 154 ++++++++++++------ .../InputCommon/ControllerInterface/SDL/SDL.h | 50 ++++++ 2 files changed, 154 insertions(+), 50 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index a2e6893801..538e65fadf 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -56,8 +56,9 @@ static bool HandleEventAndContinue(const SDL_Event& e) else if (e.type == SDL_JOYDEVICEREMOVED) { g_controller_interface.RemoveDevice([&e](const auto* device) { - const Joystick* joystick = dynamic_cast(device); - return joystick && SDL_JoystickInstanceID(joystick->GetSDLJoystick()) == e.jdevice.which; + return device->GetSource() == "SDL" && + SDL_JoystickInstanceID(static_cast(device)->GetSDLJoystick()) == + e.jdevice.which; }); } else if (e.type == s_populate_event_type) @@ -79,7 +80,7 @@ static bool HandleEventAndContinue(const SDL_Event& e) void Init() { #if !SDL_VERSION_ATLEAST(2, 0, 0) - if (SDL_Init(SDL_INIT_JOYSTICK) != 0) + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) != 0) ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to initialize"); return; #else @@ -88,16 +89,20 @@ void Init() SDL_InitSubSystem(SDL_INIT_JOYSTICK); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); #endif + +#if SDL_VERSION_ATLEAST(2, 0, 9) + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); +#endif + s_hotplug_thread = std::thread([] { Common::ScopeGuard quit_guard([] { // TODO: there seems to be some sort of memory leak with SDL, quit isn't freeing everything up SDL_Quit(); }); - { Common::ScopeGuard init_guard([] { s_init_event.Set(); }); - if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) != 0) + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) != 0) { ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to initialize"); return; @@ -172,25 +177,6 @@ void PopulateDevices() Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index) : m_joystick(joystick), m_name(StripSpaces(GetJoystickName(sdl_index))) { -// really bad HACKS: -// to not use SDL for an XInput device -// too many people on the forums pick the SDL device and ask: -// "why don't my 360 gamepad triggers/rumble work correctly" -#ifdef _WIN32 - // checking the name is probably good (and hacky) enough - // but I'll double check with the num of buttons/axes - std::string lcasename = GetName(); - Common::ToLower(&lcasename); - - if ((std::string::npos != lcasename.find("xbox 360")) && - (10 == SDL_JoystickNumButtons(joystick)) && (5 == SDL_JoystickNumAxes(joystick)) && - (1 == SDL_JoystickNumHats(joystick)) && (0 == SDL_JoystickNumBalls(joystick))) - { - // this device won't be used - return; - } -#endif - if (SDL_JoystickNumButtons(joystick) > 255 || SDL_JoystickNumAxes(joystick) > 255 || SDL_JoystickNumHats(joystick) > 255 || SDL_JoystickNumBalls(joystick) > 255) { @@ -219,38 +205,78 @@ Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index) AddAnalogInputs(new Axis(i, m_joystick, -32768), new Axis(i, m_joystick, 32767)); } + m_haptic = nullptr; + #ifdef USE_SDL_HAPTIC m_haptic = SDL_HapticOpenFromJoystick(m_joystick); - if (!m_haptic) - return; - - const unsigned int supported_effects = SDL_HapticQuery(m_haptic); - - // Disable autocenter: - if (supported_effects & SDL_HAPTIC_AUTOCENTER) - SDL_HapticSetAutocenter(m_haptic, 0); - - // Constant - if (supported_effects & SDL_HAPTIC_CONSTANT) - AddOutput(new ConstantEffect(m_haptic)); - - // Ramp - if (supported_effects & SDL_HAPTIC_RAMP) - AddOutput(new RampEffect(m_haptic)); - - // Periodic - for (auto waveform : - {SDL_HAPTIC_SINE, SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHDOWN}) + if (m_haptic) { - if (supported_effects & waveform) - AddOutput(new PeriodicEffect(m_haptic, waveform)); + const unsigned int supported_effects = SDL_HapticQuery(m_haptic); + + // Disable autocenter: + if (supported_effects & SDL_HAPTIC_AUTOCENTER) + SDL_HapticSetAutocenter(m_haptic, 0); + + // Constant + if (supported_effects & SDL_HAPTIC_CONSTANT) + AddOutput(new ConstantEffect(m_haptic)); + + // Ramp + if (supported_effects & SDL_HAPTIC_RAMP) + AddOutput(new RampEffect(m_haptic)); + + // Periodic + for (auto waveform : + {SDL_HAPTIC_SINE, SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHDOWN}) + { + if (supported_effects & waveform) + AddOutput(new PeriodicEffect(m_haptic, waveform)); + } + + // LeftRight + if (supported_effects & SDL_HAPTIC_LEFTRIGHT) + { + AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Strong)); + AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Weak)); + } } +#endif - // LeftRight - if (supported_effects & SDL_HAPTIC_LEFTRIGHT) +#if SDL_VERSION_ATLEAST(2, 0, 9) + if (!m_haptic) { - AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Strong)); - AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Weak)); + AddOutput(new Motor(m_joystick)); + } +#endif + +#ifdef USE_SDL_GAMECONTROLLER + m_controller = SDL_GameControllerOpen(sdl_index); + + if (m_controller) + { + if (SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE) == 0) + { + AddInput(new MotionInput("Accel Up", m_controller, SDL_SENSOR_ACCEL, 1, 1)); + AddInput(new MotionInput("Accel Down", m_controller, SDL_SENSOR_ACCEL, 1, -1)); + + AddInput(new MotionInput("Accel Left", m_controller, SDL_SENSOR_ACCEL, 0, -1)); + AddInput(new MotionInput("Accel Right", m_controller, SDL_SENSOR_ACCEL, 0, 1)); + + AddInput(new MotionInput("Accel Forward", m_controller, SDL_SENSOR_ACCEL, 2, -1)); + AddInput(new MotionInput("Accel Backward", m_controller, SDL_SENSOR_ACCEL, 2, 1)); + } + + if (SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE) == 0) + { + AddInput(new MotionInput("Gyro Pitch Up", m_controller, SDL_SENSOR_GYRO, 0, 1)); + AddInput(new MotionInput("Gyro Pitch Down", m_controller, SDL_SENSOR_GYRO, 0, -1)); + + AddInput(new MotionInput("Gyro Roll Left", m_controller, SDL_SENSOR_GYRO, 2, 1)); + AddInput(new MotionInput("Gyro Roll Right", m_controller, SDL_SENSOR_GYRO, 2, -1)); + + AddInput(new MotionInput("Gyro Yaw Left", m_controller, SDL_SENSOR_GYRO, 1, 1)); + AddInput(new MotionInput("Gyro Yaw Right", m_controller, SDL_SENSOR_GYRO, 1, -1)); + } } #endif } @@ -267,6 +293,11 @@ Joystick::~Joystick() } #endif +#if SDL_VERSION_ATLEAST(2, 0, 9) + // stop all rumble + SDL_JoystickRumble(m_joystick, 0, 0, 0); +#endif + // close joystick SDL_JoystickClose(m_joystick); } @@ -442,6 +473,19 @@ bool Joystick::LeftRightEffect::UpdateParameters(s16 value) } #endif +#if SDL_VERSION_ATLEAST(2, 0, 9) +std::string Joystick::Motor::GetName() const +{ + return "Motor"; +} + +void Joystick::Motor::SetState(ControlState state) +{ + Uint16 rumble = state * std::numeric_limits::max(); + SDL_JoystickRumble(m_js, rumble, rumble, std::numeric_limits::max()); +} +#endif + void Joystick::UpdateInput() { // TODO: Don't call this for every Joystick, only once per ControllerInterface::UpdateInput() @@ -492,4 +536,14 @@ ControlState Joystick::Hat::GetState() const { return (SDL_JoystickGetHat(m_js, m_index) & (1 << m_direction)) > 0; } +#ifdef USE_SDL_GAMECONTROLLER + +ControlState Joystick::MotionInput::GetState() const +{ + std::array data{}; + SDL_GameControllerGetSensorData(m_gc, m_type, data.data(), (int)data.size()); + return m_scale * data[m_index]; +} + +#endif } // namespace ciface::SDL diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h index 7fbf7cf23e..7bf7565d84 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h @@ -5,14 +5,24 @@ #include +#define INIT_FLAGS SDL_INIT_JOYSTICK + #if SDL_VERSION_ATLEAST(1, 3, 0) #define USE_SDL_HAPTIC #endif +#if SDL_VERSION_ATLEAST(2, 0, 14) +#define USE_SDL_GAMECONTROLLER +#endif + #ifdef USE_SDL_HAPTIC #include #endif +#ifdef USE_SDL_GAMECONTROLLER +#include +#endif + #include "InputCommon/ControllerInterface/CoreDevice.h" namespace ciface::SDL @@ -137,6 +147,42 @@ private: }; #endif +#if SDL_VERSION_ATLEAST(2, 0, 9) + class Motor : public Output + { + public: + explicit Motor(SDL_Joystick* js) : m_js(js){}; + std::string GetName() const override; + void SetState(ControlState state) override; + + private: + SDL_Joystick* const m_js; + }; +#endif + +#ifdef USE_SDL_GAMECONTROLLER + class MotionInput : public Input + { + public: + MotionInput(const char* name, SDL_GameController* gc, SDL_SensorType type, int index, + ControlState scale) + : m_name(name), m_gc(gc), m_type(type), m_index(index), m_scale(scale){}; + + std::string GetName() const override { return m_name; }; + bool IsDetectable() const override { return false; }; + ControlState GetState() const override; + + private: + const char* m_name; + + SDL_GameController* const m_gc; + SDL_SensorType const m_type; + int const m_index; + + ControlState const m_scale; + }; +#endif + public: void UpdateInput() override; @@ -154,5 +200,9 @@ private: #ifdef USE_SDL_HAPTIC SDL_Haptic* m_haptic; #endif + +#ifdef USE_SDL_GAMECONTROLLER + SDL_GameController* m_controller; +#endif }; } // namespace ciface::SDL From ceed42a0eeb396c30c2bf8451b5da69fabff5da9 Mon Sep 17 00:00:00 2001 From: Jun Bo Bi Date: Fri, 6 Aug 2021 15:22:18 -0400 Subject: [PATCH 2/7] Add SDL as a submodule --- .gitmodules | 5 + CMakeLists.txt | 21 +- Externals/ExternalsReferenceAll.props | 3 + Externals/SDL/SDL | 1 + Externals/SDL/SDL2.vcxproj | 412 ++++++++++++++++++ Source/Core/DolphinLib.props | 2 + Source/Core/InputCommon/CMakeLists.txt | 32 +- .../ControllerInterface/ControllerInterface.h | 3 + Source/VSProps/Base.props | 2 + Source/dolphin-emu.sln | 11 + 10 files changed, 464 insertions(+), 28 deletions(-) create mode 160000 Externals/SDL/SDL create mode 100644 Externals/SDL/SDL2.vcxproj diff --git a/.gitmodules b/.gitmodules index 01e2ced8b6..d27084a9a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,3 +23,8 @@ url = https://github.com/KhronosGroup/SPIRV-Cross.git branch = master shallow = true +[submodule "SDL"] + path = Externals/SDL/SDL + url = https://github.com/libsdl-org/SDL.git + branch = main + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index ae835b0f39..e3602d8bc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,8 +84,8 @@ option(OPROFILING "Enable profiling" OFF) # TODO: Add DSPSpy option(DSPTOOL "Build dsptool" OFF) -# Enable SDL for default on operating systems that aren't Android, Linux or Windows. -if(NOT ANDROID AND NOT CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT MSVC) +# Enable SDL for default on operating systems that aren't Android or Linux. +if(NOT ANDROID AND NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") option(ENABLE_SDL "Enables SDL as a generic controller backend" ON) else() option(ENABLE_SDL "Enables SDL as a generic controller backend" OFF) @@ -612,6 +612,23 @@ if(UNIX) add_definitions(-DUSE_MEMORYWATCHER=1) endif() +if(ENABLE_SDL) + find_package(SDL2) + + if(SDL2_FOUND) + message(STATUS "Using system SDL2") + else() + message(STATUS "Using static SDL2 from Externals") + set(SDL_SHARED OFF) + set(SDL_SHARED_ENABLED_BY_DEFAULT OFF) + set(SDL_STATIC ON) + set(SDL_STATIC_ENABLED_BY_DEFAULT ON) + add_subdirectory(Externals/SDL/SDL) + set(SDL2_FOUND TRUE) + endif() + add_definitions(-DHAVE_SDL2=1) +endif() + if(ENABLE_ANALYTICS) message(STATUS "Enabling analytics collection (subject to end-user opt-in)") add_definitions(-DUSE_ANALYTICS=1) diff --git a/Externals/ExternalsReferenceAll.props b/Externals/ExternalsReferenceAll.props index f3a19282e4..2566a4a4a0 100644 --- a/Externals/ExternalsReferenceAll.props +++ b/Externals/ExternalsReferenceAll.props @@ -94,5 +94,8 @@ {1bea10f3-80ce-4bc4-9331-5769372cdf99} + + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} + diff --git a/Externals/SDL/SDL b/Externals/SDL/SDL new file mode 160000 index 0000000000..cd2dcf54af --- /dev/null +++ b/Externals/SDL/SDL @@ -0,0 +1 @@ +Subproject commit cd2dcf54afaa6d640abf8b83855f6c4b5cda457d diff --git a/Externals/SDL/SDL2.vcxproj b/Externals/SDL/SDL2.vcxproj new file mode 100644 index 0000000000..ebc599029a --- /dev/null +++ b/Externals/SDL/SDL2.vcxproj @@ -0,0 +1,412 @@ + + + + + + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} + + + + + + + + + + + + + + SDL\include;%(AdditionalIncludeDirectories) + HAVE_LIBC=1;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 8cb5919edf..2e7e546c78 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -493,6 +493,7 @@ + @@ -1104,6 +1105,7 @@ + diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 06f959a91a..9cca78b65d 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -174,32 +174,12 @@ if(UNIX) ) endif() -if(ENABLE_SDL) - find_package(SDL2) - if(SDL2_FOUND) - message(STATUS "Using shared SDL2") - set(SDL_TARGET SDL2::SDL2) - else() - # SDL2 not found, try SDL - find_package(SDL) - if(SDL_FOUND) - message(STATUS "Using shared SDL") - add_library(System_SDL INTERFACE) - target_include_directories(System_SDL INTERFACE ${SDL_INCLUDE_DIR}) - target_link_libraries(System_SDL INTERFACE ${SDL_LIBRARY}) - set(SDL_TARGET System_SDL) - endif() - endif() - if(SDL_TARGET AND TARGET ${SDL_TARGET}) - target_sources(inputcommon PRIVATE - ControllerInterface/SDL/SDL.cpp - ControllerInterface/SDL/SDL.h - ) - target_link_libraries(inputcommon PRIVATE ${SDL_TARGET}) - target_compile_definitions(inputcommon PRIVATE "CIFACE_USE_SDL=1") - else() - message(STATUS "SDL NOT found, disabling SDL input") - endif() +if(SDL2_FOUND) + target_sources(inputcommon PRIVATE + ControllerInterface/SDL/SDL.cpp + ControllerInterface/SDL/SDL.h + ) + target_link_libraries(inputcommon PRIVATE SDL2::SDL2) endif() if(MSVC) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 7a5ee0ad26..2f43cacc42 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -30,6 +30,9 @@ #define CIFACE_USE_PIPES #endif #define CIFACE_USE_DUALSHOCKUDPCLIENT +#if defined(HAVE_SDL2) +#define CIFACE_USE_SDL +#endif namespace ciface { diff --git a/Source/VSProps/Base.props b/Source/VSProps/Base.props index 49f546d39f..ae821339cc 100644 --- a/Source/VSProps/Base.props +++ b/Source/VSProps/Base.props @@ -52,6 +52,7 @@ $(ExternalsDir)picojson;%(AdditionalIncludeDirectories) $(ExternalsDir)pugixml;%(AdditionalIncludeDirectories) $(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories) + $(ExternalsDir)SDL\SDL\include;%(AdditionalIncludeDirectories) $(ExternalsDir)SFML\include;%(AdditionalIncludeDirectories) $(ExternalsDir)soundtouch;%(AdditionalIncludeDirectories) $(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories) @@ -91,6 +92,7 @@ HAS_LIBMGBA;%(PreprocessorDefinitions) AUTOUPDATE=1;%(PreprocessorDefinitions) SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS;%(PreprocessorDefinitions) + HAVE_SDL2=1;%(PreprocessorDefinitions) diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln index 89df520d9b..b289f27548 100644 --- a/Source/dolphin-emu.sln +++ b/Source/dolphin-emu.sln @@ -81,6 +81,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "..\Externals\fmt\fmt EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spirv_cross", "..\Externals\spirv_cross\spirv_cross.vcxproj", "{3d780617-ec8c-4721-b9fd-dfc9bb658c7c}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL2", "..\Externals\SDL\SDL2.vcxproj", "{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -389,6 +391,14 @@ Global {3D780617-EC8C-4721-B9FD-DFC9BB658C7C}.Release|ARM64.Build.0 = Release|ARM64 {3D780617-EC8C-4721-B9FD-DFC9BB658C7C}.Release|x64.ActiveCfg = Release|x64 {3D780617-EC8C-4721-B9FD-DFC9BB658C7C}.Release|x64.Build.0 = Release|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|ARM64.Build.0 = Debug|ARM64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|x64.ActiveCfg = Debug|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|x64.Build.0 = Debug|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|ARM64.ActiveCfg = Release|ARM64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|ARM64.Build.0 = Release|ARM64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.ActiveCfg = Release|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -422,6 +432,7 @@ Global {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} {4BC5A148-0AB3-440F-A980-A29B4B999190} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} {3D780617-EC8C-4721-B9FD-DFC9BB658C7C} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {64B0A343-3B94-4522-9C24-6937FE5EFB22} From 3f7a2c6d4df224ce0e7a0c36d8ede695fb3468cb Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Sat, 16 Apr 2022 09:00:00 -0700 Subject: [PATCH 3/7] ci/sdl: minor cleanup --- .../ControllerInterface/SDL/SDL.cpp | 179 +++++++++++++----- .../InputCommon/ControllerInterface/SDL/SDL.h | 6 +- 2 files changed, 132 insertions(+), 53 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index 538e65fadf..b3f2147ae3 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -77,6 +77,73 @@ static bool HandleEventAndContinue(const SDL_Event& e) } #endif +static void EnableSDLLogging() +{ + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); + SDL_LogSetOutputFunction( + [](void*, int category, SDL_LogPriority priority, const char* message) { + std::string category_name; + switch (category) + { + case SDL_LOG_CATEGORY_APPLICATION: + category_name = "app"; + break; + case SDL_LOG_CATEGORY_ERROR: + category_name = "error"; + break; + case SDL_LOG_CATEGORY_ASSERT: + category_name = "assert"; + break; + case SDL_LOG_CATEGORY_SYSTEM: + category_name = "system"; + break; + case SDL_LOG_CATEGORY_AUDIO: + category_name = "audio"; + break; + case SDL_LOG_CATEGORY_VIDEO: + category_name = "video"; + break; + case SDL_LOG_CATEGORY_RENDER: + category_name = "render"; + break; + case SDL_LOG_CATEGORY_INPUT: + category_name = "input"; + break; + case SDL_LOG_CATEGORY_TEST: + category_name = "test"; + break; + default: + category_name = fmt::format("unknown({})", category); + break; + } + + auto log_level = Common::Log::LogLevel::LNOTICE; + switch (priority) + { + case SDL_LOG_PRIORITY_VERBOSE: + case SDL_LOG_PRIORITY_DEBUG: + log_level = Common::Log::LogLevel::LDEBUG; + break; + case SDL_LOG_PRIORITY_INFO: + log_level = Common::Log::LogLevel::LINFO; + break; + case SDL_LOG_PRIORITY_WARN: + log_level = Common::Log::LogLevel::LWARNING; + break; + case SDL_LOG_PRIORITY_ERROR: + log_level = Common::Log::LogLevel::LERROR; + break; + case SDL_LOG_PRIORITY_CRITICAL: + log_level = Common::Log::LogLevel::LNOTICE; + break; + } + + GENERIC_LOG_FMT(Common::Log::LogType::CONTROLLERINTERFACE, log_level, "{}: {}", + category_name, message); + }, + nullptr); +} + void Init() { #if !SDL_VERSION_ATLEAST(2, 0, 0) @@ -90,6 +157,8 @@ void Init() SDL_QuitSubSystem(SDL_INIT_JOYSTICK); #endif + EnableSDLLogging(); + #if SDL_VERSION_ATLEAST(2, 0, 9) SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); #endif @@ -205,39 +274,40 @@ Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index) AddAnalogInputs(new Axis(i, m_joystick, -32768), new Axis(i, m_joystick, 32767)); } - m_haptic = nullptr; - #ifdef USE_SDL_HAPTIC - m_haptic = SDL_HapticOpenFromJoystick(m_joystick); - if (m_haptic) + if (SDL_JoystickIsHaptic(m_joystick)) { - const unsigned int supported_effects = SDL_HapticQuery(m_haptic); - - // Disable autocenter: - if (supported_effects & SDL_HAPTIC_AUTOCENTER) - SDL_HapticSetAutocenter(m_haptic, 0); - - // Constant - if (supported_effects & SDL_HAPTIC_CONSTANT) - AddOutput(new ConstantEffect(m_haptic)); - - // Ramp - if (supported_effects & SDL_HAPTIC_RAMP) - AddOutput(new RampEffect(m_haptic)); - - // Periodic - for (auto waveform : - {SDL_HAPTIC_SINE, SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHDOWN}) + m_haptic = SDL_HapticOpenFromJoystick(m_joystick); + if (m_haptic) { - if (supported_effects & waveform) - AddOutput(new PeriodicEffect(m_haptic, waveform)); - } + const unsigned int supported_effects = SDL_HapticQuery(m_haptic); - // LeftRight - if (supported_effects & SDL_HAPTIC_LEFTRIGHT) - { - AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Strong)); - AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Weak)); + // Disable autocenter: + if (supported_effects & SDL_HAPTIC_AUTOCENTER) + SDL_HapticSetAutocenter(m_haptic, 0); + + // Constant + if (supported_effects & SDL_HAPTIC_CONSTANT) + AddOutput(new ConstantEffect(m_haptic)); + + // Ramp + if (supported_effects & SDL_HAPTIC_RAMP) + AddOutput(new RampEffect(m_haptic)); + + // Periodic + for (auto waveform : + {SDL_HAPTIC_SINE, SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHDOWN}) + { + if (supported_effects & waveform) + AddOutput(new PeriodicEffect(m_haptic, waveform)); + } + + // LeftRight + if (supported_effects & SDL_HAPTIC_LEFTRIGHT) + { + AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Strong)); + AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Weak)); + } } } #endif @@ -250,32 +320,34 @@ Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index) #endif #ifdef USE_SDL_GAMECONTROLLER - m_controller = SDL_GameControllerOpen(sdl_index); - - if (m_controller) + if (SDL_IsGameController(sdl_index)) { - if (SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE) == 0) + m_controller = SDL_GameControllerOpen(sdl_index); + if (m_controller) { - AddInput(new MotionInput("Accel Up", m_controller, SDL_SENSOR_ACCEL, 1, 1)); - AddInput(new MotionInput("Accel Down", m_controller, SDL_SENSOR_ACCEL, 1, -1)); + if (SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE) == 0) + { + AddInput(new MotionInput("Accel Up", m_controller, SDL_SENSOR_ACCEL, 1, 1)); + AddInput(new MotionInput("Accel Down", m_controller, SDL_SENSOR_ACCEL, 1, -1)); - AddInput(new MotionInput("Accel Left", m_controller, SDL_SENSOR_ACCEL, 0, -1)); - AddInput(new MotionInput("Accel Right", m_controller, SDL_SENSOR_ACCEL, 0, 1)); + AddInput(new MotionInput("Accel Left", m_controller, SDL_SENSOR_ACCEL, 0, -1)); + AddInput(new MotionInput("Accel Right", m_controller, SDL_SENSOR_ACCEL, 0, 1)); - AddInput(new MotionInput("Accel Forward", m_controller, SDL_SENSOR_ACCEL, 2, -1)); - AddInput(new MotionInput("Accel Backward", m_controller, SDL_SENSOR_ACCEL, 2, 1)); - } + AddInput(new MotionInput("Accel Forward", m_controller, SDL_SENSOR_ACCEL, 2, -1)); + AddInput(new MotionInput("Accel Backward", m_controller, SDL_SENSOR_ACCEL, 2, 1)); + } - if (SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE) == 0) - { - AddInput(new MotionInput("Gyro Pitch Up", m_controller, SDL_SENSOR_GYRO, 0, 1)); - AddInput(new MotionInput("Gyro Pitch Down", m_controller, SDL_SENSOR_GYRO, 0, -1)); + if (SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE) == 0) + { + AddInput(new MotionInput("Gyro Pitch Up", m_controller, SDL_SENSOR_GYRO, 0, 1)); + AddInput(new MotionInput("Gyro Pitch Down", m_controller, SDL_SENSOR_GYRO, 0, -1)); - AddInput(new MotionInput("Gyro Roll Left", m_controller, SDL_SENSOR_GYRO, 2, 1)); - AddInput(new MotionInput("Gyro Roll Right", m_controller, SDL_SENSOR_GYRO, 2, -1)); + AddInput(new MotionInput("Gyro Roll Left", m_controller, SDL_SENSOR_GYRO, 2, 1)); + AddInput(new MotionInput("Gyro Roll Right", m_controller, SDL_SENSOR_GYRO, 2, -1)); - AddInput(new MotionInput("Gyro Yaw Left", m_controller, SDL_SENSOR_GYRO, 1, 1)); - AddInput(new MotionInput("Gyro Yaw Right", m_controller, SDL_SENSOR_GYRO, 1, -1)); + AddInput(new MotionInput("Gyro Yaw Left", m_controller, SDL_SENSOR_GYRO, 1, 1)); + AddInput(new MotionInput("Gyro Yaw Right", m_controller, SDL_SENSOR_GYRO, 1, -1)); + } } } #endif @@ -283,13 +355,22 @@ Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index) Joystick::~Joystick() { +#ifdef USE_SDL_GAMECONTROLLER + if (m_controller) + { + SDL_GameControllerClose(m_controller); + m_controller = nullptr; + } +#endif + #ifdef USE_SDL_HAPTIC if (m_haptic) { // stop/destroy all effects SDL_HapticStopAll(m_haptic); - // close haptic first + // close haptic before joystick SDL_HapticClose(m_haptic); + m_haptic = nullptr; } #endif diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h index 7bf7565d84..f789c8846a 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h @@ -5,8 +5,6 @@ #include -#define INIT_FLAGS SDL_INIT_JOYSTICK - #if SDL_VERSION_ATLEAST(1, 3, 0) #define USE_SDL_HAPTIC #endif @@ -198,11 +196,11 @@ private: std::string m_name; #ifdef USE_SDL_HAPTIC - SDL_Haptic* m_haptic; + SDL_Haptic* m_haptic = nullptr; #endif #ifdef USE_SDL_GAMECONTROLLER - SDL_GameController* m_controller; + SDL_GameController* m_controller = nullptr; #endif }; } // namespace ciface::SDL From dd20c7cf78b603867295c88632df71d0ce93d95e Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Mon, 18 Apr 2022 03:01:03 -0700 Subject: [PATCH 4/7] ci/sdl: re-add the x360 controller block --- .../ControllerInterface/SDL/SDL.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index b3f2147ae3..0aa36f82dc 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -246,6 +246,28 @@ void PopulateDevices() Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index) : m_joystick(joystick), m_name(StripSpaces(GetJoystickName(sdl_index))) { + // really bad HACKS: + // to not use SDL for an XInput device + // too many people on the forums pick the SDL device and ask: + // "why don't my 360 gamepad triggers/rumble work correctly" + // XXX x360 controllers _should_ work on modern SDL2, so it's unclear why they're + // still broken. Perhaps it's because we're not pumping window messages, which SDL seems to + // expect. +#ifdef _WIN32 + // checking the name is probably good (and hacky) enough + // but I'll double check with the num of buttons/axes + std::string lcasename = GetName(); + Common::ToLower(&lcasename); + + if ((std::string::npos != lcasename.find("xbox 360")) && + (11 == SDL_JoystickNumButtons(joystick)) && (6 == SDL_JoystickNumAxes(joystick)) && + (1 == SDL_JoystickNumHats(joystick)) && (0 == SDL_JoystickNumBalls(joystick))) + { + // this device won't be used + return; + } +#endif + if (SDL_JoystickNumButtons(joystick) > 255 || SDL_JoystickNumAxes(joystick) > 255 || SDL_JoystickNumHats(joystick) > 255 || SDL_JoystickNumBalls(joystick) > 255) { From 655fb94e61f9edcb30d0370c801f7aee79a8811e Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Mon, 18 Apr 2022 03:01:36 -0700 Subject: [PATCH 5/7] ci/win32: give the wndclass a unique name other things (like SDL) may try to use "Message" --- Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp index a9c28efdc0..42aa12a141 100644 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp @@ -73,11 +73,13 @@ void ciface::Win32::Init(void* hwnd) } Common::ScopeGuard uninit([] { CoUninitialize(); }); + const auto window_name = TEXT("DolphinWin32ControllerInterface"); + WNDCLASSEX window_class_info{}; window_class_info.cbSize = sizeof(window_class_info); window_class_info.lpfnWndProc = WindowProc; window_class_info.hInstance = GetModuleHandle(nullptr); - window_class_info.lpszClassName = L"Message"; + window_class_info.lpszClassName = window_name; ATOM window_class = RegisterClassEx(&window_class_info); if (!window_class) @@ -92,7 +94,7 @@ void ciface::Win32::Init(void* hwnd) Common::HRWrap(GetLastError())); }); - message_window = CreateWindowEx(0, L"Message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, + message_window = CreateWindowEx(0, window_name, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr); promise_guard.Exit(); if (!message_window) From ddf83462ac1139e76d6f0732ed83fe41fa9a9050 Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Mon, 18 Apr 2022 03:31:07 -0700 Subject: [PATCH 6/7] sdl: enable SDL_HINT_JOYSTICK_THREAD fixes window message pumping --- Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index 0aa36f82dc..4a08e27880 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -159,6 +159,11 @@ void Init() EnableSDLLogging(); +#if SDL_VERSION_ATLEAST(2, 0, 14) + // This is required on windows so that SDL's joystick code properly pumps window messages + SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"); +#endif + #if SDL_VERSION_ATLEAST(2, 0, 9) SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); #endif From 54b4ad8f559d6fa16bf0ca15f91de4e7d103996f Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Mon, 18 Apr 2022 04:09:38 -0700 Subject: [PATCH 7/7] ci/sdl: pump messages for SDL_hidapi so device detection works --- .../ControllerInterface/SDL/SDL.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index 4a08e27880..6dec6003fd 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -15,6 +15,8 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" #ifdef _WIN32 +#include + #pragma comment(lib, "SDL2.lib") #endif @@ -204,11 +206,34 @@ void Init() } } +#ifdef _WIN32 + // This is a hack to workaround SDL_hidapi using window messages to detect device + // removal/arrival, yet no part of SDL pumps messages for it. It can hopefully be removed in the + // future when SDL fixes the issue. Note this is a separate issue from SDL_HINT_JOYSTICK_THREAD. + // Also note that SDL_WaitEvent may block while device detection window messages get queued up, + // causing some noticible stutter. This is just another reason it should be fixed properly by + // SDL... + const auto window_handle = + FindWindowEx(HWND_MESSAGE, nullptr, TEXT("SDL_HIDAPI_DEVICE_DETECTION"), nullptr); +#endif + SDL_Event e; while (SDL_WaitEvent(&e) != 0) { if (!HandleEventAndContinue(e)) return; + +#ifdef _WIN32 + MSG msg; + while (window_handle && PeekMessage(&msg, window_handle, 0, 0, PM_NOREMOVE)) + { + if (GetMessageA(&msg, window_handle, 0, 0) != 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#endif } });