From 3a145aeae4496a9d6fdb68208777711ac9b9ff18 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 21 Dec 2018 20:00:27 -0600 Subject: [PATCH 1/5] ResetRumble on emu pause/stop regardless of presence of DInput/Xinput as that's not relevant. All the backends would like to stop rumbling. --- Source/Core/Core/Core.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 08a990542f..0d67e9d9e5 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -227,12 +227,10 @@ static void ResetRumble() #if defined(__LIBUSB__) GCAdapter::ResetRumble(); #endif -#if defined(CIFACE_USE_XINPUT) || defined(CIFACE_USE_DINPUT) if (!Pad::IsInitialized()) return; for (int i = 0; i < 4; ++i) Pad::ResetRumble(i); -#endif } // Called from GUI thread From a8d0afe219adbbd8394e85c6f9c31c0add12c299 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 22 Dec 2018 10:58:34 -0600 Subject: [PATCH 2/5] Reduce input mapping detection time to 3 seconds because 5 seconds was ridciculously long and output (rumble) testing time to 2 seconds because 5 seconds was annoyingly long. --- Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 9bbf1b17a1..adccc12ea5 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -12,6 +12,10 @@ namespace MappingCommon { + +constexpr int INPUT_DETECT_TIME = 3000; +constexpr int OUTPUT_DETECT_TIME = 2000; + QString GetExpressionForControl(const QString& control_name, const ciface::Core::DeviceQualifier& control_device, const ciface::Core::DeviceQualifier& default_device, Quote quote) @@ -41,7 +45,9 @@ QString GetExpressionForControl(const QString& control_name, QString DetectExpression(ControlReference* reference, ciface::Core::Device* device, const ciface::Core::DeviceQualifier& default_device, Quote quote) { - ciface::Core::Device::Control* const ctrl = reference->Detect(5000, device); + const int ms = reference->IsInput() ? INPUT_DETECT_TIME : OUTPUT_DETECT_TIME; + + ciface::Core::Device::Control* const ctrl = reference->Detect(ms, device); if (ctrl) { From d5df56c6771372c2a0282495cbf6ff913aaf3368 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 22 Dec 2018 11:17:05 -0600 Subject: [PATCH 3/5] ControllerInterface: Make CoalesceExpression not set the inactive child's value (rumble) to 0. This caused rumble to not enable when a control expression was both a valid "bareword" and "complex" expression. --- .../InputCommon/ControlReference/ExpressionParser.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index e7ab53d488..ba3f78170d 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -340,11 +340,7 @@ public: } ControlState GetValue() const override { return GetActiveChild()->GetValue(); } - void SetValue(ControlState value) override - { - m_lhs->SetValue(GetActiveChild() == m_lhs ? value : 0.0); - m_rhs->SetValue(GetActiveChild() == m_rhs ? value : 0.0); - } + void SetValue(ControlState value) override { GetActiveChild()->SetValue(value); } int CountNumControls() const override { return GetActiveChild()->CountNumControls(); } operator std::string() const override @@ -549,5 +545,5 @@ std::pair> ParseExpression(const std::s std::move(complex_result.expr)); return std::make_pair(complex_result.status, std::move(combined_expr)); } -} -} +} // namespace ExpressionParser +} // namespace ciface From 0f19c4a40f8b49247233ed1f36e3e7836b617014 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 22 Dec 2018 17:49:03 -0600 Subject: [PATCH 4/5] ControllerInterface: DInput: Update force feedback effects in a thread. This should prevent slowdowns experienced by a handful of users. --- .../Config/Mapping/MappingCommon.cpp | 1 - .../DInput/DInputJoystick.cpp | 6 +- .../ForceFeedback/ForceFeedbackDevice.cpp | 191 ++++++++++++------ .../ForceFeedback/ForceFeedbackDevice.h | 72 +++++-- .../ControllerInterface/OSX/OSXJoystick.mm | 2 + 5 files changed, 184 insertions(+), 88 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index adccc12ea5..4c1f230c4b 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -12,7 +12,6 @@ namespace MappingCommon { - constexpr int INPUT_DETECT_TIME = 3000; constexpr int OUTPUT_DETECT_TIME = 2000; diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp index 9968bc798f..6de1f97d7e 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp @@ -150,6 +150,8 @@ Joystick::Joystick(/*const LPCDIDEVICEINSTANCE lpddi, */ const LPDIRECTINPUTDEVI Joystick::~Joystick() { + DeInitForceFeedback(); + m_device->Unacquire(); m_device->Release(); } @@ -265,5 +267,5 @@ ControlState Joystick::Hat::GetState() const return (abs((int)(m_hat / 4500 - m_direction * 2 + 8) % 8 - 4) > 2); } -} -} +} // namespace DInput +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp index 34d0ed018b..3f8df41419 100644 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp @@ -3,23 +3,25 @@ // Refer to the license.txt file included. #include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" + #include #include + #include "Common/Thread.h" namespace ciface { namespace ForceFeedback { -// template instantiation -template class ForceFeedbackDevice::Force; -template class ForceFeedbackDevice::Force; -template class ForceFeedbackDevice::Force; +// Template instantiation: +template class ForceFeedbackDevice::TypedForce; +template class ForceFeedbackDevice::TypedForce; +template class ForceFeedbackDevice::TypedForce; struct ForceType { GUID guid; - const std::string name; + const char* name; }; static const ForceType force_type_names[] = { @@ -36,6 +38,42 @@ static const ForceType force_type_names[] = { //{GUID_Friction, "Friction"}, }; +void ForceFeedbackDevice::DeInitForceFeedback() +{ + if (!m_run_thread.TestAndClear()) + return; + + SignalUpdateThread(); + m_update_thread.join(); +} + +void ForceFeedbackDevice::ThreadFunc() +{ + Common::SetCurrentThreadName("ForceFeedback update thread"); + + while (m_run_thread.IsSet()) + { + m_update_event.Wait(); + + for (auto output : Outputs()) + { + auto& force = *static_cast(output); + force.UpdateOutput(); + } + } + + for (auto output : Outputs()) + { + auto& force = *static_cast(output); + force.Release(); + } +} + +void ForceFeedbackDevice::SignalUpdateThread() +{ + m_update_event.Set(); +} + bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int cAxes) { if (cAxes == 0) @@ -43,14 +81,14 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i // TODO: check for DIDC_FORCEFEEDBACK in devcaps? - // temporary + // Temporary for creating the effect: DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y}; LONG rglDirection[2] = {-200, 0}; - DIEFFECT eff; - memset(&eff, 0, sizeof(eff)); + DIEFFECT eff{}; eff.dwSize = sizeof(DIEFFECT); eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + // Infinite seems to work just fine: eff.dwDuration = INFINITE; // (4 * DI_SECONDS) eff.dwSamplePeriod = 0; eff.dwGain = DI_FFNOMINALMAX; @@ -60,19 +98,19 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i eff.rgdwAxes = rgdwAxes; eff.rglDirection = rglDirection; - // initialize parameters - DICONSTANTFORCE diCF = {-10000}; + // Initialize parameters. + DICONSTANTFORCE diCF{}; diCF.lMagnitude = DI_FFNOMINALMAX; - DIRAMPFORCE diRF = {0}; - DIPERIODIC diPE = {0}; + DIRAMPFORCE diRF{}; + DIPERIODIC diPE{}; - // doesn't seem needed + // This doesn't seem needed: // DIENVELOPE env; // eff.lpEnvelope = &env; // ZeroMemory(&env, sizeof(env)); // env.dwSize = sizeof(env); - for (const ForceType& f : force_type_names) + for (auto& f : force_type_names) { if (f.guid == GUID_ConstantForce) { @@ -86,7 +124,7 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i } else { - // all other forces need periodic parameters + // All other forces need periodic parameters: eff.cbTypeSpecificParams = sizeof(DIPERIODIC); eff.lpvTypeSpecificParams = &diPE; } @@ -95,15 +133,15 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr))) { if (f.guid == GUID_ConstantForce) - AddOutput(new ForceConstant(f.name, pEffect)); + AddOutput(new ForceConstant(this, f.name, pEffect)); else if (f.guid == GUID_RampForce) - AddOutput(new ForceRamp(f.name, pEffect)); + AddOutput(new ForceRamp(this, f.name, pEffect)); else - AddOutput(new ForcePeriodic(f.name, pEffect)); + AddOutput(new ForcePeriodic(this, f.name, pEffect)); } } - // disable autocentering + // Disable autocentering: if (Outputs().size()) { DIPROPDWORD dipdw; @@ -113,95 +151,114 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i dipdw.diph.dwHow = DIPH_DEVICE; dipdw.dwData = DIPROPAUTOCENTER_OFF; device->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph); + + m_run_thread.Set(); + m_update_thread = std::thread(&ForceFeedbackDevice::ThreadFunc, this); } return true; } template -ForceFeedbackDevice::Force

