Merge pull request #7640 from jordan-woyak/input-fixes

ControllerInterface: Output/Rumble fixes
This commit is contained in:
JMC47 2019-01-05 17:16:35 -05:00 committed by GitHub
commit 0ca9accd8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 237 additions and 125 deletions

View File

@ -227,12 +227,10 @@ static void ResetRumble()
#if defined(__LIBUSB__) #if defined(__LIBUSB__)
GCAdapter::ResetRumble(); GCAdapter::ResetRumble();
#endif #endif
#if defined(CIFACE_USE_XINPUT) || defined(CIFACE_USE_DINPUT)
if (!Pad::IsInitialized()) if (!Pad::IsInitialized())
return; return;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
Pad::ResetRumble(i); Pad::ResetRumble(i);
#endif
} }
// Called from GUI thread // Called from GUI thread

View File

@ -12,6 +12,9 @@
namespace MappingCommon namespace MappingCommon
{ {
constexpr int INPUT_DETECT_TIME = 3000;
constexpr int OUTPUT_DETECT_TIME = 2000;
QString GetExpressionForControl(const QString& control_name, QString GetExpressionForControl(const QString& control_name,
const ciface::Core::DeviceQualifier& control_device, const ciface::Core::DeviceQualifier& control_device,
const ciface::Core::DeviceQualifier& default_device, Quote quote) const ciface::Core::DeviceQualifier& default_device, Quote quote)
@ -41,7 +44,9 @@ QString GetExpressionForControl(const QString& control_name,
QString DetectExpression(ControlReference* reference, ciface::Core::Device* device, QString DetectExpression(ControlReference* reference, ciface::Core::Device* device,
const ciface::Core::DeviceQualifier& default_device, Quote quote) 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) if (ctrl)
{ {

View File

@ -340,11 +340,7 @@ public:
} }
ControlState GetValue() const override { return GetActiveChild()->GetValue(); } ControlState GetValue() const override { return GetActiveChild()->GetValue(); }
void SetValue(ControlState value) override void SetValue(ControlState value) override { GetActiveChild()->SetValue(value); }
{
m_lhs->SetValue(GetActiveChild() == m_lhs ? value : 0.0);
m_rhs->SetValue(GetActiveChild() == m_rhs ? value : 0.0);
}
int CountNumControls() const override { return GetActiveChild()->CountNumControls(); } int CountNumControls() const override { return GetActiveChild()->CountNumControls(); }
operator std::string() const override operator std::string() const override
@ -549,5 +545,5 @@ std::pair<ParseStatus, std::unique_ptr<Expression>> ParseExpression(const std::s
std::move(complex_result.expr)); std::move(complex_result.expr));
return std::make_pair(complex_result.status, std::move(combined_expr)); return std::make_pair(complex_result.status, std::move(combined_expr));
} }
} } // namespace ExpressionParser
} } // namespace ciface

View File

@ -6,6 +6,7 @@
#include <map> #include <map>
#include <sstream> #include <sstream>
#include "Common/Logging/Log.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/DInput/DInput.h" #include "InputCommon/ControllerInterface/DInput/DInput.h"
#include "InputCommon/ControllerInterface/DInput/DInputJoystick.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), if (FAILED(js_device->SetCooperativeLevel(GetAncestor(hwnd, GA_ROOT),
DISCL_BACKGROUND | DISCL_EXCLUSIVE))) DISCL_BACKGROUND | DISCL_EXCLUSIVE)))
{ {
// PanicAlert("SetCooperativeLevel(DISCL_EXCLUSIVE) failed!"); WARN_LOG(
// fall back to non-exclusive mode, with no rumble PAD,
"DInput: Failed to acquire device exclusively. Force feedback will be unavailable.");
// Fall back to non-exclusive mode, with no rumble
if (FAILED( if (FAILED(
js_device->SetCooperativeLevel(nullptr, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) 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; std::list<DIDEVICEOBJECTINSTANCE> objects;
if (SUCCEEDED(m_device->EnumObjects(DIEnumDeviceObjectsCallback, (LPVOID)&objects, DIDFT_AXIS))) 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)); // Zero inputs:
// set hats to center m_state_in = {};
memset(m_state_in.rgdwPOV, 0xFF, sizeof(m_state_in.rgdwPOV)); // Set hats to center:
std::fill(std::begin(m_state_in.rgdwPOV), std::end(m_state_in.rgdwPOV), 0xFF);
} }
Joystick::~Joystick() Joystick::~Joystick()
{ {
DeInitForceFeedback();
m_device->Unacquire(); m_device->Unacquire();
m_device->Release(); 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); return (abs((int)(m_hat / 4500 - m_direction * 2 + 8) % 8 - 4) > 2);
} }
} } // namespace DInput
} } // namespace ciface

