Procházet zdrojové kódy

Fixed dropping raw input because of mixing GetRawInputBuffer() and WM_INPUT handling

It turns out that when you enable raw input and then process Windows messages, you'll get the currently pending input in GetRawInputBuffer(), and you'll get any new input that occurs while processing messages as WM_INPUT.

The fix for this is to create a dedicated thread to handle raw input and only use GetRawInputBuffer() in that thread. A nice side effect of this is that we'll get mouse input at the lowest latency possible, but raw mouse events will now occur on a separate thread, outside of the normal event loop processing.

Improved fix for https://github.com/libsdl-org/SDL/issues/8756
Sam Lantinga před 1 rokem
rodič
revize
31851a50d2

+ 22 - 28
src/video/windows/SDL_windowsevents.c

@@ -607,34 +607,14 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_WindowData *data, RAWM
     WIN_CheckRawMouseButtons(timestamp, rawmouse->usButtonFlags, data, mouseID);
 }
 
-/* The layout of memory for data returned from GetRawInputBuffer(), documented here:
- * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputbuffer
- */
-typedef struct
-{
-    union
-    {
-        RAWINPUTHEADER header;
-        BYTE padding[24];
-    } hdr;
-
-    union
-    {
-        RAWMOUSE mouse;
-        RAWKEYBOARD keyboard;
-        RAWHID hid;
-    } data;
-
-} ALIGNED_RAWINPUT;
-
-static void WIN_PollRawMouseInput()
+void WIN_PollRawMouseInput(void)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
     SDL_Window *window;
     SDL_WindowData *data;
     UINT size, count, i, total = 0;
     RAWINPUT *input;
-    Uint64 now, timestamp, increment;
+    Uint64 now;
 
     /* We only use raw mouse input in relative mode */
     if (!mouse->relative_mode || mouse->relative_mode_warp) {
@@ -649,6 +629,14 @@ static void WIN_PollRawMouseInput()
     data = window->driverdata;
 
     if (data->rawinput_size == 0) {
+        BOOL isWow64;
+
+        data->rawinput_offset = sizeof(RAWINPUTHEADER);
+        if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) {
+            /* We're going to get 64-bit data, so use the 64-bit RAWINPUTHEADER size */
+            data->rawinput_offset += 8;
+        }
+
         if (GetRawInputBuffer(NULL, &data->rawinput_size, sizeof(RAWINPUTHEADER)) == (UINT)-1) {
             return;
         }
@@ -679,13 +667,21 @@ static void WIN_PollRawMouseInput()
 
     now = SDL_GetTicksNS();
     if (total > 0) {
-        /* We'll spread these events over the time since the last poll */
-        timestamp = data->last_rawinput_poll;
-        increment = (now - timestamp) / total;
+        Uint64 timestamp, increment;
+        Uint64 delta = (now - data->last_rawinput_poll);
+        if (total > 1 && delta <= SDL_MS_TO_NS(100)) {
+            /* We'll spread these events over the time since the last poll */
+            timestamp = data->last_rawinput_poll;
+            increment = delta / total;
+        } else {
+            /* Do we want to track the update rate per device? */
+            timestamp = now;
+            increment = 0;
+        }
         for (i = 0, input = data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
             timestamp += increment;
             if (input->header.dwType == RIM_TYPEMOUSE) {
-                RAWMOUSE *rawmouse = &(((ALIGNED_RAWINPUT *)input)->data.mouse);
+                RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset);
                 WIN_HandleRawMouseInput(timestamp, window->driverdata, rawmouse);
             }
         }
@@ -1763,8 +1759,6 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
     SDL_Window *focusWindow;
 #endif
 
-    WIN_PollRawMouseInput();
-
     if (g_WindowsEnableMessageLoop) {
         SDL_processing_messages = SDL_TRUE;
 

+ 1 - 0
src/video/windows/SDL_windowsevents.h

@@ -30,6 +30,7 @@ extern HINSTANCE SDL_Instance;
 extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
 extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam,
                                        LPARAM lParam);
+extern void WIN_PollRawMouseInput(void);
 extern void WIN_PumpEvents(SDL_VideoDevice *_this);
 extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
 extern int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS);

+ 117 - 15
src/video/windows/SDL_windowsmouse.c

@@ -23,9 +23,11 @@
 #if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(__XBOXONE__) && !defined(__XBOXSERIES__)
 
 #include "SDL_windowsvideo.h"
+#include "SDL_windowsevents.h"
 
-#include "../../events/SDL_mouse_c.h"
 #include "../SDL_video_c.h"
+#include "../../events/SDL_mouse_c.h"
+#include "../../joystick/usb_ids.h"
 
 DWORD SDL_last_warp_time = 0;
 HCURSOR SDL_cursor = NULL;
@@ -33,9 +35,84 @@ static SDL_Cursor *SDL_blank_cursor = NULL;
 
 static int rawInputEnableCount = 0;
 
