Skip to content

Commit 618eb57

Browse files
authored
DirectInput FFB: Calculate appropriate update flags (#14570)
Anecdotally, some force-feedback wheels have been reported to experience a reduced "definition", "texture", "precision", or "je ne sais quoi", which appears to be caused by sending more update flags than necessary to DirectInput. This may be related to the fact that there are two USB PID packets that are sent when updating a device: One contains the "general" force data, and the other contains the "type-specific" data. My speculation is that many wheels expect to only receive the latter, and misbehave when receiving both. This has been tested and validated anecdotally by others who have received a hacked-together version of PCSX2 that corrects the flags sent to DirectInput, who noted a significant improvement in the "feeling" of the FFB effects. The only way to validate this at a technical level is to grab a wheel that uses the "generic" DirectInput FFB drivers (which map nearly 1:1 with the USB PID specification), and inspect the USB packets (e.g. with USBPcap) to check whether redundant data is being sent.
1 parent 128b926 commit 618eb57

File tree

1 file changed

+98
-8
lines changed

1 file changed

+98
-8
lines changed

src/haptic/windows/SDL_dinputhaptic.c

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,103 @@ bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect
946946
return false;
947947
}
948948

949+
BOOL DIGetDirectionUpdateFlag(DIEFFECT *before, DIEFFECT *after)
950+
{
951+
if (before->cAxes != after->cAxes) {
952+
return true;
953+
}
954+
// rglDirection must be non-null for DIEP_DIRECTION to be a valid flag
955+
if (after->rglDirection == NULL) {
956+
return false;
957+
}
958+
if (before->rglDirection == NULL) {
959+
return true;
960+
}
961+
return SDL_memcmp(before->rglDirection, after->rglDirection, sizeof(LONG) * before->cAxes) != 0;
962+
}
963+
964+
BOOL DIGetEnvelopeUpdateFlag(DIEFFECT* before, DIEFFECT* after)
965+
{
966+
if (before->lpEnvelope == NULL && after->lpEnvelope == NULL) {
967+
return false;
968+
}
969+
// A null lpEnvelope is valid for DIEP_ENVELOPE (clears the envelope from the effect)
970+
if (before->lpEnvelope == NULL || after->lpEnvelope == NULL) {
971+
return true;
972+
}
973+
return SDL_memcmp(before->lpEnvelope, after->lpEnvelope, sizeof(DIENVELOPE)) != 0;
974+
}
975+
976+
BOOL DIGetTypeSpecificParamsUpdateFlag(DIEFFECT *before, DIEFFECT *after)
977+
{
978+
// Shouldn't happen since this implies an effect's type somehow changed, but need to check to avoid an out-of-bounds memcmp
979+
if (before->cbTypeSpecificParams != after->cbTypeSpecificParams) {
980+
return true;
981+
}
982+
// lpvTypeSpecificParams must be non-null for the DIEP_TYPESPECIFICPARAMS flag.
983+
if (after->lpvTypeSpecificParams == NULL) {
984+
return false;
985+
}
986+
if (before->lpvTypeSpecificParams == NULL) {
987+
return true;
988+
}
989+
return SDL_memcmp(before->lpvTypeSpecificParams, after->lpvTypeSpecificParams, before->cbTypeSpecificParams) != 0;
990+
}
991+
992+
/*
993+
Calculate the exact flags needed when updating an existing DirectInput haptic effect.
994+
*/
995+
DWORD DICalculateUpdateFlags(DIEFFECT *before, DIEFFECT *after)
996+
{
997+
DWORD flags = 0;
998+
999+
if (DIGetDirectionUpdateFlag(before, after)) {
1000+
flags |= DIEP_DIRECTION;
1001+
}
1002+
1003+
if (before->dwDuration != after->dwDuration) {
1004+
flags |= DIEP_DURATION;
1005+
}
1006+
1007+
if (DIGetEnvelopeUpdateFlag(before, after)) {
1008+
flags |= DIEP_ENVELOPE;
1009+
}
1010+
1011+
if (before->dwStartDelay != after->dwStartDelay) {
1012+
flags |= DIEP_STARTDELAY;
1013+
}
1014+
1015+
if (before->dwTriggerButton != after->dwTriggerButton) {
1016+
flags |= DIEP_TRIGGERBUTTON;
1017+
}
1018+
1019+
if (before->dwTriggerRepeatInterval != after->dwTriggerRepeatInterval) {
1020+
flags |= DIEP_TRIGGERREPEATINTERVAL;
1021+
}
1022+
1023+
if (DIGetTypeSpecificParamsUpdateFlag(before, after)) {
1024+
flags |= DIEP_TYPESPECIFICPARAMS;
1025+
}
1026+
1027+
if (flags == 0) {
1028+
/* Awkward: SDL_UpdateHapticEffect was called, but nothing was changed.
1029+
* Calling IDirectInputEffect_SetParameters with no flags is nonsense,
1030+
* so our options are to either send all the flags, or exit early.
1031+
* Sending all the flags seems like the safer option: The programmer may be trying
1032+
* to force an update for some reason (e.g. driver bug workaround?). Conversely,
1033+
* if the programmer doesn't want IDirectInputEffect_SetParameters to be called, they
1034+
* can just avoid calling SDL_UpdateHapticEffect when there's no changes. */
1035+
flags = DIEP_DIRECTION |
1036+
DIEP_DURATION |
1037+
DIEP_ENVELOPE |
1038+
DIEP_STARTDELAY |
1039+
DIEP_TRIGGERBUTTON |
1040+
DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
1041+
}
1042+
1043+
return flags;
1044+
}
1045+
9491046
bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
9501047
{
9511048
HRESULT ret;
@@ -958,14 +1055,7 @@ bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *eff
9581055
goto err_update;
9591056
}
9601057

961-
/* Set the flags. Might be worthwhile to diff temp with loaded effect and
962-
* only change those parameters. */
963-
flags = DIEP_DIRECTION |
964-
DIEP_DURATION |
965-
DIEP_ENVELOPE |
966-
DIEP_STARTDELAY |
967-
DIEP_TRIGGERBUTTON |
968-
DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
1058+
flags = DICalculateUpdateFlags(&effect->hweffect->effect, &temp);
9691059

9701060
// Create the actual effect.
9711061
ret =

0 commit comments

Comments
 (0)