From 75eaab2e0f48eb588c1bfb85f96630e199fbc1da Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 17 Sep 2020 12:00:29 -0400 Subject: configure_input_player: Implement input exclusivity and persistence With this, the "Input Devices" combobox should accurately reflect the input device being used and disallows inputs from other input devices unless the input device is set to "Any". --- src/input_common/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/input_common') diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index d32fd8b81..354c734fe 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -78,7 +78,7 @@ struct InputSubsystem::Impl { [[nodiscard]] std::vector GetInputDevices() const { std::vector devices = { Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, - Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}}, + Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, }; #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); @@ -96,7 +96,7 @@ struct InputSubsystem::Impl { if (!params.Has("class") || params.Get("class", "") == "any") { return {}; } - if (params.Get("class", "") == "key") { + if (params.Get("class", "") == "keyboard") { // TODO consider returning the SDL key codes for the default keybindings return {}; } @@ -116,7 +116,7 @@ struct InputSubsystem::Impl { if (!params.Has("class") || params.Get("class", "") == "any") { return {}; } - if (params.Get("class", "") == "key") { + if (params.Get("class", "") == "keyboard") { // TODO consider returning the SDL key codes for the default keybindings return {}; } -- cgit v1.2.3 From 8ead176639be482fb26c2eb3f95fc942e52efee0 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 28 Sep 2020 04:53:21 -0400 Subject: udp/client: Reduce testing period to 5 seconds --- src/input_common/udp/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/input_common') diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 7039d6fc3..3677e79ca 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -344,7 +344,7 @@ void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, }; Socket socket{host, port, pad_index, client_id, std::move(callback)}; std::thread worker_thread{SocketLoop, &socket}; - const bool result = success_event.WaitFor(std::chrono::seconds(8)); + const bool result = success_event.WaitFor(std::chrono::seconds(5)); socket.Stop(); worker_thread.join(); if (result) { -- cgit v1.2.3 From ceb7b11f166a4e59945a6296d364980c37ca681e Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 28 Sep 2020 10:27:29 -0400 Subject: configure_input_player: Change "Defaults" button behavior RestoreDefaults() now restores the selected devices' mappings using UpdateMappingWithDefaults(). This allows us to move the keyboard mapping from RestoreDefaults() to UpdateMappingWithDefaults(). --- src/input_common/main.cpp | 8 ---- src/yuzu/configuration/configure_input_player.cpp | 57 ++++++++++++----------- 2 files changed, 30 insertions(+), 35 deletions(-) (limited to 'src/input_common') diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 354c734fe..b438482cc 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -96,10 +96,6 @@ struct InputSubsystem::Impl { if (!params.Has("class") || params.Get("class", "") == "any") { return {}; } - if (params.Get("class", "") == "keyboard") { - // TODO consider returning the SDL key codes for the default keybindings - return {}; - } if (params.Get("class", "") == "gcpad") { return gcadapter->GetAnalogMappingForDevice(params); } @@ -116,10 +112,6 @@ struct InputSubsystem::Impl { if (!params.Has("class") || params.Get("class", "") == "any") { return {}; } - if (params.Get("class", "") == "keyboard") { - // TODO consider returning the SDL key codes for the default keybindings - return {}; - } if (params.Get("class", "") == "gcpad") { return gcadapter->GetButtonMappingForDevice(params); } diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 213a76224..460ff08a4 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -693,32 +693,7 @@ void ConfigureInputPlayer::UpdateInputDeviceCombobox() { } void ConfigureInputPlayer::RestoreDefaults() { - // Reset Buttons - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; - } - - // Reset Analogs and Modifier Buttons - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); - } - - analogs_param[analog_id].Set( - "modifier", InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id])); - } - - for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { - motions_param[motion_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; - } - - UpdateUI(); - UpdateInputDeviceCombobox(); - ui->comboControllerType->setCurrentIndex(0); + UpdateMappingWithDefaults(); } void ConfigureInputPlayer::ClearAll() { @@ -951,9 +926,37 @@ void ConfigureInputPlayer::UpdateMotionButtons() { } void ConfigureInputPlayer::UpdateMappingWithDefaults() { - if (ui->comboDevices->currentIndex() < 2) { + if (ui->comboDevices->currentIndex() == 0) { return; } + + if (ui->comboDevices->currentIndex() == 1) { + // Reset keyboard bindings + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + + analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id])); + } + + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motions_param[motion_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + } + + UpdateUI(); + return; + } + + // Reset controller bindings const auto& device = input_devices[ui->comboDevices->currentIndex()]; auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); -- cgit v1.2.3 From 9b501af8e3d0f6457fafb0fdfbcc11f6da4f0e8a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 10 Oct 2020 09:03:47 -0400 Subject: controllers/npad: Add heuristics to reduce rumble state changes Sending too many state changes in a short period of time can cause massive performance issues. As a result, we have to use several heuristics to reduce the number of state changes to minimize/eliminate this performance impact while maintaining the quality of these vibrations as much as possible. --- src/core/frontend/input.h | 2 +- src/core/hle/service/hid/controllers/npad.cpp | 51 ++++++++++++++++++++++--- src/input_common/sdl/sdl_impl.cpp | 54 +++++++++++++-------------- 3 files changed, 72 insertions(+), 35 deletions(-) (limited to 'src/input_common') diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 277b70e53..fb2ce2514 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -33,7 +33,7 @@ public: virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const { return {}; } - virtual bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const { + virtual bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const { return {}; } }; diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 924f209c0..cc54b164d 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -698,16 +698,57 @@ void Controller_NPad::VibrateController(const std::vector& vibrati continue; } + // Filter out vibrations with equivalent values to reduce unnecessary state changes. + if (vibration_values[i].amp_low == + latest_vibration_values[npad_index][device_index].amp_low && + vibration_values[i].amp_high == + latest_vibration_values[npad_index][device_index].amp_high) { + continue; + } + + // Filter out non-zero vibrations that are within 0.015625 absolute amplitude of each other. + if ((vibration_values[i].amp_low != 0.0f || vibration_values[i].amp_high != 0.0f) && + (latest_vibration_values[npad_index][device_index].amp_low != 0.0f || + latest_vibration_values[npad_index][device_index].amp_high != 0.0f) && + (abs(vibration_values[i].amp_low - + latest_vibration_values[npad_index][device_index].amp_low) < 0.015625f && + abs(vibration_values[i].amp_high - + latest_vibration_values[npad_index][device_index].amp_high) < 0.015625f)) { + continue; + } + using namespace Settings::NativeButton; const auto& button_state = buttons[npad_index]; // TODO: Vibrate left/right vibration motors independently if possible. - button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( - vibration_values[i].amp_high * Settings::values.vibration_strength.GetValue() / 100, + const bool success = button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( vibration_values[i].amp_low * Settings::values.vibration_strength.GetValue() / 100, - vibration_values[i].freq_high, vibration_values[i].freq_low); - - latest_vibration_values[npad_index][device_index] = vibration_values[i]; + vibration_values[i].freq_low, + vibration_values[i].amp_high * Settings::values.vibration_strength.GetValue() / 100, + vibration_values[i].freq_high); + + if (success) { + switch (connected_controllers[npad_index].type) { + case NPadControllerType::None: + UNREACHABLE(); + break; + case NPadControllerType::ProController: + case NPadControllerType::Handheld: + case NPadControllerType::JoyDual: + // Since we can't vibrate motors independently yet, we can reduce state changes by + // assigning all 3 device indices the current vibration value. + latest_vibration_values[npad_index][0] = vibration_values[i]; + latest_vibration_values[npad_index][1] = vibration_values[i]; + latest_vibration_values[npad_index][2] = vibration_values[i]; + break; + case NPadControllerType::JoyLeft: + case NPadControllerType::JoyRight: + case NPadControllerType::Pokeball: + default: + latest_vibration_values[npad_index][device_index] = vibration_values[i]; + break; + } + } } } diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 10883e2d9..18fb2ac5e 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -80,30 +80,24 @@ public: return static_cast(state.axes.at(axis)) / (32767.0f * range); } - bool RumblePlay(f32 amp_low, f32 amp_high, u32 time) { - const u16 raw_amp_low = static_cast(amp_low * 0xFFFF); - const u16 raw_amp_high = static_cast(amp_high * 0xFFFF); - // Lower drastically the number of state changes - if (raw_amp_low >> 11 == last_state_rumble_low >> 11 && - raw_amp_high >> 11 == last_state_rumble_high >> 11) { - if (raw_amp_low + raw_amp_high != 0 || - last_state_rumble_low + last_state_rumble_high == 0) { - return false; - } - } - // Don't change state if last vibration was < 20ms - const auto now = std::chrono::system_clock::now(); - if (std::chrono::duration_cast(now - last_vibration) < - std::chrono::milliseconds(20)) { - return raw_amp_low + raw_amp_high == 0; + bool RumblePlay(u16 amp_low, u16 amp_high) { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::steady_clock; + + // Prevent vibrations less than 10ms apart from each other. + if (duration_cast(steady_clock::now() - last_vibration) < milliseconds(10)) { + return false; + }; + + last_vibration = steady_clock::now(); + + if (sdl_controller != nullptr) { + return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0; + } else if (sdl_joystick != nullptr) { + return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0; } - last_vibration = now; - last_state_rumble_low = raw_amp_low; - last_state_rumble_high = raw_amp_high; - if (sdl_joystick) { - SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time); - } return false; } @@ -172,13 +166,13 @@ private: } state; std::string guid; int port; - u16 last_state_rumble_high = 0; - u16 last_state_rumble_low = 0; - std::chrono::time_point last_vibration; std::unique_ptr sdl_joystick; std::unique_ptr sdl_controller; mutable std::mutex mutex; + // This is the timepoint of the last vibration and is used to ensure vibrations are 10ms apart. + std::chrono::steady_clock::time_point last_vibration; + // Motion is initialized without PID values as motion input is not aviable for SDL2 MotionInput motion{0.0f, 0.0f, 0.0f}; }; @@ -327,10 +321,12 @@ public: return joystick->GetButton(button); } - bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override { - const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)); - const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f)); - return joystick->RumblePlay(new_amp_low, new_amp_high, 250); + bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { + const u16 processed_amp_low = + static_cast(pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)) * 0xFFFF); + const u16 processed_amp_high = + static_cast(pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f)) * 0xFFFF); + return joystick->RumblePlay(processed_amp_low, processed_amp_high); } private: -- cgit v1.2.3 From 38110dd485e329fa39e2e4c02b91a89dfebcbc88 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 17 Oct 2020 09:38:12 -0400 Subject: configure_input: Add per-player vibration Allows for enabling and modifying vibration and vibration strength per player. Also adds a toggle for enabling/disabling accurate vibrations. Co-authored-by: Its-Rei --- dist/qt_themes/qdarkstyle/style.qss | 11 +- dist/qt_themes/qdarkstyle_midnight_blue/style.qss | 12 +- src/core/hle/service/hid/controllers/npad.cpp | 13 +- src/core/settings.h | 1 + src/input_common/settings.h | 3 + src/yuzu/CMakeLists.txt | 3 + src/yuzu/applets/controller.cpp | 16 + src/yuzu/applets/controller.h | 3 + src/yuzu/applets/controller.ui | 18 +- src/yuzu/configuration/config.cpp | 16 + src/yuzu/configuration/configure_input.cpp | 4 + src/yuzu/configuration/configure_input.ui | 22 +- src/yuzu/configuration/configure_vibration.cpp | 66 +++ src/yuzu/configuration/configure_vibration.h | 40 ++ src/yuzu/configuration/configure_vibration.ui | 546 ++++++++++++++++++++++ src/yuzu_cmd/config.cpp | 2 + src/yuzu_cmd/default_ini.h | 4 + src/yuzu_tester/config.cpp | 1 + 18 files changed, 751 insertions(+), 30 deletions(-) create mode 100644 src/yuzu/configuration/configure_vibration.cpp create mode 100644 src/yuzu/configuration/configure_vibration.h create mode 100644 src/yuzu/configuration/configure_vibration.ui (limited to 'src/input_common') diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index aca6531ac..2a1e8ddeb 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1293,15 +1293,24 @@ QPushButton#buttonRefreshDevices { QSpinBox#spinboxLStickRange, QSpinBox#spinboxRStickRange, -QSpinBox#vibrationSpin { +QSpinBox#vibrationSpinPlayer1, +QSpinBox#vibrationSpinPlayer2, +QSpinBox#vibrationSpinPlayer3, +QSpinBox#vibrationSpinPlayer4, +QSpinBox#vibrationSpinPlayer5, +QSpinBox#vibrationSpinPlayer6, +QSpinBox#vibrationSpinPlayer7, +QSpinBox#vibrationSpinPlayer8 { min-width: 68px; } +QDialog#ConfigureVibration QGroupBox::indicator, QGroupBox#motionGroup::indicator, QGroupBox#vibrationGroup::indicator { margin-left: 0px; } +QDialog#ConfigureVibration QGroupBox::title, QGroupBox#motionGroup::title, QGroupBox#vibrationGroup::title { spacing: 2px; diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index ad032a966..70e540b06 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -2200,21 +2200,31 @@ QPushButton#buttonRefreshDevices { QSpinBox#spinboxLStickRange, QSpinBox#spinboxRStickRange, -QSpinBox#vibrationSpin { +QSpinBox#vibrationSpinPlayer1, +QSpinBox#vibrationSpinPlayer2, +QSpinBox#vibrationSpinPlayer3, +QSpinBox#vibrationSpinPlayer4, +QSpinBox#vibrationSpinPlayer5, +QSpinBox#vibrationSpinPlayer6, +QSpinBox#vibrationSpinPlayer7, +QSpinBox#vibrationSpinPlayer8 { min-width: 68px; } +QDialog#ConfigureVibration QGroupBox::indicator, QGroupBox#motionGroup::indicator, QGroupBox#vibrationGroup::indicator { margin-left: 0px; } +QDialog#ConfigureVibration QGroupBox, QWidget#bottomPerGameInput QGroupBox#motionGroup, QWidget#bottomPerGameInput QGroupBox#vibrationGroup, QWidget#bottomPerGameInput QGroupBox#inputConfigGroup { padding: 0px; } +QDialog#ConfigureVibration QGroupBox::title, QGroupBox#motionGroup::title, QGroupBox#vibrationGroup::title { spacing: 2px; diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index ba20d3f59..dc9954377 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -680,11 +680,19 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, return false; } + const auto& player = Settings::values.players.GetValue()[npad_index]; + + if (!player.vibration_enabled) { + return false; + } + using namespace Settings::NativeButton; const auto& button_state = buttons[npad_index]; return button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( - vibration_value.amp_low, vibration_value.freq_low, vibration_value.amp_high, + std::min(vibration_value.amp_low * player.vibration_strength / 100.0f, 1.0f), + vibration_value.freq_low, + std::min(vibration_value.amp_high * player.vibration_strength / 100.0f, 1.0f), vibration_value.freq_high); } @@ -728,7 +736,8 @@ void Controller_NPad::VibrateControllers(const std::vector& vibrat } // Filter out non-zero vibrations that are within 0.015625 absolute amplitude of each other. - if ((vibration_values[i].amp_low != 0.0f || vibration_values[i].amp_high != 0.0f) && + if (!Settings::values.enable_accurate_vibrations.GetValue() && + (vibration_values[i].amp_low != 0.0f || vibration_values[i].amp_high != 0.0f) && (latest_vibration_values[npad_index][device_index].amp_low != 0.0f || latest_vibration_values[npad_index][device_index].amp_high != 0.0f) && (abs(vibration_values[i].amp_low - diff --git a/src/core/settings.h b/src/core/settings.h index edd2a00ca..476c3fdf3 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -170,6 +170,7 @@ struct Values { Setting use_docked_mode; Setting vibration_enabled; + Setting enable_accurate_vibrations; Setting motion_enabled; std::string motion_device; diff --git a/src/input_common/settings.h b/src/input_common/settings.h index f52d28540..2763ed991 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -332,6 +332,9 @@ struct PlayerInput { AnalogsRaw analogs; MotionRaw motions; + bool vibration_enabled; + int vibration_strength; + u32 body_color_left; u32 body_color_right; u32 button_color_left; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 22fe0a2a6..bf1fae9fa 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -105,6 +105,9 @@ add_executable(yuzu configuration/configure_ui.cpp configuration/configure_ui.h configuration/configure_ui.ui + configuration/configure_vibration.cpp + configuration/configure_vibration.h + configuration/configure_vibration.ui configuration/configure_web.cpp configuration/configure_web.h configuration/configure_web.ui diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index 0fc713a6e..c5e671309 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -14,6 +14,7 @@ #include "ui_controller.h" #include "yuzu/applets/controller.h" #include "yuzu/configuration/configure_input_dialog.h" +#include "yuzu/configuration/configure_vibration.h" #include "yuzu/main.h" namespace { @@ -223,6 +224,9 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( } } + connect(ui->vibrationButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureVibrationDialog); + connect(ui->inputConfigButton, &QPushButton::clicked, this, &QtControllerSelectorDialog::CallConfigureInputDialog); @@ -285,6 +289,18 @@ void QtControllerSelectorDialog::LoadConfiguration() { ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); } +void QtControllerSelectorDialog::CallConfigureVibrationDialog() { + ConfigureVibration dialog(this); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + void QtControllerSelectorDialog::CallConfigureInputDialog() { const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index 8fefecf05..a2ce03c8f 100644 --- a/src/yuzu/applets/controller.h +++ b/src/yuzu/applets/controller.h @@ -42,6 +42,9 @@ private: // Loads the current input configuration into the frontend applet. void LoadConfiguration(); + // Initializes the "Configure Vibration" Dialog. + void CallConfigureVibrationDialog(); + // Initializes the "Configure Input" Dialog. void CallConfigureInputDialog(); diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui index cc27b8ef4..8e571ba8f 100644 --- a/src/yuzu/applets/controller.ui +++ b/src/yuzu/applets/controller.ui @@ -2329,11 +2329,11 @@ 3 - + 68 - 21 + 0 @@ -2342,17 +2342,11 @@ 16777215 - - % - - - 1 - - - 100 + + min-width: 68px; - - 100 + + Configure diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 296c58f58..7f66f29aa 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -300,6 +300,14 @@ void Config::ReadPlayerValue(std::size_t player_index) { static_cast(Settings::ControllerType::ProController)) .toUInt()); + player.vibration_enabled = + qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true) + .toBool(); + + player.vibration_strength = + qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100) + .toInt(); + player.body_color_left = qt_config ->value(QStringLiteral("%1body_color_left").arg(player_prefix), Settings::JOYCON_BODY_NEON_BLUE) @@ -493,6 +501,8 @@ void Config::ReadControlValues() { ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false); ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), true); + ReadSettingGlobal(Settings::values.enable_accurate_vibrations, + QStringLiteral("enable_accurate_vibrations"), false); ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true); qt_config->endGroup(); @@ -983,6 +993,10 @@ void Config::SavePlayerValue(std::size_t player_index) { if (!player_prefix.isEmpty()) { WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false); + WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix), + player.vibration_enabled, true); + WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix), + player.vibration_strength, 100); WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left, Settings::JOYCON_BODY_NEON_BLUE); WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix), @@ -1150,6 +1164,8 @@ void Config::SaveControlValues() { WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); + WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"), + Settings::values.enable_accurate_vibrations, false); WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true); WriteSetting(QStringLiteral("motion_device"), QString::fromStdString(Settings::values.motion_device), diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 9a4de4c5d..600cc03ae 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -23,6 +23,7 @@ #include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_mouse_advanced.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" +#include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" namespace { @@ -156,6 +157,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, CallConfigureDialog(*this, input_subsystem); }); + connect(ui->vibrationButton, &QPushButton::clicked, + [this] { CallConfigureDialog(*this); }); + connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { CallConfigureDialog(*this, input_subsystem); }); diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index cbd67d4c7..2707025e7 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -6,7 +6,7 @@ 0 0 - 700 + 680 540 @@ -195,11 +195,11 @@ 3 - + 68 - 21 + 0 @@ -208,17 +208,11 @@ 16777215 - - % - - - 1 - - - 100 + + min-width: 68px; - - 100 + + Configure @@ -272,7 +266,7 @@ - + 5 diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp new file mode 100644 index 000000000..1c68f28f3 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -0,0 +1,66 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/settings.h" +#include "ui_configure_vibration.h" +#include "yuzu/configuration/configure_vibration.h" + +ConfigureVibration::ConfigureVibration(QWidget* parent) + : QDialog(parent), ui(std::make_unique()) { + ui->setupUi(this); + + vibration_groupboxes = { + ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3, + ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6, + ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8, + }; + + vibration_spinboxes = { + ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3, + ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6, + ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8, + }; + + const auto& players = Settings::values.players.GetValue(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + vibration_groupboxes[i]->setChecked(players[i].vibration_enabled); + vibration_spinboxes[i]->setValue(players[i].vibration_strength); + } + + ui->checkBoxAccurateVibration->setChecked( + Settings::values.enable_accurate_vibrations.GetValue()); + + if (!Settings::IsConfiguringGlobal()) { + ui->checkBoxAccurateVibration->setDisabled(true); + } + + RetranslateUI(); +} + +ConfigureVibration::~ConfigureVibration() = default; + +void ConfigureVibration::ApplyConfiguration() { + auto& players = Settings::values.players.GetValue(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + players[i].vibration_enabled = vibration_groupboxes[i]->isChecked(); + players[i].vibration_strength = vibration_spinboxes[i]->value(); + } + + Settings::values.enable_accurate_vibrations.SetValue( + ui->checkBoxAccurateVibration->isChecked()); +} + +void ConfigureVibration::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureVibration::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h new file mode 100644 index 000000000..37bbc2653 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.h @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +class QGroupBox; +class QSpinBox; + +namespace Ui { +class ConfigureVibration; +} + +class ConfigureVibration : public QDialog { + Q_OBJECT + +public: + explicit ConfigureVibration(QWidget* parent); + ~ConfigureVibration() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr ui; + + static constexpr std::size_t NUM_PLAYERS = 8; + + // Groupboxes encapsulating the vibration strength spinbox. + std::array vibration_groupboxes; + + // Spinboxes representing the vibration strength percentage. + std::array vibration_spinboxes; +}; diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui new file mode 100644 index 000000000..efdf317a9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.ui @@ -0,0 +1,546 @@ + + + ConfigureVibration + + + + 0 + 0 + 364 + 242 + + + + Configure Vibration + + + + + + + + + Vibration + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Player 1 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + Player 2 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + Player 3 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + Player 4 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Player 5 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + Player 6 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + Player 7 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + Player 8 + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 1 + + + 150 + + + 100 + + + + + + + + + + + + + + + + Settings + + + + + + Enable Accurate Vibration + + + + + + + + + + Qt::Vertical + + + + 167 + 55 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBoxVibration + accepted() + ConfigureVibration + accept() + + + buttonBoxVibration + rejected() + ConfigureVibration + reject() + + + diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index feee02fcd..e1adbbf2b 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -290,6 +290,8 @@ void Config::ReadValues() { Settings::values.vibration_enabled.SetValue( sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true)); + Settings::values.enable_accurate_vibrations.SetValue( + sdl2_config->GetBoolean("ControlsGeneral", "enable_accurate_vibrations", false)); Settings::values.motion_enabled.SetValue( sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true)); Settings::values.touchscreen.enabled = diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index b6f6a3bb0..bcbbcd4ca 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -69,6 +69,10 @@ rstick= # 0: Disabled, 1 (default): Enabled vibration_enabled= +# Whether to enable or disable accurate vibrations +# 0 (default): Disabled, 1: Enabled +enable_accurate_vibrations= + # for motion input, the following devices are available: # - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: # - "update_period": update period in milliseconds (default to 100) diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp index 3a8a333f0..b6cdc7c1c 100644 --- a/src/yuzu_tester/config.cpp +++ b/src/yuzu_tester/config.cpp @@ -76,6 +76,7 @@ void Config::ReadValues() { } Settings::values.vibration_enabled.SetValue(true); + Settings::values.enable_accurate_vibrations.SetValue(false); Settings::values.motion_enabled.SetValue(true); Settings::values.touchscreen.enabled = ""; Settings::values.touchscreen.device = ""; -- cgit v1.2.3 From e9e1876e821b8bd1bb5c8254ec93e2cc479e16dd Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Tue, 20 Oct 2020 13:55:25 -0400 Subject: input_common: Add VibrationDevice and VibrationDeviceFactory A vibration device is an input device that returns an unsigned byte as status. It represents whether the vibration device supports vibration or not. If the status returns 1, it supports vibration. Otherwise, it does not support vibration. --- src/core/frontend/input.h | 7 ++ src/core/hle/service/hid/controllers/npad.cpp | 48 +++++--------- src/core/hle/service/hid/controllers/npad.h | 11 +++- src/core/hle/service/hid/hid.cpp | 1 + src/input_common/gcadapter/gc_adapter.cpp | 6 +- src/input_common/gcadapter/gc_adapter.h | 4 +- src/input_common/gcadapter/gc_poller.cpp | 50 +++++++++++--- src/input_common/gcadapter/gc_poller.h | 11 ++++ src/input_common/main.cpp | 5 ++ src/input_common/sdl/sdl_impl.cpp | 74 ++++++++++++++++----- src/input_common/sdl/sdl_impl.h | 2 + src/input_common/settings.cpp | 21 ++++-- src/input_common/settings.h | 32 +++++++-- src/yuzu/applets/controller.cpp | 2 + src/yuzu/configuration/config.cpp | 61 ++++++++++------- src/yuzu/configuration/configure_input_player.cpp | 7 ++ src/yuzu/configuration/configure_vibration.cpp | 80 +++++++++++++++++++++++ src/yuzu/configuration/configure_vibration.h | 3 + src/yuzu/main.cpp | 3 + 19 files changed, 327 insertions(+), 101 deletions(-) (limited to 'src/input_common') diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index fb2ce2514..25ac5af46 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -121,6 +121,13 @@ using ButtonDevice = InputDevice; */ using AnalogDevice = InputDevice>; +/** + * A vibration device is an input device that returns an unsigned byte as status. + * It represents whether the vibration device supports vibration or not. + * If the status returns 1, it supports vibration. Otherwise, it does not support vibration. + */ +using VibrationDevice = InputDevice; + /** * A motion status is an object that returns a tuple of accelerometer state vector, * gyroscope state vector, rotation state vector and orientation state matrix. diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index dc9954377..27099de24 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -271,6 +271,10 @@ void Controller_NPad::OnLoadInputDevices() { std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks[i].begin(), Input::CreateDevice); + std::transform(players[i].vibrations.begin() + + Settings::NativeVibration::VIBRATION_HID_BEGIN, + players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END, + vibrations[i].begin(), Input::CreateDevice); std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, motions[i].begin(), Input::CreateDevice); @@ -278,8 +282,10 @@ void Controller_NPad::OnLoadInputDevices() { } void Controller_NPad::OnRelease() { - for (std::size_t index = 0; index < connected_controllers.size(); ++index) { - VibrateControllerAtIndex(index, {}); + for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) { + for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) { + VibrateControllerAtIndex(npad_idx, device_idx); + } } } @@ -674,9 +680,9 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) } } -bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, +bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, const VibrationValue& vibration_value) { - if (!connected_controllers[npad_index].is_connected) { + if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) { return false; } @@ -686,10 +692,7 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, return false; } - using namespace Settings::NativeButton; - const auto& button_state = buttons[npad_index]; - - return button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( + return vibrations[npad_index][device_index]->SetRumblePlay( std::min(vibration_value.amp_low * player.vibration_strength / 100.0f, 1.0f), vibration_value.freq_low, std::min(vibration_value.amp_high * player.vibration_strength / 100.0f, 1.0f), @@ -717,6 +720,11 @@ void Controller_NPad::VibrateControllers(const std::vector& vibrat continue; } + if (vibration_device_handles[i].device_index == DeviceIndex::None) { + UNREACHABLE_MSG("DeviceIndex should never be None!"); + continue; + } + // Some games try to send mismatched parameters in the device handle, block these. if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && (vibration_device_handles[i].npad_type == NpadType::JoyconRight || @@ -747,28 +755,8 @@ void Controller_NPad::VibrateControllers(const std::vector& vibrat continue; } - // TODO: Vibrate left/right vibration motors independently if possible. - if (VibrateControllerAtIndex(npad_index, vibration_values[i])) { - switch (connected_controllers[npad_index].type) { - case NPadControllerType::None: - UNREACHABLE(); - break; - case NPadControllerType::ProController: - case NPadControllerType::Handheld: - case NPadControllerType::JoyDual: - // Since we can't vibrate motors independently yet, we can reduce state changes by - // assigning all 3 device indices the current vibration value. - latest_vibration_values[npad_index][0] = vibration_values[i]; - latest_vibration_values[npad_index][1] = vibration_values[i]; - latest_vibration_values[npad_index][2] = vibration_values[i]; - break; - case NPadControllerType::JoyLeft: - case NPadControllerType::JoyRight: - case NPadControllerType::Pokeball: - default: - latest_vibration_values[npad_index][device_index] = vibration_values[i]; - break; - } + if (VibrateControllerAtIndex(npad_index, device_index, vibration_values[i])) { + latest_vibration_values[npad_index][device_index] = vibration_values[i]; } } } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 576ef1558..3ae9fb8e6 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -148,7 +148,8 @@ public: void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode); - bool VibrateControllerAtIndex(std::size_t npad_index, const VibrationValue& vibration_value); + bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, + const VibrationValue& vibration_value = {}); void VibrateControllers(const std::vector& vibration_device_handles, const std::vector& vibration_values); @@ -399,18 +400,22 @@ private: using StickArray = std::array< std::array, Settings::NativeAnalog::NUM_STICKS_HID>, 10>; + using VibrationArray = std::array, + Settings::NativeVibration::NUM_VIBRATIONS_HID>, + 10>; using MotionArray = std::array< - std::array, Settings::NativeMotion::NUM_MOTION_HID>, + std::array, Settings::NativeMotion::NUM_MOTIONS_HID>, 10>; ButtonArray buttons; StickArray sticks; + VibrationArray vibrations; MotionArray motions; std::vector supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; // Each controller should have their own styleset changed event std::array styleset_changed_events; - std::array, 10> latest_vibration_values; + std::array, 10> latest_vibration_values{}; std::array connected_controllers{}; std::array unintended_home_button_input_protection{}; GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index e88f30d6a..1d882a977 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -998,6 +998,7 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { break; case Controller_NPad::DeviceIndex::None: default: + UNREACHABLE_MSG("DeviceIndex should never be None!"); vibration_device_info.position = VibrationDevicePosition::None; break; } diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index b912188b6..d80195c82 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -230,10 +230,8 @@ void Adapter::SendVibrations() { vibration_changed = false; } -bool Adapter::RumblePlay(std::size_t port, f32 amplitude) { - amplitude = std::clamp(amplitude, 0.0f, 1.0f); - const auto raw_amp = static_cast(amplitude * 0x8); - pads[port].rumble_amplitude = raw_amp; +bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { + pads[port].rumble_amplitude = amplitude; return rumble_enabled; } diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index d28dcfad3..f1256c9da 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -77,8 +77,8 @@ public: Adapter(); ~Adapter(); - /// Request a vibration for a controlelr - bool RumblePlay(std::size_t port, f32 amplitude); + /// Request a vibration for a controller + bool RumblePlay(std::size_t port, u8 amplitude); /// Used for polling void BeginConfiguration(); diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 6bd6f57fc..fe57c13a5 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,7 +15,7 @@ namespace InputCommon { class GCButton final : public Input::ButtonDevice { public: - explicit GCButton(u32 port_, s32 button_, GCAdapter::Adapter* adapter) + explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter) : port(port_), button(button_), gcadapter(adapter) {} ~GCButton() override; @@ -27,18 +27,10 @@ public: return false; } - bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override { - const float amplitude = amp_high + amp_low > 2.0f ? 1.0f : (amp_high + amp_low) * 0.5f; - const auto new_amp = - static_cast(pow(amplitude, 0.5f) * (3.0f - 2.0f * pow(amplitude, 0.15f))); - - return gcadapter->RumblePlay(port, new_amp); - } - private: const u32 port; const s32 button; - GCAdapter::Adapter* gcadapter; + const GCAdapter::Adapter* gcadapter; }; class GCAxisButton final : public Input::ButtonDevice { @@ -299,4 +291,42 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() { return params; } +class GCVibration final : public Input::VibrationDevice { +public: + explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) + : port(port_), gcadapter(adapter) {} + + u8 GetStatus() const override { + return gcadapter->RumblePlay(port, 0); + } + + bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { + const auto mean_amplitude = (amp_low + amp_high) * 0.5f; + const auto processed_amplitude = static_cast( + pow(mean_amplitude, 0.5f) * (3.0f - 2.0f * pow(mean_amplitude, 0.15f)) * 0x8); + + return gcadapter->RumblePlay(port, processed_amplitude); + } + +private: + const u32 port; + GCAdapter::Adapter* gcadapter; +}; + +/// An vibration device factory that creates vibration devices from GC Adapter +GCVibrationFactory::GCVibrationFactory(std::shared_ptr adapter_) + : adapter(std::move(adapter_)) {} + +/** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + */ +std::unique_ptr GCVibrationFactory::Create( + const Common::ParamPackage& params) { + const auto port = static_cast(params.Get("port", 0)); + + return std::make_unique(port, adapter.get()); +} + } // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index 0527f328f..d1271e3ea 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/src/input_common/gcadapter/gc_poller.h @@ -64,4 +64,15 @@ private: bool polling = false; }; +/// A vibration device factory creates vibration devices from GC Adapter +class GCVibrationFactory final : public Input::Factory { +public: + explicit GCVibrationFactory(std::shared_ptr adapter_); + + std::unique_ptr Create(const Common::ParamPackage& params) override; + +private: + std::shared_ptr adapter; +}; + } // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b438482cc..e59ad4ff5 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -28,6 +28,8 @@ struct InputSubsystem::Impl { Input::RegisterFactory("gcpad", gcbuttons); gcanalog = std::make_shared(gcadapter); Input::RegisterFactory("gcpad", gcanalog); + gcvibration = std::make_shared(gcadapter); + Input::RegisterFactory("gcpad", gcvibration); keyboard = std::make_shared(); Input::RegisterFactory("keyboard", keyboard); @@ -64,9 +66,11 @@ struct InputSubsystem::Impl { #endif Input::UnregisterFactory("gcpad"); Input::UnregisterFactory("gcpad"); + Input::UnregisterFactory("gcpad"); gcbuttons.reset(); gcanalog.reset(); + gcvibration.reset(); Input::UnregisterFactory("cemuhookudp"); Input::UnregisterFactory("cemuhookudp"); @@ -142,6 +146,7 @@ struct InputSubsystem::Impl { #endif std::shared_ptr gcbuttons; std::shared_ptr gcanalog; + std::shared_ptr gcvibration; std::shared_ptr udpmotion; std::shared_ptr udptouch; std::shared_ptr udp; diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 18fb2ac5e..a2a83cdc9 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -85,16 +85,17 @@ public: using std::chrono::milliseconds; using std::chrono::steady_clock; - // Prevent vibrations less than 10ms apart from each other. - if (duration_cast(steady_clock::now() - last_vibration) < milliseconds(10)) { + // Block non-zero vibrations less than 10ms apart from each other. + if ((amp_low != 0 || amp_high != 0) && + duration_cast(steady_clock::now() - last_vibration) < milliseconds(10)) { return false; - }; + } last_vibration = steady_clock::now(); - if (sdl_controller != nullptr) { + if (sdl_controller) { return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0; - } else if (sdl_joystick != nullptr) { + } else if (sdl_joystick) { return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0; } @@ -321,14 +322,6 @@ public: return joystick->GetButton(button); } - bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { - const u16 processed_amp_low = - static_cast(pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)) * 0xFFFF); - const u16 processed_amp_high = - static_cast(pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f)) * 0xFFFF); - return joystick->RumblePlay(processed_amp_low, processed_amp_high); - } - private: std::shared_ptr joystick; int button; @@ -412,6 +405,32 @@ private: const float range; }; +class SDLVibration final : public Input::VibrationDevice { +public: + explicit SDLVibration(std::shared_ptr joystick_) + : joystick(std::move(joystick_)) {} + + u8 GetStatus() const override { + joystick->RumblePlay(1, 1); + return joystick->RumblePlay(0, 0); + } + + bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { + const auto process_amplitude = [](f32 amplitude) { + return static_cast(std::pow(amplitude, 0.5f) * + (3.0f - 2.0f * std::pow(amplitude, 0.15f)) * 0xFFFF); + }; + + const auto processed_amp_low = process_amplitude(amp_low); + const auto processed_amp_high = process_amplitude(amp_high); + + return joystick->RumblePlay(processed_amp_low, processed_amp_high); + } + +private: + std::shared_ptr joystick; +}; + class SDLDirectionMotion final : public Input::MotionDevice { public: explicit SDLDirectionMotion(std::shared_ptr joystick_, int hat_, Uint8 direction_) @@ -554,7 +573,7 @@ class SDLAnalogFactory final : public Input::Factory { public: explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} /** - * Creates analog device from joystick axes + * Creates an analog device from joystick axes * @param params contains parameters for creating the device: * - "guid": the guid of the joystick to bind * - "port": the nth joystick of the same type @@ -580,6 +599,26 @@ private: SDLState& state; }; +/// An vibration device factory that creates vibration devices from SDL joystick +class SDLVibrationFactory final : public Input::Factory { +public: + explicit SDLVibrationFactory(SDLState& state_) : state(state_) {} + /** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type + */ + std::unique_ptr Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + return std::make_unique(state.GetSDLJoystickByGUID(guid, port)); + } + +private: + SDLState& state; +}; + /// A motion device factory that creates motion devices from SDL joystick class SDLMotionFactory final : public Input::Factory { public: @@ -646,11 +685,13 @@ private: SDLState::SDLState() { using namespace Input; - analog_factory = std::make_shared(*this); button_factory = std::make_shared(*this); + analog_factory = std::make_shared(*this); + vibration_factory = std::make_shared(*this); motion_factory = std::make_shared(*this); - RegisterFactory("sdl", analog_factory); RegisterFactory("sdl", button_factory); + RegisterFactory("sdl", analog_factory); + RegisterFactory("sdl", vibration_factory); RegisterFactory("sdl", motion_factory); // If the frontend is going to manage the event loop, then we don't start one here @@ -687,6 +728,7 @@ SDLState::~SDLState() { using namespace Input; UnregisterFactory("sdl"); UnregisterFactory("sdl"); + UnregisterFactory("sdl"); UnregisterFactory("sdl"); CloseJoysticks(); diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index b9bb4dc56..08044b00d 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -22,6 +22,7 @@ namespace InputCommon::SDL { class SDLAnalogFactory; class SDLButtonFactory; class SDLMotionFactory; +class SDLVibrationFactory; class SDLJoystick; class SDLState : public State { @@ -72,6 +73,7 @@ private: std::shared_ptr button_factory; std::shared_ptr analog_factory; + std::shared_ptr vibration_factory; std::shared_ptr motion_factory; bool start_thread = false; diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp index b66c05856..557e7a9a0 100644 --- a/src/input_common/settings.cpp +++ b/src/input_common/settings.cpp @@ -14,13 +14,6 @@ const std::array mapping = {{ }}; } -namespace NativeMotion { -const std::array mapping = {{ - "motionleft", - "motionright", -}}; -} - namespace NativeAnalog { const std::array mapping = {{ "lstick", @@ -28,6 +21,20 @@ const std::array mapping = {{ }}; } +namespace NativeVibration { +const std::array mapping = {{ + "left_vibration_device", + "right_vibration_device", +}}; +} + +namespace NativeMotion { +const std::array mapping = {{ + "motionleft", + "motionright", +}}; +} + namespace NativeMouseButton { const std::array mapping = {{ "left", diff --git a/src/input_common/settings.h b/src/input_common/settings.h index 2763ed991..75486554b 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -66,17 +66,32 @@ constexpr int NUM_STICKS_HID = NumAnalogs; extern const std::array mapping; } // namespace NativeAnalog +namespace NativeVibration { +enum Values : int { + LeftVibrationDevice, + RightVibrationDevice, + + NumVibrations, +}; + +constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice; +constexpr int VIBRATION_HID_END = NumVibrations; +constexpr int NUM_VIBRATIONS_HID = NumVibrations; + +extern const std::array mapping; +}; // namespace NativeVibration + namespace NativeMotion { enum Values : int { - MOTIONLEFT, - MOTIONRIGHT, + MotionLeft, + MotionRight, NumMotions, }; -constexpr int MOTION_HID_BEGIN = MOTIONLEFT; +constexpr int MOTION_HID_BEGIN = MotionLeft; constexpr int MOTION_HID_END = NumMotions; -constexpr int NUM_MOTION_HID = NumMotions; +constexpr int NUM_MOTIONS_HID = NumMotions; extern const std::array mapping; } // namespace NativeMotion @@ -305,9 +320,11 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; } // namespace NativeKeyboard -using ButtonsRaw = std::array; using AnalogsRaw = std::array; -using MotionRaw = std::array; +using ButtonsRaw = std::array; +using MotionsRaw = std::array; +using VibrationsRaw = std::array; + using MouseButtonsRaw = std::array; using KeyboardKeysRaw = std::array; using KeyboardModsRaw = std::array; @@ -330,7 +347,8 @@ struct PlayerInput { ControllerType controller_type; ButtonsRaw buttons; AnalogsRaw analogs; - MotionRaw motions; + VibrationsRaw vibrations; + MotionsRaw motions; bool vibration_enabled; int vibration_strength; diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index c5e671309..cdcd3d28d 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -478,6 +478,8 @@ void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) return; } + ConfigureVibration::SetVibrationDevices(player_index); + // Player 1 and Handheld auto& handheld = Settings::values.players.GetValue()[8]; // If Handheld is selected, copy all the settings from Player 1 to Handheld. diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 7f66f29aa..6fa842cd5 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -344,21 +344,6 @@ void Config::ReadPlayerValue(std::size_t player_index) { } } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); - auto& player_motions = player.motions[i]; - - player_motions = qt_config - ->value(QStringLiteral("%1").arg(player_prefix) + - QString::fromUtf8(Settings::NativeMotion::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_motions.empty()) { - player_motions = default_param; - } - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], @@ -375,6 +360,33 @@ void Config::ReadPlayerValue(std::size_t player_index) { player_analogs = default_param; } } + + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + auto& player_vibrations = player.vibrations[i]; + + player_vibrations = + qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeVibration::mapping[i]), + QString{}) + .toString() + .toStdString(); + } + + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = default_param; + } + } } void Config::ReadDebugValues() { @@ -1014,13 +1026,6 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.buttons[i]), QString::fromStdString(default_param)); } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteSetting(QStringLiteral("%1").arg(player_prefix) + - QString::fromStdString(Settings::NativeMotion::mapping[i]), - QString::fromStdString(player.motions[i]), - QString::fromStdString(default_param)); - } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], @@ -1030,6 +1035,18 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.analogs[i]), QString::fromStdString(default_param)); } + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeVibration::mapping[i]), + QString::fromStdString(player.vibrations[i]), QString{}); + } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[i]), + QString::fromStdString(default_param)); + } } void Config::SaveDebugValues() { diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 460ff08a4..3e785c224 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -22,6 +22,7 @@ #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" #include "yuzu/util/limitable_input_dialog.h" @@ -39,6 +40,10 @@ namespace { void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, bool connected) { + auto& player = Settings::values.players.GetValue()[npad_index]; + player.controller_type = controller_type; + player.connected = connected; + Core::System& system{Core::System::GetInstance()}; if (!system.IsPoweredOn()) { return; @@ -565,6 +570,8 @@ void ConfigureInputPlayer::ApplyConfiguration() { static_cast(ui->comboControllerType->currentIndex()); player.connected = ui->groupConnectedController->isChecked(); + ConfigureVibration::SetVibrationDevices(player_index); + // Player 2-8 if (player_index != 0) { UpdateController(player.controller_type, player_index, player.connected); diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index 1c68f28f3..714db5b80 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -2,6 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include + +#include + +#include "common/param_package.h" #include "core/settings.h" #include "ui_configure_vibration.h" #include "yuzu/configuration/configure_vibration.h" @@ -53,6 +59,80 @@ void ConfigureVibration::ApplyConfiguration() { ui->checkBoxAccurateVibration->isChecked()); } +void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { + using namespace Settings::NativeButton; + static constexpr std::array, 2> buttons{{ + {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons + {A, B, X, Y, R, ZR}, // Right Buttons + }}; + + auto& player = Settings::values.players.GetValue()[player_index]; + + for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) { + std::unordered_map params_count; + + for (const auto button_index : buttons[device_idx]) { + const auto& player_button = player.buttons[button_index]; + + if (params_count.find(player_button) != params_count.end()) { + ++params_count[player_button]; + continue; + } + + params_count.insert_or_assign(player_button, 1); + } + + const auto it = std::max_element( + params_count.begin(), params_count.end(), + [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; }); + + auto& vibration_param_str = player.vibrations[device_idx]; + vibration_param_str.clear(); + + if (it->first.empty()) { + continue; + } + + const auto param = Common::ParamPackage(it->first); + + const auto engine = param.Get("engine", ""); + const auto guid = param.Get("guid", ""); + const auto port = param.Get("port", ""); + + if (engine.empty() || engine == "keyboard") { + continue; + } + + vibration_param_str += fmt::format("engine:{}", engine); + + if (!port.empty()) { + vibration_param_str += fmt::format(",port:{}", port); + } + if (!guid.empty()) { + vibration_param_str += fmt::format(",guid:{}", guid); + } + } + + if (player.vibrations[0] != player.vibrations[1]) { + return; + } + + if (!player.vibrations[0].empty() && + player.controller_type != Settings::ControllerType::RightJoycon) { + player.vibrations[1].clear(); + } else if (!player.vibrations[1].empty() && + player.controller_type == Settings::ControllerType::RightJoycon) { + player.vibrations[0].clear(); + } +} + +void ConfigureVibration::SetAllVibrationDevices() { + // Set vibration devices for all player indices including handheld + for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) { + SetVibrationDevices(player_idx); + } +} + void ConfigureVibration::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h index 37bbc2653..07411a86f 100644 --- a/src/yuzu/configuration/configure_vibration.h +++ b/src/yuzu/configuration/configure_vibration.h @@ -24,6 +24,9 @@ public: void ApplyConfiguration(); + static void SetVibrationDevices(std::size_t player_index); + static void SetAllVibrationDevices(); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 54a46827f..76a5c32f4 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -18,6 +18,7 @@ #include "applets/web_browser.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/controller.h" @@ -1096,6 +1097,8 @@ void GMainWindow::BootGame(const QString& filename) { Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig); } + ConfigureVibration::SetAllVibrationDevices(); + Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { -- cgit v1.2.3 From 30e0d1c973290f4813b040eecf83ff4a2c7432c3 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 25 Oct 2020 07:30:23 -0400 Subject: controllers/npad: Remove the old vibration filter Previously we used a vibration filter that filters out amplitudes close to each other. It turns out there are cases where this results into vibrations that are too inaccurate. Remove this and move the 100Hz vibration filter (Only allowing a maximum of 100 vibrations per second) from sdl_impl to npad when enable_accurate_vibrations is set to false. --- src/core/hle/service/hid/controllers/npad.cpp | 108 ++++++++++++++------------ src/core/hle/service/hid/controllers/npad.h | 4 + src/core/hle/service/hid/hid.cpp | 2 +- src/input_common/sdl/sdl_impl.cpp | 15 ---- 4 files changed, 64 insertions(+), 65 deletions(-) (limited to 'src/input_common') diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index ecc33bc08..cfafabbd8 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -703,6 +703,23 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size return false; } + if (!Settings::values.enable_accurate_vibrations.GetValue()) { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::steady_clock; + + const auto now = steady_clock::now(); + + // Filter out non-zero vibrations that are within 10ms of each other. + if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) && + duration_cast(now - last_vibration_timepoints[npad_index][device_index]) < + milliseconds(10)) { + return false; + } + + last_vibration_timepoints[npad_index][device_index] = now; + } + return vibrations[npad_index][device_index]->SetRumblePlay( std::min(vibration_value.amp_low * player.vibration_strength / 100.0f, 1.0f), vibration_value.freq_low, @@ -710,66 +727,59 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size vibration_value.freq_high); } -void Controller_NPad::VibrateControllers(const std::vector& vibration_device_handles, - const std::vector& vibration_values) { - LOG_TRACE(Service_HID, "called"); - +void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle, + const VibrationValue& vibration_value) { if (!Settings::values.vibration_enabled.GetValue()) { return; } - ASSERT_MSG(vibration_device_handles.size() == vibration_values.size(), - "The amount of device handles does not match with the amount of vibration values," - "this is undefined behavior!"); + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast(vibration_device_handle.device_index); - for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) { - const auto npad_index = NPadIdToIndex(vibration_device_handles[i].npad_id); - const auto device_index = - static_cast(vibration_device_handles[i].device_index); + if (!vibration_devices_mounted[npad_index][device_index] || + !connected_controllers[npad_index].is_connected) { + return; + } - if (!vibration_devices_mounted[npad_index][device_index] || - !connected_controllers[npad_index].is_connected) { - continue; - } + if (vibration_device_handle.device_index == DeviceIndex::None) { + UNREACHABLE_MSG("DeviceIndex should never be None!"); + return; + } - if (vibration_device_handles[i].device_index == DeviceIndex::None) { - UNREACHABLE_MSG("DeviceIndex should never be None!"); - continue; - } + // Some games try to send mismatched parameters in the device handle, block these. + if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && + (vibration_device_handle.npad_type == NpadType::JoyconRight || + vibration_device_handle.device_index == DeviceIndex::Right)) || + (connected_controllers[npad_index].type == NPadControllerType::JoyRight && + (vibration_device_handle.npad_type == NpadType::JoyconLeft || + vibration_device_handle.device_index == DeviceIndex::Left))) { + return; + } - // Some games try to send mismatched parameters in the device handle, block these. - if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && - (vibration_device_handles[i].npad_type == NpadType::JoyconRight || - vibration_device_handles[i].device_index == DeviceIndex::Right)) || - (connected_controllers[npad_index].type == NPadControllerType::JoyRight && - (vibration_device_handles[i].npad_type == NpadType::JoyconLeft || - vibration_device_handles[i].device_index == DeviceIndex::Left))) { - continue; - } + // Filter out vibrations with equivalent values to reduce unnecessary state changes. + if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low && + vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) { + return; + } - // Filter out vibrations with equivalent values to reduce unnecessary state changes. - if (vibration_values[i].amp_low == - latest_vibration_values[npad_index][device_index].amp_low && - vibration_values[i].amp_high == - latest_vibration_values[npad_index][device_index].amp_high) { - continue; - } + if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) { + latest_vibration_values[npad_index][device_index] = vibration_value; + } +} - // Filter out non-zero vibrations that are within 0.015625 absolute amplitude of each other. - if (!Settings::values.enable_accurate_vibrations.GetValue() && - (vibration_values[i].amp_low != 0.0f || vibration_values[i].amp_high != 0.0f) && - (latest_vibration_values[npad_index][device_index].amp_low != 0.0f || - latest_vibration_values[npad_index][device_index].amp_high != 0.0f) && - (abs(vibration_values[i].amp_low - - latest_vibration_values[npad_index][device_index].amp_low) < 0.015625f && - abs(vibration_values[i].amp_high - - latest_vibration_values[npad_index][device_index].amp_high) < 0.015625f)) { - continue; - } +void Controller_NPad::VibrateControllers(const std::vector& vibration_device_handles, + const std::vector& vibration_values) { + if (!Settings::values.vibration_enabled.GetValue()) { + return; + } - if (VibrateControllerAtIndex(npad_index, device_index, vibration_values[i])) { - latest_vibration_values[npad_index][device_index] = vibration_values[i]; - } + ASSERT_OR_EXECUTE_MSG( + vibration_device_handles.size() == vibration_values.size(), { return; }, + "The amount of device handles does not match with the amount of vibration values," + "this is undefined behavior!"); + + for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) { + VibrateController(vibration_device_handles[i], vibration_values[i]); } } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 30e3cb02f..f5122124c 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -151,6 +151,9 @@ public: bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, const VibrationValue& vibration_value = {}); + void VibrateController(const DeviceHandle& vibration_device_handle, + const VibrationValue& vibration_value); + void VibrateControllers(const std::vector& vibration_device_handles, const std::vector& vibration_values); @@ -421,6 +424,7 @@ private: NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; // Each controller should have their own styleset changed event std::array styleset_changed_events; + std::array, 10> last_vibration_timepoints; std::array, 10> latest_vibration_values{}; std::array, 10> vibration_devices_mounted{}; std::array connected_controllers{}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index ecaa847b2..2e9682bed 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -1029,7 +1029,7 @@ void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .VibrateControllers({parameters.vibration_device_handle}, {parameters.vibration_value}); + .VibrateController(parameters.vibration_device_handle, parameters.vibration_value); LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index a2a83cdc9..a9f7e5103 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -81,18 +81,6 @@ public: } bool RumblePlay(u16 amp_low, u16 amp_high) { - using std::chrono::duration_cast; - using std::chrono::milliseconds; - using std::chrono::steady_clock; - - // Block non-zero vibrations less than 10ms apart from each other. - if ((amp_low != 0 || amp_high != 0) && - duration_cast(steady_clock::now() - last_vibration) < milliseconds(10)) { - return false; - } - - last_vibration = steady_clock::now(); - if (sdl_controller) { return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0; } else if (sdl_joystick) { @@ -171,9 +159,6 @@ private: std::unique_ptr sdl_controller; mutable std::mutex mutex; - // This is the timepoint of the last vibration and is used to ensure vibrations are 10ms apart. - std::chrono::steady_clock::time_point last_vibration; - // Motion is initialized without PID values as motion input is not aviable for SDL2 MotionInput motion{0.0f, 0.0f, 0.0f}; }; -- cgit v1.2.3 From 117bdc71e016629b9355b33a6d64655f0245f833 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Tue, 27 Oct 2020 13:15:57 -0400 Subject: sdl_impl: Revert to the "old" method of mapping sticks Not all controllers have a SDL_GameController binding. This caused controllers not present in the SDL GameController database to have buttons mapped instead of axes. Furthermore, it was not possible to invert the axes when it could be useful such as emulating a horizontal single joycon or other potential cases. This allows us to invert the axes by reversing the order of mapping (vertical, then horizontal). --- src/input_common/sdl/sdl_impl.cpp | 45 +++++++---------------- src/yuzu/configuration/configure_input_player.cpp | 12 ++++++ src/yuzu/configuration/configure_input_player.h | 5 ++- 3 files changed, 29 insertions(+), 33 deletions(-) (limited to 'src/input_common') diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index a9f7e5103..6024ed97a 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -1068,7 +1068,6 @@ public: void Start(const std::string& device_id) override { SDLPoller::Start(device_id); - // Load the game controller // Reset stored axes analog_x_axis = -1; analog_y_axis = -1; @@ -1081,40 +1080,21 @@ public: if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) { continue; } - // Simplify controller config by testing if game controller support is enabled. if (event.type == SDL_JOYAXISMOTION) { const auto axis = event.jaxis.axis; - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - auto* const controller = joystick->GetSDLGameController()) { - const auto axis_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) - .value.axis; - const auto axis_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) - .value.axis; - const auto axis_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) - .value.axis; - const auto axis_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY) - .value.axis; - - if (axis == axis_left_x || axis == axis_left_y) { - analog_x_axis = axis_left_x; - analog_y_axis = axis_left_y; - break; - } else if (axis == axis_right_x || axis == axis_right_y) { - analog_x_axis = axis_right_x; - analog_y_axis = axis_right_y; - break; - } + // In order to return a complete analog param, we need inputs for both axes. + // First we take the x-axis (horizontal) input, then the y-axis (vertical) input. + if (analog_x_axis == -1) { + analog_x_axis = axis; + } else if (analog_y_axis == -1 && analog_x_axis != axis) { + analog_y_axis = axis; + } + } else { + // If the press wasn't accepted as a joy axis, check for a button press + auto button_press = button_poller.FromEvent(event); + if (button_press) { + return *button_press; } - } - - // If the press wasn't accepted as a joy axis, check for a button press - auto button_press = button_poller.FromEvent(event); - if (button_press) { - return *button_press; } } @@ -1127,6 +1107,7 @@ public: return params; } } + return {}; } diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 4ed704793..5abf9f0bf 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -370,6 +370,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } connect(analog_button, &QPushButton::clicked, [=, this] { + if (!map_analog_stick_accepted) { + map_analog_stick_accepted = + QMessageBox::information( + this, tr("Map Analog Stick"), + tr("After pressing OK, first move your joystick horizontally, and then " + "vertically.\nTo invert the axes, first move your joystick " + "vertically, and then horizontally."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok; + if (!map_analog_stick_accepted) { + return; + } + } HandleClick( analog_map_buttons[analog_id][sub_button_id], [=, this](const Common::ParamPackage& params) { diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 05dee5af5..4895e8850 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -181,9 +181,12 @@ private: std::vector> device_pollers; + /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. + bool map_analog_stick_accepted{}; + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, /// keyboard events are ignored. - bool want_keyboard_mouse = false; + bool want_keyboard_mouse{}; /// List of physical devices users can map with. If a SDL backed device is selected, then you /// can use this device to get a default mapping. -- cgit v1.2.3 From e7e8a87927899b69bfe9f8e38f26dac08ec37abe Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 15 Nov 2020 23:32:58 -0500 Subject: sdl_impl: Pump SDL Events at 1000 Hz --- src/input_common/sdl/sdl_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/input_common') diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 6024ed97a..8c48bb861 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -698,7 +698,7 @@ SDLState::SDLState() { using namespace std::chrono_literals; while (initialized) { SDL_PumpEvents(); - std::this_thread::sleep_for(5ms); + std::this_thread::sleep_for(1ms); } }); } -- cgit v1.2.3