+typedef struct
+{
+    HANDLE ready_event;
+    HANDLE done_event;
+    HANDLE thread;
+} RawMouseThreadData;
+
+static RawMouseThreadData thread_data = {
+    INVALID_HANDLE_VALUE,
+    INVALID_HANDLE_VALUE,
+    INVALID_HANDLE_VALUE
+};
+
+static DWORD WINAPI WIN_RawMouseThread(LPVOID param)
+{
+    RAWINPUTDEVICE rawMouse;
+    HWND window;
+
+    window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
+    if (!window) {
+        return 0;
+    }
+
+    rawMouse.usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
+    rawMouse.usUsage = USB_USAGE_GENERIC_MOUSE;
+    rawMouse.dwFlags = 0;
+    rawMouse.hwndTarget = window;
+
+    if (!RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse))) {
+        DestroyWindow(window);
+        return 0;
+    }
+
+    /* Tell the parent we're ready to go! */
+    SetEvent(thread_data.ready_event);
+
+    for ( ; ; ) {
+        if (MsgWaitForMultipleObjects(1, &thread_data.done_event, 0, INFINITE, QS_RAWINPUT) != WAIT_OBJECT_0 + 1) {
+            break;
+        }
+
+        /* Clear the queue status so MsgWaitForMultipleObjects() will wait again */
+        (void)GetQueueStatus(QS_RAWINPUT);
+
+        WIN_PollRawMouseInput();
+    }
+
+    rawMouse.dwFlags |= RIDEV_REMOVE;
+    RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse));
+
+    DestroyWindow(window);
+
+    return 0;
+}
+
+static void CleanupRawMouseThreadData(void)
+{
+    if (thread_data.thread != INVALID_HANDLE_VALUE) {
+        SetEvent(thread_data.done_event);
+        WaitForSingleObject(thread_data.thread, 500);
+        CloseHandle(thread_data.thread);
+        thread_data.thread = INVALID_HANDLE_VALUE;
+    }
+
+    if (thread_data.ready_event != INVALID_HANDLE_VALUE) {
+        CloseHandle(thread_data.ready_event);
+        thread_data.ready_event = INVALID_HANDLE_VALUE;
+    }
+
+    if (thread_data.done_event != INVALID_HANDLE_VALUE) {
+        CloseHandle(thread_data.done_event);
+        thread_data.done_event = INVALID_HANDLE_VALUE;
+    }
+}
+
 static int ToggleRawInput(SDL_bool enabled)
 {
-    RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
+    int result = -1;
 
     if (enabled) {
         rawInputEnableCount++;
@@ -52,23 +129,48 @@ static int ToggleRawInput(SDL_bool enabled)
         }
     }
 
-    if (!enabled) {
-        rawMouse.dwFlags |= RIDEV_REMOVE;
-    }
+    if (enabled) {
+        HANDLE handles[2];
 
-    /* (Un)register raw input for mice */
-    if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) {
-        /* Reset the enable count, otherwise subsequent enable calls will
-           believe raw input is enabled */
-        rawInputEnableCount = 0;
+        thread_data.ready_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+        if (thread_data.ready_event == INVALID_HANDLE_VALUE) {
+            WIN_SetError("CreateEvent");
+            goto done;
+        }
+
+        thread_data.done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+        if (thread_data.done_event == INVALID_HANDLE_VALUE) {
+            WIN_SetError("CreateEvent");
+            goto done;
+        }
 
-        /* Only return an error when registering. If we unregister and fail,
-           then it's probably that we unregistered twice. That's OK. */
-        if (enabled) {
-            return SDL_Unsupported();
+        thread_data.thread = CreateThread(NULL, 0, WIN_RawMouseThread, &thread_data, 0, NULL);
+        if (thread_data.thread == INVALID_HANDLE_VALUE) {
+            WIN_SetError("CreateThread");
+            goto done;
         }
+
+        /* Wait for the thread to signal ready or exit */
+        handles[0] = thread_data.ready_event;
+        handles[1] = thread_data.thread;
+        if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) != WAIT_OBJECT_0) {
+            SDL_SetError("Couldn't set up raw input handling");
+            goto done;
+        }
+        result = 0;
+    } else {
+        CleanupRawMouseThreadData();
+        result = 0;
     }
-    return 0;
+
+done:
+    if (enabled && result < 0) {
+        CleanupRawMouseThreadData();
+
+        /* Reset rawInputEnableCount so we can try again */
+        rawInputEnableCount = 0;
+    }
+    return result;
 }
 
 static SDL_Cursor *WIN_CreateDefaultCursor()

+ 1 - 0
src/video/windows/SDL_windowswindow.h

@@ -67,6 +67,7 @@ struct SDL_WindowData
     SDL_bool in_window_deactivation;
     RECT cursor_clipped_rect;
     RAWINPUT *rawinput;
+    UINT rawinput_offset;
     UINT rawinput_size;
     UINT rawinput_count;
     Uint64 last_rawinput_poll;