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.
This commit is contained in:
		| @@ -33,7 +33,7 @@ public: | |||||||
|     virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const { |     virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const { | ||||||
|         return {}; |         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 {}; |         return {}; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -698,16 +698,57 @@ void Controller_NPad::VibrateController(const std::vector<DeviceHandle>& vibrati | |||||||
|             continue; |             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; |         using namespace Settings::NativeButton; | ||||||
|         const auto& button_state = buttons[npad_index]; |         const auto& button_state = buttons[npad_index]; | ||||||
|  |  | ||||||
|         // TODO: Vibrate left/right vibration motors independently if possible. |         // TODO: Vibrate left/right vibration motors independently if possible. | ||||||
|         button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( |         const bool success = button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( | ||||||
|             vibration_values[i].amp_high * Settings::values.vibration_strength.GetValue() / 100, |  | ||||||
|             vibration_values[i].amp_low * Settings::values.vibration_strength.GetValue() / 100, |             vibration_values[i].amp_low * Settings::values.vibration_strength.GetValue() / 100, | ||||||
|             vibration_values[i].freq_high, vibration_values[i].freq_low); |             vibration_values[i].freq_low, | ||||||
|  |             vibration_values[i].amp_high * Settings::values.vibration_strength.GetValue() / 100, | ||||||
|  |             vibration_values[i].freq_high); | ||||||
|  |  | ||||||
|         latest_vibration_values[npad_index][device_index] = vibration_values[i]; |         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; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -80,30 +80,24 @@ public: | |||||||
|         return static_cast<float>(state.axes.at(axis)) / (32767.0f * range); |         return static_cast<float>(state.axes.at(axis)) / (32767.0f * range); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool RumblePlay(f32 amp_low, f32 amp_high, u32 time) { |     bool RumblePlay(u16 amp_low, u16 amp_high) { | ||||||
|         const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF); |         using std::chrono::duration_cast; | ||||||
|         const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF); |         using std::chrono::milliseconds; | ||||||
|         // Lower drastically the number of state changes |         using std::chrono::steady_clock; | ||||||
|         if (raw_amp_low >> 11 == last_state_rumble_low >> 11 && |  | ||||||
|             raw_amp_high >> 11 == last_state_rumble_high >> 11) { |         // Prevent vibrations less than 10ms apart from each other. | ||||||
|             if (raw_amp_low + raw_amp_high != 0 || |         if (duration_cast<milliseconds>(steady_clock::now() - last_vibration) < milliseconds(10)) { | ||||||
|                 last_state_rumble_low + last_state_rumble_high == 0) { |             return false; | ||||||
|                 return false; |         }; | ||||||
|             } |  | ||||||
|         } |         last_vibration = steady_clock::now(); | ||||||
|         // Don't change state if last vibration was < 20ms |  | ||||||
|         const auto now = std::chrono::system_clock::now(); |         if (sdl_controller != nullptr) { | ||||||
|         if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) < |             return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0; | ||||||
|             std::chrono::milliseconds(20)) { |         } else if (sdl_joystick != nullptr) { | ||||||
|             return raw_amp_low + raw_amp_high == 0; |             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; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -172,13 +166,13 @@ private: | |||||||
|     } state; |     } state; | ||||||
|     std::string guid; |     std::string guid; | ||||||
|     int port; |     int port; | ||||||
|     u16 last_state_rumble_high = 0; |  | ||||||
|     u16 last_state_rumble_low = 0; |  | ||||||
|     std::chrono::time_point<std::chrono::system_clock> last_vibration; |  | ||||||
|     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; |     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; | ||||||
|     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; |     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; | ||||||
|     mutable std::mutex mutex; |     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 |     // Motion is initialized without PID values as motion input is not aviable for SDL2 | ||||||
|     MotionInput motion{0.0f, 0.0f, 0.0f}; |     MotionInput motion{0.0f, 0.0f, 0.0f}; | ||||||
| }; | }; | ||||||
| @@ -327,10 +321,12 @@ public: | |||||||
|         return joystick->GetButton(button); |         return joystick->GetButton(button); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override { |     bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { | ||||||
|         const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)); |         const u16 processed_amp_low = | ||||||
|         const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f)); |             static_cast<u16>(pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)) * 0xFFFF); | ||||||
|         return joystick->RumblePlay(new_amp_low, new_amp_high, 250); |         const u16 processed_amp_high = | ||||||
|  |             static_cast<u16>(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: | private: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Morph
					Morph