::~Force() +void ForceFeedbackDevice::TypedForce

::PlayEffect() { - m_iface->Stop(); - m_iface->Unload(); - m_iface->Release(); -} - -template -void ForceFeedbackDevice::Force

::Update() -{ - DIEFFECT eff = {}; + DIEFFECT eff{}; eff.dwSize = sizeof(DIEFFECT); eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; - eff.cbTypeSpecificParams = sizeof(P); - eff.lpvTypeSpecificParams = ¶ms; - - // set params and start effect - m_iface->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START); + eff.cbTypeSpecificParams = sizeof(m_params); + eff.lpvTypeSpecificParams = &m_params; + m_effect->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START); } template -void ForceFeedbackDevice::Force

::Stop() +void ForceFeedbackDevice::TypedForce

::StopEffect() { - m_iface->Stop(); + m_effect->Stop(); } template <> -void ForceFeedbackDevice::ForceConstant::SetState(const ControlState state) +bool ForceFeedbackDevice::ForceConstant::UpdateParameters(int magnitude) { - const LONG new_val = LONG(10000 * state); + const auto old_magnitude = m_params.lMagnitude; - if (params.lMagnitude == new_val) - return; + m_params.lMagnitude = magnitude; - params.lMagnitude = new_val; - if (new_val) - Update(); - else - Stop(); + return old_magnitude != m_params.lMagnitude; } template <> -void ForceFeedbackDevice::ForceRamp::SetState(const ControlState state) +bool ForceFeedbackDevice::ForceRamp::UpdateParameters(int magnitude) { - const LONG new_val = LONG(10000 * state); + const auto old_magnitude = m_params.lStart; - if (params.lStart == new_val) - return; + m_params.lStart = m_params.lEnd = magnitude; - params.lStart = params.lEnd = new_val; - if (new_val) - Update(); - else - Stop(); + return old_magnitude != m_params.lStart; } template <> -void ForceFeedbackDevice::ForcePeriodic::SetState(const ControlState state) +bool ForceFeedbackDevice::ForcePeriodic::UpdateParameters(int magnitude) { - const DWORD new_val = DWORD(10000 * state); + const auto old_magnitude = m_params.dwMagnitude; - if (params.dwMagnitude == new_val) - return; + m_params.dwMagnitude = magnitude; + // Zero is working fine for me: + // params.dwPeriod = 0;//DWORD(0.05 * DI_SECONDS); - params.dwMagnitude = new_val; - if (new_val) - Update(); - else - Stop(); + return old_magnitude != m_params.dwMagnitude; } template -ForceFeedbackDevice::Force

::Force(const std::string& name, LPDIRECTINPUTEFFECT iface) - : m_name(name), m_iface(iface) +ForceFeedbackDevice::TypedForce

::TypedForce(ForceFeedbackDevice* parent, std::string name, + LPDIRECTINPUTEFFECT effect) + : Force(parent, std::move(name), effect), m_params{} { - memset(¶ms, 0, sizeof(params)); } template -std::string ForceFeedbackDevice::Force

::GetName() const +void ForceFeedbackDevice::TypedForce

::UpdateEffect(int magnitude) +{ + if (UpdateParameters(magnitude)) + { + if (magnitude) + PlayEffect(); + else + StopEffect(); + } +} + +std::string ForceFeedbackDevice::Force::GetName() const { return m_name; } + +ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const std::string name, + LPDIRECTINPUTEFFECT effect) + : m_effect(effect), m_parent(*parent), m_name(std::move(name)), m_desired_magnitude() +{ } + +void ForceFeedbackDevice::Force::SetState(ControlState state) +{ + const auto new_val = int(DI_FFNOMINALMAX * state); + + if (m_desired_magnitude.exchange(new_val) != new_val) + m_parent.SignalUpdateThread(); } + +void ForceFeedbackDevice::Force::UpdateOutput() +{ + UpdateEffect(m_desired_magnitude); +} + +void ForceFeedbackDevice::Force::Release() +{ + // This isn't in the destructor because it should happen before the device is released. + m_effect->Stop(); + m_effect->Unload(); + m_effect->Release(); +} + +} // namespace ForceFeedback +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h index 5994bf2547..ca1ad6647a 100644 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h @@ -4,9 +4,12 @@ #pragma once -#include +#include #include +#include +#include "Common/Event.h" +#include "Common/Flag.h" #include "InputCommon/ControllerInterface/Device.h" #ifdef _WIN32 @@ -22,30 +25,63 @@ namespace ForceFeedback { class ForceFeedbackDevice : public Core::Device { +public: + bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes); + void DeInitForceFeedback(); + private: - template + void ThreadFunc(); + class Force : public Output { public: - Force(const std::string& name, LPDIRECTINPUTEFFECT iface); - ~Force(); + Force(ForceFeedbackDevice* parent, const std::string name, LPDIRECTINPUTEFFECT effect); + + void UpdateOutput(); + void Release(); - std::string GetName() const override; void SetState(ControlState state) override; - void Update(); - void Stop(); + std::string GetName() const override; + + protected: + const LPDIRECTINPUTEFFECT m_effect; private: - const std::string m_name; - LPDIRECTINPUTEFFECT m_iface; - P params; - }; - typedef Force ForceConstant; - typedef Force ForceRamp; - typedef Force ForcePeriodic; + virtual void UpdateEffect(int magnitude) = 0; -public: - bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes); + ForceFeedbackDevice& m_parent; + const std::string m_name; + std::atomic m_desired_magnitude; + }; + + template + class TypedForce : public Force + { + public: + TypedForce(ForceFeedbackDevice* parent, const std::string name, LPDIRECTINPUTEFFECT effect); + + private: + void UpdateEffect(int magnitude) override; + + // Returns true if parameters changed. + bool UpdateParameters(int magnitude); + + void PlayEffect(); + void StopEffect(); + + P m_params = {}; + }; + + void SignalUpdateThread(); + + typedef TypedForce ForceConstant; + typedef TypedForce ForceRamp; + typedef TypedForce ForcePeriodic; + + std::thread m_update_thread; + Common::Event m_update_event; + Common::Flag m_run_thread; }; -} -} + +} // namespace ForceFeedback +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm index ecbef721a5..bfe2f27505 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm @@ -99,6 +99,8 @@ Joystick::Joystick(IOHIDDeviceRef device, std::string name) Joystick::~Joystick() { + DeInitForceFeedback(); + if (m_ff_device) m_ff_device->Release(); } From a995e2f5ba3bb55b4245675428bbdc3d34cb7355 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 29 Dec 2018 09:10:48 -0600 Subject: [PATCH 5/5] ControllerInterface: Set DInput FF effect parameters sanely. This fixes a crash with periodic effects and my GCPad adapter (probably a divide by zero behind the scenes). --- .../DInput/DInputJoystick.cpp | 22 ++++-- .../ForceFeedback/ForceFeedbackDevice.cpp | 79 ++++++++++--------- .../ForceFeedback/ForceFeedbackDevice.h | 9 ++- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp index 6de1f97d7e..a01ae6c75a 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp @@ -6,6 +6,7 @@ #include #include +#include "Common/Logging/Log.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/DInput/DInput.h" #include "InputCommon/ControllerInterface/DInput/DInputJoystick.h" @@ -40,8 +41,10 @@ void InitJoystick(IDirectInput8* const idi8, HWND hwnd) if (FAILED(js_device->SetCooperativeLevel(GetAncestor(hwnd, GA_ROOT), DISCL_BACKGROUND | DISCL_EXCLUSIVE))) { - // PanicAlert("SetCooperativeLevel(DISCL_EXCLUSIVE) failed!"); - // fall back to non-exclusive mode, with no rumble + WARN_LOG( + PAD, + "DInput: Failed to acquire device exclusively. Force feedback will be unavailable."); + // Fall back to non-exclusive mode, with no rumble if (FAILED( js_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) { @@ -136,16 +139,21 @@ Joystick::Joystick(/*const LPCDIDEVICEINSTANCE lpddi, */ const LPDIRECTINPUTDEVI } } - // force feedback + // Force feedback: std::list objects; if (SUCCEEDED(m_device->EnumObjects(DIEnumDeviceObjectsCallback, (LPVOID)&objects, DIDFT_AXIS))) { - InitForceFeedback(m_device, (int)objects.size()); + const int num_ff_axes = + std::count_if(std::begin(objects), std::end(objects), [](DIDEVICEOBJECTINSTANCE& pdidoi) { + return pdidoi.dwFlags && DIDOI_FFACTUATOR; + }); + InitForceFeedback(m_device, num_ff_axes); } - ZeroMemory(&m_state_in, sizeof(m_state_in)); - // set hats to center - memset(m_state_in.rgdwPOV, 0xFF, sizeof(m_state_in.rgdwPOV)); + // Zero inputs: + m_state_in = {}; + // Set hats to center: + std::fill(std::begin(m_state_in.rgdwPOV), std::end(m_state_in.rgdwPOV), 0xFF); } Joystick::~Joystick() diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp index 3f8df41419..26112015af 100644 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp @@ -4,7 +4,7 @@ #include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" -#include +#include #include #include "Common/Thread.h" @@ -13,6 +13,14 @@ namespace ciface { namespace ForceFeedback { +// 100Hz which homebrew docs very roughly imply is within WiiMote normal +// range, used for periodic haptic effects though often ignored by devices +constexpr int RUMBLE_PERIOD = DI_SECONDS / 100; +// This needs to be at least as long as the longest rumble that might ever be played. +// Too short and it's going to stop in the middle of a long effect. +// "INFINITE" is invalid for ramp effects and probably not sensible. +constexpr int RUMBLE_LENGTH_MAX = DI_SECONDS * 10; + // Template instantiation: template class ForceFeedbackDevice::TypedForce; template class ForceFeedbackDevice::TypedForce; @@ -74,41 +82,41 @@ void ForceFeedbackDevice::SignalUpdateThread() m_update_event.Set(); } -bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int cAxes) +bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int axis_count) { - if (cAxes == 0) + if (axis_count == 0) return false; - // TODO: check for DIDC_FORCEFEEDBACK in devcaps? - - // Temporary for creating the effect: - DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y}; - LONG rglDirection[2] = {-200, 0}; + // We just use the X axis (for wheel left/right). + // Gamepads seem to not care which axis you use. + // These are temporary for creating the effect: + std::array rgdwAxes = {DIJOFS_X}; + std::array rglDirection = {-200}; DIEFFECT eff{}; - eff.dwSize = sizeof(DIEFFECT); + eff.dwSize = sizeof(eff); eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; - // Infinite seems to work just fine: - eff.dwDuration = INFINITE; // (4 * DI_SECONDS) + eff.dwDuration = RUMBLE_LENGTH_MAX; eff.dwSamplePeriod = 0; eff.dwGain = DI_FFNOMINALMAX; eff.dwTriggerButton = DIEB_NOTRIGGER; eff.dwTriggerRepeatInterval = 0; - eff.cAxes = std::min(1, cAxes); - eff.rgdwAxes = rgdwAxes; - eff.rglDirection = rglDirection; + eff.cAxes = DWORD(rgdwAxes.size()); + eff.rgdwAxes = rgdwAxes.data(); + eff.rglDirection = rglDirection.data(); + eff.dwStartDelay = 0; - // Initialize parameters. + // Initialize parameters with zero force (their current state). DICONSTANTFORCE diCF{}; - diCF.lMagnitude = DI_FFNOMINALMAX; + diCF.lMagnitude = 0; DIRAMPFORCE diRF{}; + diRF.lStart = diRF.lEnd = 0; DIPERIODIC diPE{}; - - // This doesn't seem needed: - // DIENVELOPE env; - // eff.lpEnvelope = &env; - // ZeroMemory(&env, sizeof(env)); - // env.dwSize = sizeof(env); + diPE.dwMagnitude = 0; + // Is it sensible to have a zero-offset? + diPE.lOffset = 0; + diPE.dwPhase = 0; + diPE.dwPeriod = RUMBLE_PERIOD; for (auto& f : force_type_names) { @@ -133,11 +141,11 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr))) { if (f.guid == GUID_ConstantForce) - AddOutput(new ForceConstant(this, f.name, pEffect)); + AddOutput(new ForceConstant(this, f.name, pEffect, diCF)); else if (f.guid == GUID_RampForce) - AddOutput(new ForceRamp(this, f.name, pEffect)); + AddOutput(new ForceRamp(this, f.name, pEffect, diRF)); else - AddOutput(new ForcePeriodic(this, f.name, pEffect)); + AddOutput(new ForcePeriodic(this, f.name, pEffect, diPE)); } } @@ -163,12 +171,11 @@ template void ForceFeedbackDevice::TypedForce

::PlayEffect() { DIEFFECT eff{}; - eff.dwSize = sizeof(DIEFFECT); - eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; - + eff.dwSize = sizeof(eff); eff.cbTypeSpecificParams = sizeof(m_params); eff.lpvTypeSpecificParams = &m_params; - m_effect->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START); + + m_effect->SetParameters(&eff, DIEP_START | DIEP_TYPESPECIFICPARAMS); } template @@ -192,6 +199,8 @@ bool ForceFeedbackDevice::ForceRamp::UpdateParameters(int magnitude) { const auto old_magnitude = m_params.lStart; + // Having the same "start" and "end" here is a bit odd.. + // But ramp forces don't really make sense for our rumble effects anyways.. m_params.lStart = m_params.lEnd = magnitude; return old_magnitude != m_params.lStart; @@ -203,16 +212,14 @@ bool ForceFeedbackDevice::ForcePeriodic::UpdateParameters(int magnitude) const auto old_magnitude = m_params.dwMagnitude; m_params.dwMagnitude = magnitude; - // Zero is working fine for me: - // params.dwPeriod = 0;//DWORD(0.05 * DI_SECONDS); return old_magnitude != m_params.dwMagnitude; } template -ForceFeedbackDevice::TypedForce

::TypedForce(ForceFeedbackDevice* parent, std::string name, - LPDIRECTINPUTEFFECT effect) - : Force(parent, std::move(name), effect), m_params{} +ForceFeedbackDevice::TypedForce

::TypedForce(ForceFeedbackDevice* parent, const char* name, + LPDIRECTINPUTEFFECT effect, const P& params) + : Force(parent, name, effect), m_params(params) { } @@ -233,9 +240,9 @@ std::string ForceFeedbackDevice::Force::GetName() const return m_name; } -ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const std::string name, +ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect) - : m_effect(effect), m_parent(*parent), m_name(std::move(name)), m_desired_magnitude() + : m_effect(effect), m_parent(*parent), m_name(name), m_desired_magnitude() { } diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h index ca1ad6647a..8f5814106e 100644 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h @@ -26,7 +26,7 @@ namespace ForceFeedback class ForceFeedbackDevice : public Core::Device { public: - bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes); + bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int axis_count); void DeInitForceFeedback(); private: @@ -35,7 +35,7 @@ private: class Force : public Output { public: - Force(ForceFeedbackDevice* parent, const std::string name, LPDIRECTINPUTEFFECT effect); + Force(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect); void UpdateOutput(); void Release(); @@ -50,7 +50,7 @@ private: virtual void UpdateEffect(int magnitude) = 0; ForceFeedbackDevice& m_parent; - const std::string m_name; + const char* const m_name; std::atomic m_desired_magnitude; }; @@ -58,7 +58,8 @@ private: class TypedForce : public Force { public: - TypedForce(ForceFeedbackDevice* parent, const std::string name, LPDIRECTINPUTEFFECT effect); + TypedForce(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect, + const P& params); private: void UpdateEffect(int magnitude) override;