View File

@ -3,23 +3,33 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" #include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h"
#include <algorithm>
#include <array>
#include <string> #include <string>
#include "Common/Thread.h" #include "Common/Thread.h"
namespace ciface namespace ciface
{ {
namespace ForceFeedback namespace ForceFeedback
{ {
// template instantiation // 100Hz which homebrew docs very roughly imply is within WiiMote normal
template class ForceFeedbackDevice::Force<DICONSTANTFORCE>; // range, used for periodic haptic effects though often ignored by devices
template class ForceFeedbackDevice::Force<DIRAMPFORCE>; constexpr int RUMBLE_PERIOD = DI_SECONDS / 100;
template class ForceFeedbackDevice::Force<DIPERIODIC>; // 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 struct ForceType
{ {
GUID guid; GUID guid;
const std::string name; const char* name;
}; };
static const ForceType force_type_names[] = { static const ForceType force_type_names[] = {
@ -36,43 +46,79 @@ static const ForceType force_type_names[] = {
//{GUID_Friction, "Friction"}, //{GUID_Friction, "Friction"},
}; };
bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int cAxes) void ForceFeedbackDevice::DeInitForceFeedback()
{ {
if (cAxes == 0) 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 axis_count)
{
if (axis_count == 0)
return false; return false;
// TODO: check for DIDC_FORCEFEEDBACK in devcaps? // 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};
// temporary DIEFFECT eff{};
DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y}; eff.dwSize = sizeof(eff);
LONG rglDirection[2] = {-200, 0};
DIEFFECT eff;
memset(&eff, 0, sizeof(eff));
eff.dwSize = sizeof(DIEFFECT);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
eff.dwDuration = INFINITE; // (4 * DI_SECONDS) eff.dwDuration = RUMBLE_LENGTH_MAX;
eff.dwSamplePeriod = 0; eff.dwSamplePeriod = 0;
eff.dwGain = DI_FFNOMINALMAX; eff.dwGain = DI_FFNOMINALMAX;
eff.dwTriggerButton = DIEB_NOTRIGGER; eff.dwTriggerButton = DIEB_NOTRIGGER;
eff.dwTriggerRepeatInterval = 0; eff.dwTriggerRepeatInterval = 0;
eff.cAxes = std::min<DWORD>(1, cAxes); eff.cAxes = DWORD(rgdwAxes.size());
eff.rgdwAxes = rgdwAxes; eff.rgdwAxes = rgdwAxes.data();
eff.rglDirection = rglDirection; eff.rglDirection = rglDirection.data();
eff.dwStartDelay = 0;
// initialize parameters // Initialize parameters with zero force (their current state).
DICONSTANTFORCE diCF = {-10000}; DICONSTANTFORCE diCF{};
diCF.lMagnitude = DI_FFNOMINALMAX; diCF.lMagnitude = 0;
DIRAMPFORCE diRF = {0}; DIRAMPFORCE diRF{};
DIPERIODIC diPE = {0}; 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;
// doesn't seem needed for (auto& f : force_type_names)
// DIENVELOPE env;
// eff.lpEnvelope = &env;
// ZeroMemory(&env, sizeof(env));
// env.dwSize = sizeof(env);
for (const ForceType& f : force_type_names)
{ {
if (f.guid == GUID_ConstantForce) if (f.guid == GUID_ConstantForce)
{ {
@ -86,7 +132,7 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
} }
else else
{ {
// all other forces need periodic parameters // All other forces need periodic parameters:
eff.cbTypeSpecificParams = sizeof(DIPERIODIC); eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
eff.lpvTypeSpecificParams = &diPE; eff.lpvTypeSpecificParams = &diPE;
} }
@ -95,15 +141,15 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr))) if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr)))
{ {
if (f.guid == GUID_ConstantForce) 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) else if (f.guid == GUID_RampForce)
AddOutput(new ForceRamp(f.name, pEffect)); AddOutput(new ForceRamp(this, f.name, pEffect, diRF));
else else
AddOutput(new ForcePeriodic(f.name, pEffect)); AddOutput(new ForcePeriodic(this, f.name, pEffect, diPE));
} }
} }
// disable autocentering // Disable autocentering:
if (Outputs().size()) if (Outputs().size())
{ {
DIPROPDWORD dipdw; DIPROPDWORD dipdw;
@ -113,95 +159,113 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
dipdw.diph.dwHow = DIPH_DEVICE; dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = DIPROPAUTOCENTER_OFF; dipdw.dwData = DIPROPAUTOCENTER_OFF;
device->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph); device->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph);
m_run_thread.Set();
m_update_thread = std::thread(&ForceFeedbackDevice::ThreadFunc, this);
} }
return true; return true;
} }
template <typename P> template <typename P>
ForceFeedbackDevice::Force<P>::~Force() void ForceFeedbackDevice::TypedForce<P>::PlayEffect()
{ {
m_iface->Stop(); DIEFFECT eff{};
m_iface->Unload(); eff.dwSize = sizeof(eff);
m_iface->Release(); eff.cbTypeSpecificParams = sizeof(m_params);
eff.lpvTypeSpecificParams = &m_params;
m_effect->SetParameters(&eff, DIEP_START | DIEP_TYPESPECIFICPARAMS);
} }
template <typename P> template <typename P>
void ForceFeedbackDevice::Force<P>::Update() void ForceFeedbackDevice::TypedForce<P>::StopEffect()
{ {
DIEFFECT eff = {}; m_effect->Stop();
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);
}
template <typename P>
void ForceFeedbackDevice::Force<P>::Stop()
{
m_iface->Stop();
} }
template <> 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) m_params.lMagnitude = magnitude;
return;
params.lMagnitude = new_val; return old_magnitude != m_params.lMagnitude;
if (new_val)
Update();
else
Stop();
} }
template <> 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) // Having the same "start" and "end" here is a bit odd..
return; // 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; return old_magnitude != m_params.lStart;
if (new_val)
Update();
else
Stop();
} }
template <> 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) m_params.dwMagnitude = magnitude;
return;
params.dwMagnitude = new_val; return old_magnitude != m_params.dwMagnitude;
if (new_val)
Update();
else
Stop();
} }
template <typename P> template <typename P>
ForceFeedbackDevice::Force<P>::Force(const std::string& name, LPDIRECTINPUTEFFECT iface) ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, const char* name,
: m_name(name), m_iface(iface) LPDIRECTINPUTEFFECT effect, const P& params)
: Force(parent, name, effect), m_params(params)
{ {
memset(&params, 0, sizeof(params));
} }
template <typename P> 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; 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

