CommonHostInterface: Reimplement controller rumble support

Even better than before, supports separate motor control.
This commit is contained in:
Connor McLaughlin
2020-04-14 16:34:39 +10:00
parent 7677c95fa7
commit d9ebb975b2
16 changed files with 242 additions and 38 deletions

View File

@ -335,8 +335,20 @@ void CommonHostInterface::OnSystemPaused(bool paused)
{
HostInterface::OnSystemPaused(paused);
if (paused && IsFullscreen())
SetFullscreen(false);
if (paused)
{
if (IsFullscreen())
SetFullscreen(false);
StopControllerRumble();
}
}
void CommonHostInterface::OnSystemDestroyed()
{
HostInterface::OnSystemDestroyed();
StopControllerRumble();
}
void CommonHostInterface::OnControllerTypeChanged(u32 slot)
@ -405,6 +417,55 @@ void CommonHostInterface::UpdateInputMap(SettingsInterface& si)
UpdateHotkeyInputMap(si);
}
void CommonHostInterface::AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback)
{
ControllerRumbleState rumble;
rumble.controller_index = 0;
rumble.num_motors = std::min<u32>(num_motors, ControllerRumbleState::MAX_MOTORS);
rumble.last_strength.fill(0.0f);
rumble.update_callback = std::move(callback);
m_controller_vibration_motors.push_back(std::move(rumble));
}
void CommonHostInterface::UpdateControllerRumble()
{
DebugAssert(m_system);
for (ControllerRumbleState& rumble : m_controller_vibration_motors)
{
Controller* controller = m_system->GetController(rumble.controller_index);
if (!controller)
continue;
bool changed = false;
for (u32 i = 0; i < rumble.num_motors; i++)
{
const float strength = controller->GetVibrationMotorStrength(i);
changed |= (strength != rumble.last_strength[i]);
rumble.last_strength[i] = strength;
}
if (changed)
rumble.update_callback(rumble.last_strength.data(), rumble.num_motors);
}
}
void CommonHostInterface::StopControllerRumble()
{
for (ControllerRumbleState& rumble : m_controller_vibration_motors)
{
bool changed = true;
for (u32 i = 0; i < rumble.num_motors; i++)
{
changed |= (rumble.last_strength[i] != 0.0f);
rumble.last_strength[i] = 0.0f;
}
if (changed)
rumble.update_callback(rumble.last_strength.data(), rumble.num_motors);
}
}
static bool SplitBinding(const std::string& binding, std::string_view* device, std::string_view* sub_binding)
{
const std::string::size_type slash_pos = binding.find('/');
@ -421,6 +482,9 @@ static bool SplitBinding(const std::string& binding, std::string_view* device, s
void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si)
{
StopControllerRumble();
m_controller_vibration_motors.clear();
for (u32 controller_index = 0; controller_index < 2; controller_index++)
{
const ControllerType ctype = m_settings.controller_types[controller_index];
@ -477,6 +541,14 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si)
});
}
}
const u32 num_motors = Controller::GetVibrationMotorCount(ctype);
if (num_motors > 0)
{
const std::vector<std::string> bindings = si.GetStringList(category, TinyString::FromFormat("Rumble"));
for (const std::string& binding : bindings)
AddRumbleToInputMap(binding, controller_index, num_motors);
}
}
}
@ -600,6 +672,34 @@ bool CommonHostInterface::AddAxisToInputMap(const std::string& binding, const st
return false;
}
bool CommonHostInterface::AddRumbleToInputMap(const std::string& binding, u32 controller_index, u32 num_motors)
{
if (StringUtil::StartsWith(binding, "Controller"))
{
if (!m_controller_interface)
{
Log_ErrorPrintf("No controller interface set, cannot bind '%s'", binding.c_str());
return false;
}
const std::optional<int> host_controller_index = StringUtil::FromChars<int>(binding.substr(10));
if (!host_controller_index || *host_controller_index < 0)
{
Log_WarningPrintf("Invalid controller index in rumble binding '%s'", binding.c_str());
return false;
}
AddControllerRumble(controller_index, num_motors,
std::bind(&ControllerInterface::SetControllerRumbleStrength, m_controller_interface.get(),
host_controller_index.value(), std::placeholders::_1, std::placeholders::_2));
return true;
}
Log_WarningPrintf("Unknown input device in rumble binding '%s'", binding.c_str());
return false;
}
void CommonHostInterface::RegisterGeneralHotkeys()
{
RegisterHotkey(StaticString("General"), StaticString("FastForward"), StaticString("Toggle Fast Forward"),

View File

@ -22,6 +22,7 @@ public:
using InputButtonHandler = std::function<void(bool)>;
using InputAxisHandler = std::function<void(float)>;
using ControllerRumbleCallback = std::function<void(const float*, u32)>;
struct HotkeyInfo
{
@ -69,6 +70,7 @@ protected:
virtual void OnSystemCreated() override;
virtual void OnSystemPaused(bool paused) override;
virtual void OnSystemDestroyed() override;
virtual void OnControllerTypeChanged(u32 slot) override;
virtual void SetDefaultSettings(SettingsInterface& si) override;
@ -79,6 +81,7 @@ protected:
const std::string_view& button, InputButtonHandler handler);
virtual bool AddAxisToInputMap(const std::string& binding, const std::string_view& device,
const std::string_view& axis, InputAxisHandler handler);
virtual bool AddRumbleToInputMap(const std::string& binding, u32 controller_index, u32 num_motors);
/// Reloads the input map from config. Callable from controller interface.
virtual void UpdateInputMap() = 0;
@ -87,6 +90,10 @@ protected:
bool HandleHostKeyEvent(HostKeyCode code, bool pressed);
void UpdateInputMap(SettingsInterface& si);
void AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback);
void UpdateControllerRumble();
void StopControllerRumble();
std::unique_ptr<ControllerInterface> m_controller_interface;
private:
@ -101,6 +108,21 @@ private:
// input key maps
std::map<HostKeyCode, InputButtonHandler> m_keyboard_input_handlers;
// controller vibration motors/rumble
struct ControllerRumbleState
{
enum : u32
{
MAX_MOTORS = 2
};
u32 controller_index;
u32 num_motors;
std::array<float, MAX_MOTORS> last_strength;
ControllerRumbleCallback update_callback;
};
std::vector<ControllerRumbleState> m_controller_vibration_motors;
// running in batch mode? i.e. exit after stopping emulation
bool m_batch_mode = false;
};

