WinRT: moved ill-performing XInput device-detection calls to a separate thread
authorDavid Ludwig <dludwig@pobox.com>
Tue, 24 Dec 2013 21:28:31 -0500
changeset 8552 abd934eac415
parent 8551 666f05b079ea
child 8553 199f6c870823
WinRT: moved ill-performing XInput device-detection calls to a separate thread
src/joystick/winrt/SDL_xinputjoystick.c
--- a/src/joystick/winrt/SDL_xinputjoystick.c	Tue Dec 24 21:08:11 2013 -0500
+++ b/src/joystick/winrt/SDL_xinputjoystick.c	Tue Dec 24 21:28:31 2013 -0500
@@ -38,16 +38,18 @@
 #include "../SDL_joystick_c.h"
 #include "SDL_events.h"
 #include "../../events/SDL_events_c.h"
+#include "SDL_timer.h"
 
 #include <Windows.h>
 #include <Xinput.h>
 
 struct joystick_hwdata {
-    //Uint8 bXInputHaptic; // Supports force feedback via XInput.
-    DWORD userIndex;    // The XInput device index, in the range [0, XUSER_MAX_COUNT-1] (probably [0,3]).
-    XINPUT_STATE XInputState;   // the last-read in XInputState, kept around to compare old and new values
-    SDL_bool isDeviceConnected; // was the device connected (on the last polling, or during backend-initialization)?
-    SDL_bool isDeviceRemovalEventPending;   // was the device removed, and is the associated removal event pending?
+    //Uint8 bXInputHaptic; // Supports force feedback via XInput.
+    DWORD userIndex;    // The XInput device index, in the range [0, XUSER_MAX_COUNT-1] (probably [0,3]).
+    XINPUT_STATE XInputState;   // the last-read in XInputState, kept around to compare old and new values
+    SDL_bool isDeviceConnected; // was the device connected (on the last detection-polling, or during backend-initialization)?
+    SDL_bool isDeviceConnectionEventPending;    // was a device added, and is the associated add-event pending?
+    SDL_bool isDeviceRemovalEventPending;   // was the device removed, and is the associated remove-event pending?
 };
 
 /* Keep track of data on all XInput devices, regardless of whether or not
@@ -55,6 +57,79 @@
  */
 static struct joystick_hwdata g_XInputData[XUSER_MAX_COUNT];
 