View File

@ -4,9 +4,12 @@
#pragma once #pragma once
#include <list> #include <atomic>
#include <string> #include <string>
#include <thread>
#include "Common/Event.h"
#include "Common/Flag.h"
#include "InputCommon/ControllerInterface/Device.h" #include "InputCommon/ControllerInterface/Device.h"
#ifdef _WIN32 #ifdef _WIN32
@ -22,30 +25,64 @@ namespace ForceFeedback
{ {
class ForceFeedbackDevice : public Core::Device class ForceFeedbackDevice : public Core::Device
{ {
public:
bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int axis_count);
void DeInitForceFeedback();
private: private:
template <typename P> void ThreadFunc();
class Force : public Output class Force : public Output
{ {
public: public:
Force(const std::string& name, LPDIRECTINPUTEFFECT iface); Force(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect);
~Force();
void UpdateOutput();
void Release();
std::string GetName() const override;
void SetState(ControlState state) override; void SetState(ControlState state) override;
void Update(); std::string GetName() const override;
void Stop();
protected:
const LPDIRECTINPUTEFFECT m_effect;
private: private:
const std::string m_name; virtual void UpdateEffect(int magnitude) = 0;
LPDIRECTINPUTEFFECT m_iface;
P params;
};
typedef Force<DICONSTANTFORCE> ForceConstant;
typedef Force<DIRAMPFORCE> ForceRamp;
typedef Force<DIPERIODIC> ForcePeriodic;
public: ForceFeedbackDevice& m_parent;
bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes); const char* const m_name;
std::atomic<int> m_desired_magnitude;
};
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

View File

@ -99,6 +99,8 @@ Joystick::Joystick(IOHIDDeviceRef device, std::string name)
Joystick::~Joystick() Joystick::~Joystick()
{ {
DeInitForceFeedback();
if (m_ff_device) if (m_ff_device)
m_ff_device->Release(); m_ff_device->Release();
} }