mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 05:47:56 -07:00
ControllerInterface: DInput: Update force feedback effects in a thread. This should prevent slowdowns experienced by a handful of users.
This commit is contained in:
parent
d5df56c677
commit
0f19c4a40f
@ -12,7 +12,6 @@
|
||||
|
||||
namespace MappingCommon
|
||||
{
|
||||
|
||||
constexpr int INPUT_DETECT_TIME = 3000;
|
||||
constexpr int OUTPUT_DETECT_TIME = 2000;
|
||||
|
||||
|
@ -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
|
||||
|
@ -3,23 +3,25 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h"
|
||||
|
||||
#include <algorithm>
|
||||
#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>;
|
||||
// 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,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<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 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 <typename P>
|
||||
ForceFeedbackDevice::Force<P>::~Force()
|
||||
void ForceFeedbackDevice::TypedForce<P>::PlayEffect()
|
||||
{
|
||||
m_iface->Stop();
|
||||
m_iface->Unload();
|
||||
m_iface->Release();
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
void ForceFeedbackDevice::Force<P>::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 <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;
|
||||
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 <typename P>
|
||||
ForceFeedbackDevice::Force<P>::Force(const std::string& name, LPDIRECTINPUTEFFECT iface)
|
||||
: m_name(name), m_iface(iface)
|
||||
ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, std::string name,
|
||||
LPDIRECTINPUTEFFECT effect)
|
||||
: Force(parent, std::move(name), effect), m_params{}
|
||||
{
|
||||
memset(¶ms, 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 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
|
||||
|
@ -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,63 @@ namespace ForceFeedback
|
||||
{
|
||||
class ForceFeedbackDevice : public Core::Device
|
||||
{
|
||||
public:
|
||||
bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes);
|
||||
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 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<DICONSTANTFORCE> ForceConstant;
|
||||
typedef Force<DIRAMPFORCE> ForceRamp;
|
||||
typedef Force<DIPERIODIC> ForcePeriodic;
|
||||
virtual void UpdateEffect(int magnitude) = 0;
|
||||
|
||||
public:
|
||||
bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes);
|
||||
ForceFeedbackDevice& m_parent;
|
||||
const std::string m_name;
|
||||
std::atomic<int> m_desired_magnitude;
|
||||
};
|
||||
|
||||
template <typename P>
|
||||
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<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
|
||||
|
@ -99,6 +99,8 @@ Joystick::Joystick(IOHIDDeviceRef device, std::string name)
|
||||
|
||||
Joystick::~Joystick()
|
||||
{
|
||||
DeInitForceFeedback();
|
||||
|
||||
if (m_ff_device)
|
||||
m_ff_device->Release();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user