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:
Morph 2020-10-10 09:03:47 -04:00
parent 652d6766d5
commit 9b501af8e3
3 changed files with 71 additions and 34 deletions

View File

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

View File

@ -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);
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]; latest_vibration_values[npad_index][device_index] = vibration_values[i];
break;
}
}
} }
} }

View File

@ -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;
} };
}
// Don't change state if last vibration was < 20ms last_vibration = steady_clock::now();
const auto now = std::chrono::system_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) < if (sdl_controller != nullptr) {
std::chrono::milliseconds(20)) { return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0;
return raw_amp_low + raw_amp_high == 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; 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: