Browse Source

For gamepads that don't have their own sensors, try to use the system sensors.

This allows using the gyro and accelerometer in handheld devices in conjunction with built-in or wraparound controllers.
Sam Lantinga 1 year ago
parent
commit
42e4639a5e

+ 12 - 0
include/SDL3/SDL_hints.h

@@ -616,6 +616,18 @@ extern "C" {
  */
 #define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS"
 
+/**
+ *  \brief  Controls whether the device's built-in accelerometer and gyro should be used as sensors for gamepads.
+ *
+ *  The variable can be set to the following values:
+ *    "auto"    - Sensor fusion is enabled for known wraparound controllers like the Razer Kishi and Backbone One
+ *    "0"       - Sensor fusion is disabled
+ *    "1"       - Sensor fusion is enabled for all controllers that lack sensors
+ *
+ *  The default value is "auto". This hint is checked when a gamepad is opened.
+ */
+#define SDL_HINT_GAMECONTROLLER_SENSOR_FUSION "SDL_GAMECONTROLLER_SENSOR_FUSION"
+
 /**
  *  \brief  A variable controlling whether grabbing input grabs the keyboard
  *

+ 17 - 0
src/SDL_hints.c

@@ -171,6 +171,23 @@ const char *SDL_GetHint(const char *name)
     return env;
 }
 
+int SDL_GetStringInteger(const char *value, int default_value)
+{
+    if (value == NULL || !*value) {
+        return default_value;
+    }
+    if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
+        return 0;
+    }
+    if (*value == '1' || SDL_strcasecmp(value, "true") == 0) {
+        return 1;
+    }
+    if (*value == '-' || SDL_isdigit(*value)) {
+        return SDL_atoi(value);
+    }
+    return default_value;
+}
+
 SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value)
 {
     if (value == NULL || !*value) {

+ 1 - 0
src/SDL_hints_c.h

@@ -26,5 +26,6 @@
 #define SDL_hints_c_h_
 
 extern SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value);
+extern int SDL_GetStringInteger(const char *value, int default_value);
 
 #endif /* SDL_hints_c_h_ */

+ 49 - 8
src/joystick/SDL_gamepad.c

@@ -444,6 +444,19 @@ static int SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event)
             SDL_PushEvent(&deviceevent);
         }
     } break;
+    case SDL_EVENT_SENSOR_UPDATE:
+    {
+        SDL_LockJoysticks();
+        for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
+            if (gamepad->joystick->accel && gamepad->joystick->accel_sensor == event->sensor.which) {
+                SDL_SendJoystickSensor(event->common.timestamp, gamepad->joystick, SDL_SENSOR_ACCEL, event->sensor.sensor_timestamp, event->sensor.data, SDL_arraysize(event->sensor.data));
+            }
+            if (gamepad->joystick->gyro && gamepad->joystick->gyro_sensor == event->sensor.which) {
+                SDL_SendJoystickSensor(event->common.timestamp, gamepad->joystick, SDL_SENSOR_GYRO, event->sensor.sensor_timestamp, event->sensor.data, SDL_arraysize(event->sensor.data));
+            }
+        }
+        SDL_UnlockJoysticks();
+    } break;
     default:
         break;
     }
@@ -2523,22 +2536,50 @@ int SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, SDL_b
                         return 0;
                     }
 
-                    if (enabled) {
-                        if (joystick->nsensors_enabled == 0) {
-                            if (joystick->driver->SetSensorsEnabled(joystick, SDL_TRUE) < 0) {
+                    if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) {
+                        if (enabled) {
+                            joystick->accel = SDL_OpenSensor(joystick->accel_sensor);
+                            if (!joystick->accel) {
                                 SDL_UnlockJoysticks();
                                 return -1;
                             }
+                        } else {
+                            if (joystick->accel) {
+                                SDL_CloseSensor(joystick->accel);
+                                joystick->accel = NULL;
+                            }
                         }
-                        ++joystick->nsensors_enabled;
-                    } else {
-                        if (joystick->nsensors_enabled == 1) {
-                            if (joystick->driver->SetSensorsEnabled(joystick, SDL_FALSE) < 0) {
+                    } else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) {
+                        if (enabled) {
+                            joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor);
+                            if (!joystick->gyro) {
                                 SDL_UnlockJoysticks();
                                 return -1;
                             }
+                        } else {
+                            if (joystick->gyro) {
+                                SDL_CloseSensor(joystick->gyro);
+                                joystick->gyro = NULL;
+                            }
+                        }
+                    } else {
+                        if (enabled) {
+                            if (joystick->nsensors_enabled == 0) {
+                                if (joystick->driver->SetSensorsEnabled(joystick, SDL_TRUE) < 0) {
+                                    SDL_UnlockJoysticks();
+                                    return -1;
+                                }
+                            }
+                            ++joystick->nsensors_enabled;
+                        } else {
+                            if (joystick->nsensors_enabled == 1) {
+                                if (joystick->driver->SetSensorsEnabled(joystick, SDL_FALSE) < 0) {
+                                    SDL_UnlockJoysticks();
+                                    return -1;
+                                }
+                            }
+                            --joystick->nsensors_enabled;
                         }
-                        --joystick->nsensors_enabled;
                     }
 
                     sensor->enabled = enabled;

+ 179 - 2
src/joystick/SDL_joystick.c

@@ -31,6 +31,7 @@
 #include "../events/SDL_events_c.h"
 #endif
 #include "../video/SDL_sysvideo.h"
+#include "../sensor/SDL_sensor_c.h"
 #include "hidapi/SDL_hidapijoystick_c.h"
 
 /* This is included in only one place because it has a large static list of controllers */
@@ -510,6 +511,166 @@ static SDL_bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick)
 #endif /* __WINRT__ */
 }
 