+/* Device detection can be extremely costly performance-wise, in some cases.
+   In particular, if no devices are connected, calls to detect a single device,
+   via either XInputGetState() or XInputGetCapabilities(), can take upwards of
+   20 ms on a 1st generation Surface RT, more if devices are detected across
+   all of of XInput's four device slots.  WinRT and XInput do not appear to
+   have callback-based APIs to notify an app when a device is connected, at
+   least as of Windows 8.1.  The synchronous XInput calls must be used.
+
+   Once a device is connected, calling XInputGetState() is a much less costly
+   operation, with individual calls costing well under 1 ms, and often under
+   0.1 ms [on a 1st gen Surface RT].
+
+   With XInput's performance limitations in mind, a separate device-detection
+   thread will be utilized (by SDL) to try to move costly XInput calls off the
+   main thread.  Polling of active devices still, however, occurs on the main
+   thread.
+ */
+static SDL_Thread * g_DeviceDetectionThread = NULL;
+static SDL_mutex * g_DeviceInfoLock = NULL;
+static SDL_bool g_DeviceDetectionQuit = SDL_FALSE;
+
+/* Main function for the device-detection thread.
+ */
+static int
+DeviceDetectionThreadMain(void * _data)
+{
+    DWORD result;
+    XINPUT_CAPABILITIES tempXInputCaps;
+    int i;
+
+    while (1) {
+        /* See if the device-detection thread is being asked to shutdown.
+         */
+        SDL_LockMutex(g_DeviceInfoLock);
+        if (g_DeviceDetectionQuit) {
+            SDL_UnlockMutex(g_DeviceInfoLock);
+            break;
+        }
+        SDL_UnlockMutex(g_DeviceInfoLock);
+
+        /* Add a short delay to prevent the device-detection thread from eating
+           up too much CPU time:
+         */
+        SDL_Delay(300);
+
+        /* TODO, WinRT: try making the device-detection thread wakeup sooner from its CPU-preserving SDL_Delay, if the thread was asked to quit.
+         */
+
+        /* See if any new devices are connected. */
+        for (i = 0; i < XUSER_MAX_COUNT; ++i) {
+            if (!g_XInputData[i].isDeviceConnected &&
+                !g_XInputData[i].isDeviceConnectionEventPending &&
+                !g_XInputData[i].isDeviceRemovalEventPending)
+            {
+                result = XInputGetCapabilities(i, 0, &tempXInputCaps);
+                if (result == ERROR_SUCCESS) {
+                    /* Yes, a device is connected.  Mark it as such.
+                       Others will be told about this (via an
+                       SDL_JOYDEVICEADDED event) in the next call to
+                       SDL_SYS_JoystickDetect.
+                     */
+                    SDL_LockMutex(g_DeviceInfoLock);
+                    g_XInputData[i].isDeviceConnected = SDL_TRUE;
+                    g_XInputData[i].isDeviceConnectionEventPending = SDL_TRUE;
+                    SDL_UnlockMutex(g_DeviceInfoLock);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
 /* Function to scan the system for joysticks.
  * It should return 0, or -1 on an unrecoverable fatal error.
  */
@@ -76,6 +151,12 @@
         }
     }
 
+    /* Start up the device-detection thread.
+     */
+    g_DeviceDetectionQuit = SDL_FALSE;
+    g_DeviceInfoLock = SDL_CreateMutex();
+    g_DeviceDetectionThread = SDL_CreateThread(DeviceDetectionThreadMain, "SDL_joystick", NULL);
+
     return (0);
 }
 
@@ -87,11 +168,13 @@
     /* Iterate through each possible XInput device and see if something
        was connected (at joystick init, or during the last polling).
      */
+    SDL_LockMutex(g_DeviceInfoLock);
     for (i = 0; i < XUSER_MAX_COUNT; ++i) {
         if (g_XInputData[i].isDeviceConnected) {
             ++joystickCount;
         }
     }
+    SDL_UnlockMutex(g_DeviceInfoLock);
 
     return joystickCount;
 }
@@ -99,36 +182,28 @@
 void SDL_SYS_JoystickDetect()
 {
     DWORD i;
-    XINPUT_STATE tempXInputState;
-    HRESULT result;
     SDL_Event event;
 
     /* Iterate through each possible XInput device, seeing if any devices
        have been connected, or if they were removed.
      */
+    SDL_LockMutex(g_DeviceInfoLock);
     for (i = 0; i < XUSER_MAX_COUNT; ++i) {
         /* See if any new devices are connected. */
-        if (!g_XInputData[i].isDeviceConnected && !g_XInputData[i].isDeviceRemovalEventPending) {
-            result = XInputGetState(i, &tempXInputState);
-            if (result == ERROR_SUCCESS) {
-                /* Yup, a device is connected.  Mark the device as connected,
-                   then tell others about it (via an SDL_JOYDEVICEADDED event.)
-                 */
-                g_XInputData[i].isDeviceConnected = SDL_TRUE;
-
+        if (g_XInputData[i].isDeviceConnectionEventPending) {
 #if !SDL_EVENTS_DISABLED
-                SDL_zero(event);
-                event.type = SDL_JOYDEVICEADDED;
-                
-                if (SDL_GetEventState(event.type) == SDL_ENABLE) {
-                    event.jdevice.which = i;
-                    if ((SDL_EventOK == NULL)
-                        || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
-                        SDL_PushEvent(&event);
-                    }
+            SDL_zero(event);
+            event.type = SDL_JOYDEVICEADDED;
+                
+            if (SDL_GetEventState(event.type) == SDL_ENABLE) {
+                event.jdevice.which = i;
+                if ((SDL_EventOK == NULL)
+                    || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
+                    SDL_PushEvent(&event);
                 }
+            }
 #endif
-            }
+            g_XInputData[i].isDeviceConnectionEventPending = SDL_FALSE;
         } else if (g_XInputData[i].isDeviceRemovalEventPending) {
             /* A device was previously marked as removed (by
                SDL_SYS_JoystickUpdate).  Tell others about the device removal.
@@ -137,19 +212,20 @@
             g_XInputData[i].isDeviceRemovalEventPending = SDL_FALSE;
 
 #if !SDL_EVENTS_DISABLED
-            SDL_zero(event);
-            event.type = SDL_JOYDEVICEREMOVED;
-                
-            if (SDL_GetEventState(event.type) == SDL_ENABLE) {
-                event.jdevice.which = i; //joystick->hwdata->userIndex;
-                if ((SDL_EventOK == NULL)
-                    || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
-                    SDL_PushEvent(&event);
-                }
+            SDL_zero(event);
+            event.type = SDL_JOYDEVICEREMOVED;
+                
+            if (SDL_GetEventState(event.type) == SDL_ENABLE) {
+                event.jdevice.which = i; //joystick->hwdata->userIndex;
+                if ((SDL_EventOK == NULL)
+                    || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
+                    SDL_PushEvent(&event);
+                }
             }
-#endif
+#endif
         }
     }
+    SDL_UnlockMutex(g_DeviceInfoLock);
 }
 
 SDL_bool SDL_SYS_JoystickNeedsPolling()
@@ -276,31 +352,35 @@
             device_index, (unsigned int)deviceCaps.Flags);
     }
 
-    /* Create the joystick data structure */
-    joystick->instance_id = device_index;
-    joystick->hwdata = &g_XInputData[device_index];
-
-    // The XInput API has a hard coded button/axis mapping, so we just match it
-    joystick->naxes = 6;
-    joystick->nbuttons = 15;
-    joystick->nballs = 0;
-    joystick->nhats = 0;
-
-    /* We're done! */
+    /* Create the joystick data structure */
+    joystick->instance_id = device_index;
+    joystick->hwdata = &g_XInputData[device_index];
+
+    // The XInput API has a hard coded button/axis mapping, so we just match it
+    joystick->naxes = 6;
+    joystick->nbuttons = 15;
+    joystick->nballs = 0;
+    joystick->nhats = 0;
+
+    /* We're done! */
     return (0);
 }
 
 /* Function to determine is this joystick is attached to the system right now */
 SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
 {
-    return joystick->hwdata->isDeviceConnected;
+    SDL_bool isDeviceConnected;
+    SDL_LockMutex(g_DeviceInfoLock);
+    isDeviceConnected = joystick->hwdata->isDeviceConnected;
+    SDL_UnlockMutex(g_DeviceInfoLock);
+    return isDeviceConnected;
 }
 
-/* Function to return > 0 if a bit array of buttons differs after applying a mask
-*/
-static int ButtonChanged( int ButtonsNow, int ButtonsPrev, int ButtonMask )
-{
-    return ( ButtonsNow & ButtonMask ) != ( ButtonsPrev & ButtonMask );
+/* Function to return > 0 if a bit array of buttons differs after applying a mask
+*/
+static int ButtonChanged( int ButtonsNow, int ButtonsPrev, int ButtonMask )
+{
+    return ( ButtonsNow & ButtonMask ) != ( ButtonsPrev & ButtonMask );
 }
 
 /* Function to update the state of a joystick - called as a device poll.
@@ -311,70 +391,76 @@
 void
 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
 {
-    HRESULT result;
-
-    /* Before polling for new data, make note of the old data */
-    XINPUT_STATE prevXInputState = joystick->hwdata->XInputState;
-
-    /* Poll for new data */
-    result = XInputGetState(joystick->hwdata->userIndex, &joystick->hwdata->XInputState);
-    if (result == ERROR_DEVICE_NOT_CONNECTED) {
-        if (joystick->hwdata->isDeviceConnected) {
-            joystick->hwdata->isDeviceConnected = SDL_FALSE;
-            joystick->hwdata->isDeviceRemovalEventPending = SDL_TRUE;
-            /* TODO, WinRT: make sure isDeviceRemovalEventPending gets cleared as appropriate, and that quick re-plugs don't cause trouble */
-        }
-        return;
-    }
-
-    /* Make sure the device is marked as connected */
-    joystick->hwdata->isDeviceConnected = SDL_TRUE;
-
-    // only fire events if the data changed from last time
-    if ( joystick->hwdata->XInputState.dwPacketNumber != 0 
-        && joystick->hwdata->XInputState.dwPacketNumber != prevXInputState.dwPacketNumber )
-    {
-        XINPUT_STATE *pXInputState = &joystick->hwdata->XInputState;
-        XINPUT_STATE *pXInputStatePrev = &prevXInputState;
-
-        SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX );
-        SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-1*pXInputState->Gamepad.sThumbLY-1) );
-        SDL_PrivateJoystickAxis(joystick, 2, (Sint16)pXInputState->Gamepad.sThumbRX );
-        SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(-1*pXInputState->Gamepad.sThumbRY-1) );
-        SDL_PrivateJoystickAxis(joystick, 4, (Sint16)((int)pXInputState->Gamepad.bLeftTrigger*32767/255) );
-        SDL_PrivateJoystickAxis(joystick, 5, (Sint16)((int)pXInputState->Gamepad.bRightTrigger*32767/255) );
-
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_UP ) )
-            SDL_PrivateJoystickButton(joystick, 0, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_DOWN ) )
-            SDL_PrivateJoystickButton(joystick, 1, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_LEFT ) )
-            SDL_PrivateJoystickButton(joystick, 2, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_RIGHT ) )
-            SDL_PrivateJoystickButton(joystick, 3, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_START ) )
-            SDL_PrivateJoystickButton(joystick, 4, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_START ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_BACK ) )
-            SDL_PrivateJoystickButton(joystick, 5, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_BACK ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_THUMB ) )
-            SDL_PrivateJoystickButton(joystick, 6, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_THUMB ) )
-            SDL_PrivateJoystickButton(joystick, 7, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_SHOULDER ) )
-            SDL_PrivateJoystickButton(joystick, 8, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_SHOULDER ) )
-            SDL_PrivateJoystickButton(joystick, 9, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_A ) )
-            SDL_PrivateJoystickButton(joystick, 10, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_A ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_B ) )
-            SDL_PrivateJoystickButton(joystick, 11, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_B ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_X ) )
-            SDL_PrivateJoystickButton(joystick, 12, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_X ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_Y ) )
-            SDL_PrivateJoystickButton(joystick, 13, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_Y ? SDL_PRESSED :	SDL_RELEASED );
-        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons,  0x400 ) )
-            SDL_PrivateJoystickButton(joystick, 14, pXInputState->Gamepad.wButtons & 0x400 ? SDL_PRESSED :	SDL_RELEASED ); // 0x400 is the undocumented code for the guide button
+    HRESULT result;
+    XINPUT_STATE prevXInputState;
+
+    SDL_LockMutex(g_DeviceInfoLock);
+
+    /* Before polling for new data, make note of the old data */
+    prevXInputState = joystick->hwdata->XInputState;
+
+    /* Poll for new data */
+    result = XInputGetState(joystick->hwdata->userIndex, &joystick->hwdata->XInputState);
+    if (result == ERROR_DEVICE_NOT_CONNECTED) {
+        if (joystick->hwdata->isDeviceConnected) {
+            joystick->hwdata->isDeviceConnected = SDL_FALSE;
+            joystick->hwdata->isDeviceRemovalEventPending = SDL_TRUE;
+            /* TODO, WinRT: make sure isDeviceRemovalEventPending gets cleared as appropriate, and that quick re-plugs don't cause trouble */
+        }
+        SDL_UnlockMutex(g_DeviceInfoLock);
+        return;
     }
