Sfoglia il codice sorgente

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.
dwillbarron 2 mesi fa
parent
commit
618eb57519
1 ha cambiato i file con 98 aggiunte e 8 eliminazioni
  1. 98 8
      src/haptic/windows/SDL_dinputhaptic.c

+ 98 - 8
src/haptic/windows/SDL_dinputhaptic.c

@@ -946,6 +946,103 @@ err_effectdone:
     return false;
 }
 
+BOOL DIGetDirectionUpdateFlag(DIEFFECT *before, DIEFFECT *after)
+{
+    if (before->cAxes != after->cAxes) {
+        return true;
+    }
+    // rglDirection must be non-null for DIEP_DIRECTION to be a valid flag
+    if (after->rglDirection == NULL) {
+        return false;
+    }
+    if (before->rglDirection == NULL) {
+        return true;
+    }
+    return SDL_memcmp(before->rglDirection, after->rglDirection, sizeof(LONG) * before->cAxes) != 0;
+}
+
+BOOL DIGetEnvelopeUpdateFlag(DIEFFECT* before, DIEFFECT* after)
+{
+    if (before->lpEnvelope == NULL && after->lpEnvelope == NULL) {
+        return false;
+    }
+    // A null lpEnvelope is valid for DIEP_ENVELOPE (clears the envelope from the effect)
+    if (before->lpEnvelope == NULL || after->lpEnvelope == NULL) {
+        return true;
+    }
+    return SDL_memcmp(before->lpEnvelope, after->lpEnvelope, sizeof(DIENVELOPE)) != 0;
+}
+
+BOOL DIGetTypeSpecificParamsUpdateFlag(DIEFFECT *before, DIEFFECT *after)
+{
+    // Shouldn't happen since this implies an effect's type somehow changed, but need to check to avoid an out-of-bounds memcmp
+    if (before->cbTypeSpecificParams != after->cbTypeSpecificParams) {
+        return true;
+    }
+    // lpvTypeSpecificParams must be non-null for the DIEP_TYPESPECIFICPARAMS flag.
+    if (after->lpvTypeSpecificParams == NULL) {
+        return false;
+    }
+    if (before->lpvTypeSpecificParams == NULL) {
+        return true;
+    }
+    return SDL_memcmp(before->lpvTypeSpecificParams, after->lpvTypeSpecificParams, before->cbTypeSpecificParams) != 0;
+}
+
+/*
+    Calculate the exact flags needed when updating an existing DirectInput haptic effect.
+*/
+DWORD DICalculateUpdateFlags(DIEFFECT *before, DIEFFECT *after)
+{
+    DWORD flags = 0;
+
+    if (DIGetDirectionUpdateFlag(before, after)) {
+        flags |= DIEP_DIRECTION;
+    }
+
+    if (before->dwDuration != after->dwDuration) {
+        flags |= DIEP_DURATION;
+    }
+
+    if (DIGetEnvelopeUpdateFlag(before, after)) {
+        flags |= DIEP_ENVELOPE;
+    }
+
+    if (before->dwStartDelay != after->dwStartDelay) {
+        flags |= DIEP_STARTDELAY;
+    }
+
+    if (before->dwTriggerButton != after->dwTriggerButton) {
+        flags |= DIEP_TRIGGERBUTTON;
+    }
+
+    if (before->dwTriggerRepeatInterval != after->dwTriggerRepeatInterval) {
+        flags |= DIEP_TRIGGERREPEATINTERVAL;
+    }
+
+    if (DIGetTypeSpecificParamsUpdateFlag(before, after)) {
+        flags |= DIEP_TYPESPECIFICPARAMS;
+    }
+
+    if (flags == 0) {
+        /* Awkward: SDL_UpdateHapticEffect was called, but nothing was changed.
+         * Calling IDirectInputEffect_SetParameters with no flags is nonsense,
+         * so our options are to either send all the flags, or exit early.
+         * Sending all the flags seems like the safer option: The programmer may be trying
+         * to force an update for some reason (e.g. driver bug workaround?). Conversely,
+         * if the programmer doesn't want IDirectInputEffect_SetParameters to be called, they
+         * can just avoid calling SDL_UpdateHapticEffect when there's no changes. */
+        flags = DIEP_DIRECTION |
+                DIEP_DURATION |
+                DIEP_ENVELOPE |
+                DIEP_STARTDELAY |
+                DIEP_TRIGGERBUTTON |
+                DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
+    }
+
+    return flags;
+}
+
 bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
 {
     HRESULT ret;
@@ -958,14 +1055,7 @@ bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *eff
         goto err_update;
     }
 
-    /* Set the flags.  Might be worthwhile to diff temp with loaded effect and
-     *  only change those parameters. */
-    flags = DIEP_DIRECTION |
-            DIEP_DURATION |
-            DIEP_ENVELOPE |
-            DIEP_STARTDELAY |
-            DIEP_TRIGGERBUTTON |
-            DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
+    flags = DICalculateUpdateFlags(&effect->hweffect->effect, &temp);
 
     // Create the actual effect.
     ret =