+static SDL_bool IsBackboneOne(SDL_Joystick *joystick)
+{
+    if (joystick->name && SDL_strstr(joystick->name, "Backbone One")) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
+static SDL_bool IsROGAlly(SDL_Joystick *joystick)
+{
+    Uint16 vendor, product;
+    SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick);
+
+    /* The ROG Ally controller spoofs an Xbox 360 controller */
+    SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
+    if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) {
+        /* Check to see if this system has the expected sensors */
+        SDL_bool has_ally_accel = SDL_FALSE;
+        SDL_bool has_ally_gyro = SDL_FALSE;
+
+        if (SDL_InitSubSystem(SDL_INIT_SENSOR) == 0) {
+            SDL_SensorID *sensors = SDL_GetSensors(NULL);
+            if (sensors) {
+                int i;
+                for (i = 0; sensors[i]; ++i) {
+                    SDL_SensorID sensor = sensors[i];
+
+                    if (!has_ally_accel && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_ACCEL) {
+                        const char *sensor_name = SDL_GetSensorInstanceName(sensor);
+                        if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Acc") == 0) {
+                            has_ally_accel = SDL_TRUE;
+                        }
+                    }
+                    if (!has_ally_gyro && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_GYRO) {
+                        const char *sensor_name = SDL_GetSensorInstanceName(sensor);
+                        if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Gyr") == 0) {
+                            has_ally_gyro = SDL_TRUE;
+                        }
+                    }
+                }
+                SDL_free(sensors);
+            }
+            SDL_QuitSubSystem(SDL_INIT_SENSOR);
+        }
+        if (has_ally_accel && has_ally_gyro) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
+static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick)
+{
+    static Uint32 wraparound_gamepads[] = {
+        MAKE_VIDPID(0x1949, 0x0402),    /* Ipega PG-9083S */
+        MAKE_VIDPID(0x27f8, 0x0bbc),    /* Gamevice */
+        MAKE_VIDPID(0x27f8, 0x0bbf),    /* Razer Kishi */
+    };
+    SDL_JoystickGUID guid;
+    Uint16 vendor, product;
+    Uint32 vidpid;
+    int i;
+    int hint;
+
+    /* The SDL controller sensor API is only available for gamepads (at the moment) */
+    if (!joystick->is_gamepad) {
+        return SDL_FALSE;
+    }
+
+    /* If the controller already has sensors, use those */
+    if (joystick->nsensors > 0) {
+        return SDL_FALSE;
+    }
+
+    hint = SDL_GetStringInteger(SDL_GetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION), -1);
+    if (hint > 0) {
+        return SDL_TRUE;
+    }
+    if (hint == 0) {
+        return SDL_FALSE;
+    }
+
+    /* See if the controller is in our list of wraparound gamepads */
+    guid = SDL_GetJoystickGUID(joystick);
+    SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
+    vidpid = MAKE_VIDPID(vendor, product);
+    for (i = 0; i < SDL_arraysize(wraparound_gamepads); ++i) {
+        if (vidpid == wraparound_gamepads[i]) {
+            return SDL_TRUE;
+        }
+    }
+
+    /* See if this is another known wraparound gamepad */
+    if (IsBackboneOne(joystick) || IsROGAlly(joystick)) {
+        return SDL_TRUE;
+    }
+
+    return SDL_FALSE;
+}
+
+static void AttemptSensorFusion(SDL_Joystick *joystick)
+{
+    SDL_SensorID *sensors;
+    int i;
+
+    if (SDL_InitSubSystem(SDL_INIT_SENSOR) < 0) {
+        return;
+    }
+
+    sensors = SDL_GetSensors(NULL);
+    if (sensors) {
+        for (i = 0; sensors[i]; ++i) {
+            SDL_SensorID sensor = sensors[i];
+
+            if (!joystick->accel_sensor && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_ACCEL) {
+                /* Increment the sensor subsystem reference count */
+                SDL_InitSubSystem(SDL_INIT_SENSOR);
+
+                joystick->accel_sensor = sensor;
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
+            }
+            if (!joystick->gyro_sensor && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_GYRO) {
+                /* Increment the sensor subsystem reference count */
+                SDL_InitSubSystem(SDL_INIT_SENSOR);
+
+                joystick->gyro_sensor = sensor;
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
+            }
+        }
+        SDL_free(sensors);
+    }
+    SDL_QuitSubSystem(SDL_INIT_SENSOR);
+}
+
+static void CleanupSensorFusion(SDL_Joystick *joystick)
+{
+    if (joystick->accel_sensor || joystick->gyro_sensor) {
+        if (joystick->accel_sensor) {
+            if (joystick->accel) {
+                SDL_CloseSensor(joystick->accel);
+                joystick->accel = NULL;
+            }
+            joystick->accel_sensor = 0;
+
+            /* Decrement the sensor subsystem reference count */
+            SDL_QuitSubSystem(SDL_INIT_SENSOR);
+        }
+        if (joystick->gyro_sensor) {
+            if (joystick->gyro) {
+                SDL_CloseSensor(joystick->gyro);
+                joystick->gyro = NULL;
+            }
+            joystick->gyro_sensor = 0;
+
+            /* Decrement the sensor subsystem reference count */
+            SDL_QuitSubSystem(SDL_INIT_SENSOR);
+        }
+    }
+}
+
 /*
  * Open a joystick for use - the index passed as an argument refers to
  * the N'th joystick on the system.  This index is the value which will
@@ -611,6 +772,11 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
 
     joystick->is_gamepad = SDL_IsGamepad(instance_id);
 
+    /* Use system gyro and accelerometer if the gamepad doesn't have built-in sensors */
+    if (ShouldAttemptSensorFusion(joystick)) {
+        AttemptSensorFusion(joystick);
+    }
+
     /* Add joystick to list */
     ++joystick->ref_count;
     /* Link the joystick in the list */
