SDL: 8 bitdo polling rate corrections (#13221)

From 1e886c8a2f0068affe878374ed942668ca14890c Mon Sep 17 00:00:00 2001
From: Aubrey Hesselgren <[EMAIL REDACTED]>
Date: Fri, 13 Jun 2025 22:25:31 -0700
Subject: [PATCH] 8 bitdo polling rate corrections (#13221)

Gathered correct IMU polling rate in wired mode for good gyro synchronization.

Wireless: different models had different amounts of Bluetooth packet loss.
USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: Solid 120hz over bluetooth (note: only appears via).
USB_PRODUCT_8BITDO_PRO_2_BT: Lossy - 80-90hz registered.
SB_PRODUCT_8BITDO_SN30_PRO_BT & USB_PRODUCT_8BITDO_SN30_PRO_BT: Very Lossy - 60-90hz registered
---
 src/joystick/hidapi/SDL_hidapi_8bitdo.c | 36 ++++++++++++++++++++-----
 1 file changed, 30 insertions(+), 6 deletions(-)

diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c
index 170eb66638eab..10f19fc279df9 100644
--- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c
+++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c
@@ -48,8 +48,6 @@ enum
 #define SDL_8BITDO_BT_REPORTID_SDL_REPORTID                     0x01
 
 #define ABITDO_ACCEL_SCALE 4096.f
-#define ABITDO_SENSOR_POLLING_RATE 125.f
-#define SENSOR_INTERVAL_NS         8000000ULL
 #define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f
 
 typedef struct
@@ -69,7 +67,8 @@ typedef struct
     float accelScale;
     float gyroScale;
     Uint8 last_state[USB_PACKET_LENGTH];
-    Uint64 sensor_timestamp; // Nanoseconds.  Simulate onboard clock. Advance by known rate: SENSOR_INTERVAL_NS == 8ms = 125 Hz
+    Uint64 sensor_timestamp; // Nanoseconds.  Simulate onboard clock. Different models have different rates vs different connection styles.
+    Uint64 sensor_timestamp_interval;
 } SDL_Driver8BitDo_Context;
 
 #pragma pack(push,1)
@@ -217,6 +216,27 @@ static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device,
 {
 }
 
+static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *device)
+{
+    // TODO: If sensor time stamp is sent, these fixed settings from observation can be replaced
+    switch (device->product_id) {
+    // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool
+    case USB_PRODUCT_8BITDO_SN30_PRO_BT: 
+    case USB_PRODUCT_8BITDO_SF30_PRO_BT: 
+        return 90; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state
+    case USB_PRODUCT_8BITDO_SF30_PRO:
+    case USB_PRODUCT_8BITDO_SN30_PRO:
+        return 100;
+    case USB_PRODUCT_8BITDO_PRO_2:
+    case USB_PRODUCT_8BITDO_PRO_2_BT:// Note, labelled as "BT" but appears this way when wired. Observed bluetooth packet rate seems to be 80-90hz
+        return (device->is_bluetooth ? 85 : 100);
+    case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
+    default:
+        return 120;
+        break;
+    }
+}
+
 #ifndef DEG2RAD
 #define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
 #endif
@@ -242,9 +262,13 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
     joystick->nhats = 1;
 
     if (ctx->sensors_supported) {
-        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, ABITDO_SENSOR_POLLING_RATE);
-        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, ABITDO_SENSOR_POLLING_RATE);
 
+        // Different 8Bitdo controllers in different connection modes have different polling rates.
+        const Uint64 imu_polling_rate = HIDAPI_Driver8BitDo_GetIMURateForProductID(device);
+        ctx->sensor_timestamp_interval = SDL_NS_PER_SECOND / imu_polling_rate;
+
+        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)imu_polling_rate);
+        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)imu_polling_rate);
 
         ctx->accelScale = SDL_STANDARD_GRAVITY / ABITDO_ACCEL_SCALE;
         // Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX
@@ -525,7 +549,7 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr
         // In the absence of time stamp data from the data[], we can simulate that by
         // advancing a time stamp by the observed/known imu clock rate. This is 8ms = 125 Hz
         sensor_timestamp = ctx->sensor_timestamp;
-        ctx->sensor_timestamp += SENSOR_INTERVAL_NS;
+        ctx->sensor_timestamp += ctx->sensor_timestamp_interval;
 
         // This device's IMU values are reported differently from SDL
         // Thus we perform a rotation of the coordinate system to match the SDL standard.