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).

This commit is contained in:
Jordan Woyak 2018-12-29 09:10:48 -06:00
parent 0f19c4a40f
commit a995e2f5ba
3 changed files with 63 additions and 47 deletions

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,16 +139,21 @@ 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()

View File

@ -4,7 +4,7 @@
#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"
@ -13,6 +13,14 @@ namespace ciface
{ {
namespace ForceFeedback 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 instantiation:
template class ForceFeedbackDevice::TypedForce<DICONSTANTFORCE>; template class ForceFeedbackDevice::TypedForce<DICONSTANTFORCE>;
template class ForceFeedbackDevice::TypedForce<DIRAMPFORCE>; template class ForceFeedbackDevice::TypedForce<DIRAMPFORCE>;
@ -74,41 +82,41 @@ void ForceFeedbackDevice::SignalUpdateThread()
m_update_event.Set(); 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; 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.
// Temporary for creating the effect: // These are temporary for creating the effect:
DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y}; std::array<DWORD, 1> rgdwAxes = {DIJOFS_X};
LONG rglDirection[2] = {-200, 0}; std::array<LONG, 1> rglDirection = {-200};
DIEFFECT eff{}; DIEFFECT eff{};
eff.dwSize = sizeof(DIEFFECT); eff.dwSize = sizeof(eff);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
// Infinite seems to work just fine: eff.dwDuration = RUMBLE_LENGTH_MAX;
eff.dwDuration = INFINITE; // (4 * DI_SECONDS)
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{}; DICONSTANTFORCE diCF{};
diCF.lMagnitude = DI_FFNOMINALMAX; diCF.lMagnitude = 0;
DIRAMPFORCE diRF{}; DIRAMPFORCE diRF{};
diRF.lStart = diRF.lEnd = 0;
DIPERIODIC diPE{}; DIPERIODIC diPE{};
diPE.dwMagnitude = 0;
// This doesn't seem needed: // Is it sensible to have a zero-offset?
// DIENVELOPE env; diPE.lOffset = 0;
// eff.lpEnvelope = &env; diPE.dwPhase = 0;
// ZeroMemory(&env, sizeof(env)); diPE.dwPeriod = RUMBLE_PERIOD;
// env.dwSize = sizeof(env);
for (auto& f : force_type_names) 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 (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr)))
{ {
if (f.guid == GUID_ConstantForce) 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) else if (f.guid == GUID_RampForce)
AddOutput(new ForceRamp(this, f.name, pEffect)); AddOutput(new ForceRamp(this, f.name, pEffect, diRF));
else else
AddOutput(new ForcePeriodic(this, f.name, pEffect)); AddOutput(new ForcePeriodic(this, f.name, pEffect, diPE));
} }
} }
@ -163,12 +171,11 @@ template <typename P>
void ForceFeedbackDevice::TypedForce<P>::PlayEffect() void ForceFeedbackDevice::TypedForce<P>::PlayEffect()
{ {
DIEFFECT eff{}; DIEFFECT eff{};
eff.dwSize = sizeof(DIEFFECT); eff.dwSize = sizeof(eff);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
eff.cbTypeSpecificParams = sizeof(m_params); eff.cbTypeSpecificParams = sizeof(m_params);
eff.lpvTypeSpecificParams = &m_params; eff.lpvTypeSpecificParams = &m_params;
m_effect->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START);
m_effect->SetParameters(&eff, DIEP_START | DIEP_TYPESPECIFICPARAMS);
} }
template <typename P> template <typename P>
@ -192,6 +199,8 @@ bool ForceFeedbackDevice::ForceRamp::UpdateParameters(int magnitude)
{ {
const auto old_magnitude = m_params.lStart; 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; m_params.lStart = m_params.lEnd = magnitude;
return old_magnitude != m_params.lStart; return old_magnitude != m_params.lStart;
@ -203,16 +212,14 @@ bool ForceFeedbackDevice::ForcePeriodic::UpdateParameters(int magnitude)
const auto old_magnitude = m_params.dwMagnitude; const auto old_magnitude = m_params.dwMagnitude;
m_params.dwMagnitude = magnitude; m_params.dwMagnitude = magnitude;
// Zero is working fine for me:
// params.dwPeriod = 0;//DWORD(0.05 * DI_SECONDS);
return old_magnitude != m_params.dwMagnitude; return old_magnitude != m_params.dwMagnitude;
} }
template <typename P> template <typename P>
ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, std::string name, ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, const char* name,
LPDIRECTINPUTEFFECT effect) LPDIRECTINPUTEFFECT effect, const P& params)
: Force(parent, std::move(name), effect), m_params{} : Force(parent, name, effect), m_params(params)
{ {
} }
@ -233,9 +240,9 @@ std::string ForceFeedbackDevice::Force::GetName() const
return m_name; return m_name;
} }
ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const std::string name, ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const char* name,
LPDIRECTINPUTEFFECT effect) 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()
{ {
} }

View File

@ -26,7 +26,7 @@ namespace ForceFeedback
class ForceFeedbackDevice : public Core::Device class ForceFeedbackDevice : public Core::Device
{ {
public: public:
bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int cAxes); bool InitForceFeedback(const LPDIRECTINPUTDEVICE8, int axis_count);
void DeInitForceFeedback(); void DeInitForceFeedback();
private: private:
@ -35,7 +35,7 @@ private:
class Force : public Output class Force : public Output
{ {
public: public:
Force(ForceFeedbackDevice* parent, const std::string name, LPDIRECTINPUTEFFECT effect); Force(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect);
void UpdateOutput(); void UpdateOutput();
void Release(); void Release();
@ -50,7 +50,7 @@ private:
virtual void UpdateEffect(int magnitude) = 0; virtual void UpdateEffect(int magnitude) = 0;
ForceFeedbackDevice& m_parent; ForceFeedbackDevice& m_parent;
const std::string m_name; const char* const m_name;
std::atomic<int> m_desired_magnitude; std::atomic<int> m_desired_magnitude;
}; };
@ -58,7 +58,8 @@ private:
class TypedForce : public Force class TypedForce : public Force
{ {
public: public:
TypedForce(ForceFeedbackDevice* parent, const std::string name, LPDIRECTINPUTEFFECT effect); TypedForce(ForceFeedbackDevice* parent, const char* name, LPDIRECTINPUTEFFECT effect,
const P& params);
private: private:
void UpdateEffect(int magnitude) override; void UpdateEffect(int magnitude) override;