+
+    /* Make sure the device is marked as connected */
+    joystick->hwdata->isDeviceConnected = SDL_TRUE;
+
+    // only fire events if the data changed from last time
+    if ( joystick->hwdata->XInputState.dwPacketNumber != 0 
+        && joystick->hwdata->XInputState.dwPacketNumber != prevXInputState.dwPacketNumber )
+    {
+        XINPUT_STATE *pXInputState = &joystick->hwdata->XInputState;
+        XINPUT_STATE *pXInputStatePrev = &prevXInputState;
+
+        SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX );
+        SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-1*pXInputState->Gamepad.sThumbLY-1) );
+        SDL_PrivateJoystickAxis(joystick, 2, (Sint16)pXInputState->Gamepad.sThumbRX );
+        SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(-1*pXInputState->Gamepad.sThumbRY-1) );
+        SDL_PrivateJoystickAxis(joystick, 4, (Sint16)((int)pXInputState->Gamepad.bLeftTrigger*32767/255) );
+        SDL_PrivateJoystickAxis(joystick, 5, (Sint16)((int)pXInputState->Gamepad.bRightTrigger*32767/255) );
+
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_UP ) )
+            SDL_PrivateJoystickButton(joystick, 0, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_DOWN ) )
+            SDL_PrivateJoystickButton(joystick, 1, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_LEFT ) )
+            SDL_PrivateJoystickButton(joystick, 2, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_RIGHT ) )
+            SDL_PrivateJoystickButton(joystick, 3, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_START ) )
+            SDL_PrivateJoystickButton(joystick, 4, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_START ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_BACK ) )
+            SDL_PrivateJoystickButton(joystick, 5, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_BACK ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_THUMB ) )
+            SDL_PrivateJoystickButton(joystick, 6, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_THUMB ) )
+            SDL_PrivateJoystickButton(joystick, 7, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_SHOULDER ) )
+            SDL_PrivateJoystickButton(joystick, 8, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_SHOULDER ) )
+            SDL_PrivateJoystickButton(joystick, 9, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_A ) )
+            SDL_PrivateJoystickButton(joystick, 10, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_A ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_B ) )
+            SDL_PrivateJoystickButton(joystick, 11, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_B ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_X ) )
+            SDL_PrivateJoystickButton(joystick, 12, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_X ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_Y ) )
+            SDL_PrivateJoystickButton(joystick, 13, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_Y ? SDL_PRESSED :	SDL_RELEASED );
+        if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons,  0x400 ) )
+            SDL_PrivateJoystickButton(joystick, 14, pXInputState->Gamepad.wButtons & 0x400 ? SDL_PRESSED :	SDL_RELEASED ); // 0x400 is the undocumented code for the guide button
+    }
+
+    SDL_UnlockMutex(g_DeviceInfoLock);
 }
 
 /* Function to close a joystick after use */
