summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerInterface
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2019-01-05 17:16:35 -0500
committerGitHub <noreply@github.com>2019-01-05 17:16:35 -0500
commit0ca9accd8b9f92a2f6e4fa600136a9e51f543b5f (patch)
treeeb2f8eb6eeb0c05f52515578704ba809a08442d0 /Source/Core/InputCommon/ControllerInterface
parentd75e9b2d5d6e8075edad15da00cb10e32996890f (diff)
parenta995e2f5ba3bb55b4245675428bbdc3d34cb7355 (diff)
Merge pull request #7640 from jordan-woyak/input-fixes
ControllerInterface: Output/Rumble fixes
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface')
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp28
-rw-r--r--Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp242
-rw-r--r--Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h71
-rw-r--r--Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm2
4 files changed, 228 insertions, 115 deletions
diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp
index 9968bc798f..a01ae6c75a 100644
--- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp
+++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp
@@ -6,6 +6,7 @@
#include <map>
#include <sstream>
+#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,20 +139,27 @@ Joystick::Joystick(/*const LPCDIDEVICEINSTANCE lpddi, */ const LPDIRECTINPUTDEVI
}
}
- // force feedback
+ // Force feedback:
std::list<DIDEVICEOBJECTINSTANCE> 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()
{
+ DeInitForceFeedback();
+
m_device->Unacquire();
m_device->Release();
}
@@ -265,5 +275,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..26112015af 100644
--- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp
+++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp
@@ -3,23 +3,33 @@
// Refer to the license.txt file included.
#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h"
-#include <algorithm>
+
+#include <array>
#include <string>
+
#include "Common/Thread.h"
namespace ciface
{
namespace ForceFeedback
{
-// template instantiation
-template class ForceFeedbackDevice::Force<DICONSTANTFORCE>;
-template class ForceFeedbackDevice::Force<DIRAMPFORCE>;
-template class ForceFeedbackDevice::Force<DIPERIODIC>;
+// 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<DICONSTANTFORCE>;
+template class ForceFeedbackDevice::TypedForce<DIRAMPFORCE>;
+template class ForceFeedbackDevice::TypedForce<DIPERIODIC>;
struct ForceType
{
GUID guid;
- const std::string name;
+ const char* name;
};
static const ForceType force_type_names[] = {
@@ -36,43 +46,79 @@ static const ForceType force_type_names[] = {
//{GUID_Friction, "Friction"},
};
-bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int cAxes)
+void ForceFeedbackDevice::DeInitForceFeedback()
{
- if (cAxes == 0)
- return false;
+ if (!m_run_thread.TestAndClear())
+ return;
+
+ SignalUpdateThread();
+ m_update_thread.join();
+}
- // TODO: check for DIDC_FORCEFEEDBACK in devcaps?
+void ForceFeedbackDevice::ThreadFunc()
+{
+ Common::SetCurrentThreadName("ForceFeedback update thread");
- // temporary
- DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y};
- LONG rglDirection[2] = {-200, 0};
+ while (m_run_thread.IsSet())
+ {
+ m_update_event.Wait();
- DIEFFECT eff;
- memset(&eff, 0, sizeof(eff));
- eff.dwSize = sizeof(DIEFFECT);
+ for (auto output : Outputs())
+ {
+ auto& force = *static_cast<Force*>(output);
+ force.UpdateOutput();
+ }
+ }
+
+ for (auto output : Outputs())
+ {
+ auto& force = *static_cast<Force*>(output);
+ force.Release();
+ }
+}
+
+void ForceFeedbackDevice::SignalUpdateThread()
+{
+ m_update_event.Set();
+}
+
+bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int axis_count)
+{
+ if (axis_count == 0)
+ return false;
+
+ // 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<DWORD, 1> rgdwAxes = {DIJOFS_X};
+ std::array<LONG, 1> rglDirection = {-200};
+
+ DIEFFECT eff{};
+ eff.dwSize = sizeof(eff);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
- 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<DWORD>(1, cAxes);
- eff.rgdwAxes = rgdwAxes;
- eff.rglDirection = rglDirection;
-
- // initialize parameters
- DICONSTANTFORCE diCF = {-10000};
- diCF.lMagnitude = DI_FFNOMINALMAX;
- DIRAMPFORCE diRF = {0};
- DIPERIODIC diPE = {0};
-
- // doesn't seem needed
- // DIENVELOPE env;
- // eff.lpEnvelope = &env;
- // ZeroMemory(&env, sizeof(env));
- // env.dwSize = sizeof(env);
-
- for (const ForceType& f : force_type_names)
+ eff.cAxes = DWORD(rgdwAxes.size());
+ eff.rgdwAxes = rgdwAxes.data();
+ eff.rglDirection = rglDirection.data();
+ eff.dwStartDelay = 0;
+
+ // Initialize parameters with zero force (their current state).
+ DICONSTANTFORCE diCF{};
+ diCF.lMagnitude = 0;
+ DIRAMPFORCE diRF{};
+ diRF.lStart = diRF.lEnd = 0;
+ DIPERIODIC diPE{};
+ 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)
{
if (f.guid == GUID_ConstantForce)
{
@@ -86,7 +132,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 +141,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, diCF));
else if (f.guid == GUID_RampForce)
- AddOutput(new ForceRamp(f.name, pEffect));
+ AddOutput(new ForceRamp(this, f.name, pEffect, diRF));
else
- AddOutput(new ForcePeriodic(f.name, pEffect));
+ AddOutput(new ForcePeriodic(this, f.name, pEffect, diPE));
}
}
- // disable autocentering
+ // Disable autocentering:
if (Outputs().size())
{
DIPROPDWORD dipdw;
@@ -113,95 +159,113 @@ 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 <typename P>
-ForceFeedbackDevice::Force<P>::~Force()
+void ForceFeedbackDevice::TypedForce<P>::PlayEffect()
{
- m_iface->Stop();
- m_iface->Unload();
- m_iface->Release();
-}
+ DIEFFECT eff{};
+ eff.dwSize = sizeof(eff);
+ eff.cbTypeSpecificParams = sizeof(m_params);
+ eff.lpvTypeSpecificParams = &m_params;
-template <typename P>
-void ForceFeedbackDevice::Force<P>::Update()
-{
- DIEFFECT eff = {};
- eff.dwSize = sizeof(DIEFFECT);
- eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
-
- eff.cbTypeSpecificParams = sizeof(P);
- eff.lpvTypeSpecificParams = &params;
-
- // set params and start effect
- m_iface->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START);
+ m_effect->SetParameters(&eff, DIEP_START | DIEP_TYPESPECIFICPARAMS);
}
template <typename P>
-void ForceFeedbackDevice::Force<P>::Stop()
+void ForceFeedbackDevice::TypedForce<P>::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;
+ // 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;
- 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;
- params.dwMagnitude = new_val;
- if (new_val)
- Update();
- else
- Stop();
+ return old_magnitude != m_params.dwMagnitude;
}
template <typename P>
-ForceFeedbackDevice::Force<P>::Force(const std::string& name, LPDIRECTINPUTEFFECT iface)
- : m_name(name), m_iface(iface)
+ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, const char* name,
+ LPDIRECTINPUTEFFECT effect, const P& params)
+ : Force(parent, name, effect), m_params(params)
{
- memset(&params, 0, sizeof(params));
}
template <typename P>
-std::string ForceFeedbackDevice::Force<P>::GetName() const
+void ForceFeedbackDevice::TypedForce<P>::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 char* name,
+ LPDIRECTINPUTEFFECT effect)
+ : m_effect(effect), m_parent(*parent), m_name(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..8f5814106e 100644
--- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h
+++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h
@@ -4,9 +4,12 @@
#pragma once
-#include <list>
+#include <atomic>
#include <string>
+#include <thread>
+#include "Common/Event.h"
+#include "Common/Flag.h"
#include "InputCommon/ControllerInterface/Device.h"
#ifdef _WIN32
@@ -22,30 +25,64 @@ namespace ForceFeedback
{
class ForceFeedbackDevice : public Core::Device
{
+public:
+ bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int axis_count);
+ void DeInitForceFeedback();
+
private:
- template <typename P>
+ void ThreadFunc();
+
class Force : public Output
{
public:
- Force(const std::string& name, LPDIRECTINPUTEFFECT iface);
- ~Force();
+ Force(ForceFeedbackDevice* parent, const char* 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;
+ virtual void UpdateEffect(int magnitude) = 0;
+
+ ForceFeedbackDevice& m_parent;
+ const char* const m_name;
+ std::atomic<int> m_desired_magnitude;
};
- typedef Force<DICONSTANTFORCE> ForceConstant;
- typedef Force<DIRAMPFORCE> ForceRamp;
- typedef Force<DIPERIODIC> ForcePeriodic;
-public:
- bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes);
+ template <typename P>
+ class TypedForce : public Force
+ {
+ public:
+ TypedForce(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect,
+ const P& params);
+
+ 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<DICONSTANTFORCE> ForceConstant;
+ typedef TypedForce<DIRAMPFORCE> ForceRamp;
+ typedef TypedForce<DIPERIODIC> 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();
}