@@ -1253,6 +1419,8 @@ void SDL_CloseJoystick(SDL_Joystick *joystick)
             SDL_RumbleJoystickTriggers(joystick, 0, 0, 0);
         }
 
+        CleanupSensorFusion(joystick);
+
         joystick->driver->Close(joystick);
         joystick->hwdata = NULL;
         joystick->magic = NULL;
@@ -1273,11 +1441,10 @@ void SDL_CloseJoystick(SDL_Joystick *joystick)
             joysticklist = joysticklist->next;
         }
 
+        /* Free the data associated with this joystick */
         SDL_free(joystick->name);
         SDL_free(joystick->path);
         SDL_free(joystick->serial);
-
-        /* Free the data associated with this joystick */
         SDL_free(joystick->axes);
         SDL_free(joystick->hats);
         SDL_free(joystick->buttons);
@@ -1682,6 +1849,16 @@ void SDL_UpdateJoysticks(void)
 
     for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
         if (joystick->attached) {
+            if (joystick->accel || joystick->gyro) {
+                SDL_LockSensors();
+                if (joystick->gyro) {
+                    SDL_UpdateSensor(joystick->gyro);
+                }
+                if (joystick->accel) {
+                    SDL_UpdateSensor(joystick->accel);
+                }
+                SDL_UnlockSensors();
+            }
             joystick->driver->Update(joystick);
 
             if (joystick->delayed_guide_button) {

+ 5 - 0
src/joystick/SDL_sysjoystick.h

@@ -113,6 +113,11 @@ struct SDL_Joystick
     SDL_bool delayed_guide_button _guarded;      /* SDL_TRUE if this device has the guide button event delayed */
     SDL_JoystickPowerLevel epowerlevel _guarded; /* power level of this joystick, SDL_JOYSTICK_POWER_UNKNOWN if not supported */
 
+    SDL_SensorID accel_sensor _guarded;
+    SDL_Sensor *accel _guarded;
+    SDL_SensorID gyro_sensor _guarded;
+    SDL_Sensor *gyro _guarded;
+
     struct SDL_JoystickDriver *driver _guarded;
 
     struct joystick_hwdata *hwdata _guarded; /* Driver dependent information */

+ 5 - 0
src/sensor/SDL_sensor.c

@@ -504,6 +504,11 @@ int SDL_SendSensorUpdate(Uint64 timestamp, SDL_Sensor *sensor, Uint64 sensor_tim
     return posted;
 }
 
+void SDL_UpdateSensor(SDL_Sensor *sensor)
+{
+    sensor->driver->Update(sensor);
+}
+
 void SDL_UpdateSensors(void)
 {
     int i;

+ 3 - 0
src/sensor/SDL_sensor_c.h

@@ -40,6 +40,9 @@ extern void SDL_UnlockSensors(void);
 /* Function to return whether there are any sensors opened by the application */
 extern SDL_bool SDL_SensorsOpened(void);
 
+/* Update an individual sensor, used by gamepad sensor fusion */
+extern void SDL_UpdateSensor(SDL_Sensor *sensor);
+
 /* Internal event queueing functions */
 extern int SDL_SendSensorUpdate(Uint64 timestamp, SDL_Sensor *sensor, Uint64 sensor_timestamp, float *data, int num_values);