@@ -382,7 +468,9 @@
 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
 {
     /* Clear cached button data on the joystick */
+    SDL_LockMutex(g_DeviceInfoLock);
     SDL_zero(joystick->hwdata->XInputState);
+    SDL_UnlockMutex(g_DeviceInfoLock);
 
     /* There's need to free 'hwdata', as it's a pointer to a global array.
        The field will be cleared anyways, just to indicate that it's not
@@ -395,6 +483,18 @@
 void
 SDL_SYS_JoystickQuit(void)
 {
+    /* Tell the joystick detection thread to stop, then wait for it to finish */
+    SDL_LockMutex(g_DeviceInfoLock);
+    g_DeviceDetectionQuit = SDL_TRUE;
+    SDL_UnlockMutex(g_DeviceInfoLock);
+    SDL_WaitThread(g_DeviceDetectionThread, NULL);
+
+    /* Clean up device-detection stuff */
+    SDL_DestroyMutex(g_DeviceInfoLock);
+    g_DeviceInfoLock = NULL;
+    g_DeviceDetectionThread = NULL;
+    g_DeviceDetectionQuit = SDL_FALSE;
+
     return;
 }
 
@@ -419,15 +519,15 @@
     return guid;
 }
 
-SDL_bool SDL_SYS_IsXInputDeviceIndex(int device_index)
-{
-    /* The XInput-capable DirectInput joystick backend implements the same
-       function (SDL_SYS_IsXInputDeviceIndex), however in that case, not all
-       joystick devices are XInput devices.  In this case, with the
-       WinRT-enabled XInput-only backend, all "joystick" devices are XInput
-       devices.
-     */
-    return SDL_TRUE;
+SDL_bool SDL_SYS_IsXInputDeviceIndex(int device_index)
+{
+    /* The XInput-capable DirectInput joystick backend implements the same
+       function (SDL_SYS_IsXInputDeviceIndex), however in that case, not all
+       joystick devices are XInput devices.  In this case, with the
+       WinRT-enabled XInput-only backend, all "joystick" devices are XInput
+       devices.
+     */
+    return SDL_TRUE;
 }
 
 #endif /* SDL_JOYSTICK_XINPUT */