View File

@ -90,4 +90,3 @@ bool ControllerInterface::BindControllerAxisToButton(int controller_index, int a
return false;
}
void ControllerInterface::UpdateControllerRumble() {}

View File

@ -36,9 +36,12 @@ public:
virtual bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) = 0;
virtual bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
ButtonCallback callback) = 0;
virtual void PollEvents() = 0;
virtual void UpdateControllerRumble() = 0;
// Changing rumble strength.
virtual u32 GetControllerRumbleMotorCount(int controller_index) = 0;
virtual void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) = 0;
// Input monitoring for external access.
struct Hook

View File

@ -161,12 +161,35 @@ bool SDLControllerInterface::OpenGameController(int index)
cd.controller = gcontroller;
cd.player_id = player_id;
cd.joystick_id = joystick_id;
cd.haptic_left_right_effect = -1;
SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) == 0)
cd.haptic = haptic;
else if (haptic)
SDL_HapticClose(haptic);
if (haptic)
{
SDL_HapticEffect ef = {};
ef.leftright.type = SDL_HAPTIC_LEFTRIGHT;
ef.leftright.length = 1000;
int ef_id = SDL_HapticNewEffect(haptic, &ef);
if (ef_id >= 0)
{
cd.haptic = haptic;
cd.haptic_left_right_effect = ef_id;
}
else
{
Log_ErrorPrintf("Failed to create haptic left/right effect: %s", SDL_GetError());
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0)
{
cd.haptic = haptic;
}
else
{
Log_ErrorPrintf("No haptic rumble supported: %s", SDL_GetError());
SDL_HapticClose(haptic);
}
}
}
if (cd.haptic)
Log_InfoPrintf("Rumble is supported on '%s'", SDL_GameControllerName(gcontroller));
@ -314,31 +337,52 @@ bool SDLControllerInterface::HandleControllerButtonEvent(const SDL_Event* ev)
return true;
}
void SDLControllerInterface::UpdateControllerRumble()
u32 SDLControllerInterface::GetControllerRumbleMotorCount(int controller_index)
{
for (auto& cd : m_controllers)
auto it = GetControllerDataForPlayerId(controller_index);
if (it == m_controllers.end())
return 0;
return (it->haptic_left_right_effect >= 0) ? 2 : (it->haptic ? 1 : 0);
}
void SDLControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors)
{
auto it = GetControllerDataForPlayerId(controller_index);
if (it == m_controllers.end())
return;
// we'll update before this duration is elapsed
static constexpr float MIN_STRENGTH = 0.01f;
static constexpr u32 DURATION = 100000;
SDL_Haptic* haptic = static_cast<SDL_Haptic*>(it->haptic);
if (it->haptic_left_right_effect >= 0 && num_motors > 1)
{
// TODO: FIXME proper binding
if (!cd.haptic || cd.player_id < 0 || cd.player_id >= 2)
continue;
float new_strength = 0.0f;
Controller* controller = GetController(cd.player_id);
if (controller)
if (strengths[0] >= MIN_STRENGTH || strengths[1] >= MIN_STRENGTH)
{
const u32 motor_count = controller->GetVibrationMotorCount();
for (u32 i = 0; i < motor_count; i++)
new_strength = std::max(new_strength, controller->GetVibrationMotorStrength(i));
SDL_HapticEffect ef;
ef.type = SDL_HAPTIC_LEFTRIGHT;
ef.leftright.large_magnitude = static_cast<u32>(strengths[0] * 65535.0f);
ef.leftright.small_magnitude = static_cast<u32>(strengths[1] * 65535.0f);
ef.leftright.length = DURATION;
SDL_HapticUpdateEffect(haptic, it->haptic_left_right_effect, &ef);
SDL_HapticRunEffect(haptic, it->haptic_left_right_effect, SDL_HAPTIC_INFINITY);
}
if (cd.last_rumble_strength == new_strength)
continue;
if (new_strength > 0.01f)
SDL_HapticRumblePlay(static_cast<SDL_Haptic*>(cd.haptic), new_strength, 100000);
else
SDL_HapticRumbleStop(static_cast<SDL_Haptic*>(cd.haptic));
{
SDL_HapticStopEffect(haptic, it->haptic_left_right_effect);
}
}
else
{
float max_strength = 0.0f;
for (u32 i = 0; i < num_motors; i++)
max_strength = std::max(max_strength, strengths[i]);
cd.last_rumble_strength = new_strength;
if (max_strength >= MIN_STRENGTH)
SDL_HapticRumblePlay(haptic, max_strength, DURATION);
else
SDL_HapticRumbleStop(haptic);
}
}

View File

@ -25,20 +25,22 @@ public:
bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override;
bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) override;
// Changing rumble strength.
u32 GetControllerRumbleMotorCount(int controller_index) override;
void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override;
void PollEvents() override;
bool ProcessSDLEvent(const SDL_Event* event);
void UpdateControllerRumble() override;
private:
struct ControllerData
{
void* controller;
void* haptic;
int haptic_left_right_effect;
int joystick_id;
int player_id;
float last_rumble_strength;
std::array<AxisCallback, MAX_NUM_AXISES> axis_mapping;
std::array<ButtonCallback, MAX_NUM_BUTTONS> button_mapping;