From b9c227e07d2956b23f43f08fc65835e1a1d3ed7c Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 17 Mar 2026 12:54:18 -0700 Subject: [PATCH] Make changing raw input mode on Windows a very fast operation (take 2) The first approach had the drawback that it accumulated handles while raw input was paused, eventually crashing the application. Now we'll keep reading raw events from the queue, we just won't deliver them to the application. --- src/video/windows/SDL_windowsevents.c | 4 +- src/video/windows/SDL_windowsevents.h | 2 +- src/video/windows/SDL_windowsrawinput.c | 68 ++++++++----------------- src/video/windows/SDL_windowsrawinput.h | 2 +- src/video/windows/SDL_windowsvideo.c | 4 +- 5 files changed, 29 insertions(+), 51 deletions(-) diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index ea57c1ca42..19f49e9b36 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -786,7 +786,7 @@ static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_VideoData *data, HA SDL_SendKeyboardKey(timestamp, keyboardID, rawcode, code, down); } -void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start, bool process_input) +void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start) { SDL_VideoData *data = _this->internal; UINT size, i, count, total = 0; @@ -832,7 +832,7 @@ void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start, bool process_in } } - if (total > 0 && process_input) { + if (total > 0) { Uint64 delta = poll_finish - poll_start; UINT mouse_total = 0; for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) { diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h index 21bd7a420c..45f5527dc6 100644 --- a/src/video/windows/SDL_windowsevents.h +++ b/src/video/windows/SDL_windowsevents.h @@ -29,7 +29,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_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start, bool process_input); +extern void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start); extern void WIN_PumpEvents(SDL_VideoDevice *_this); extern void WIN_PumpEventsForHWND(SDL_VideoDevice *_this, HWND hwnd); extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/windows/SDL_windowsrawinput.c b/src/video/windows/SDL_windowsrawinput.c index 3c5d80ab2b..5ae2f51d23 100644 --- a/src/video/windows/SDL_windowsrawinput.c +++ b/src/video/windows/SDL_windowsrawinput.c @@ -40,7 +40,6 @@ typedef struct { bool done; - bool paused; Uint32 flags; HANDLE ready_event; HANDLE signal_event; @@ -48,7 +47,6 @@ typedef struct } RawInputThreadData; static RawInputThreadData thread_data = { - false, false, 0, INVALID_HANDLE_VALUE, @@ -106,34 +104,22 @@ static DWORD WINAPI WIN_RawInputThread(LPVOID param) // Tell the parent we're ready to go! SetEvent(data->ready_event); - while (!data->done) { - Uint64 idle_begin = SDL_GetTicksNS(); - while (!data->done && !data->paused && - // The high-order word of GetQueueStatus() will let us know if there's currently raw input to be processed. - // If there isn't, then we'll wait for new data to arrive with MsgWaitForMultipleObjects(). - ((HIWORD(GetQueueStatus(QS_RAWINPUT)) & QS_RAWINPUT) || - (MsgWaitForMultipleObjects(1, &data->signal_event, FALSE, INFINITE, QS_RAWINPUT) == WAIT_OBJECT_0 + 1))) { + Uint64 idle_begin = SDL_GetTicksNS(); + while (!data->done && + // The high-order word of GetQueueStatus() will let us know if there's currently raw input to be processed. + // If there isn't, then we'll wait for new data to arrive with MsgWaitForMultipleObjects(). + ((HIWORD(GetQueueStatus(QS_RAWINPUT)) & QS_RAWINPUT) || + (MsgWaitForMultipleObjects(1, &data->signal_event, FALSE, INFINITE, QS_RAWINPUT) == WAIT_OBJECT_0 + 1))) { - Uint64 idle_end = SDL_GetTicksNS(); - Uint64 idle_time = idle_end - idle_begin; - Uint64 usb_8khz_interval = SDL_US_TO_NS(125); - Uint64 poll_start = idle_time < usb_8khz_interval ? _this->internal->last_rawinput_poll : idle_end; + Uint64 idle_end = SDL_GetTicksNS(); + Uint64 idle_time = idle_end - idle_begin; + Uint64 usb_8khz_interval = SDL_US_TO_NS(125); + Uint64 poll_start = idle_time < usb_8khz_interval ? _this->internal->last_rawinput_poll : idle_end; - WIN_PollRawInput(_this, poll_start, true); + WIN_PollRawInput(_this, poll_start); - // Reset idle_begin for the next go-around - idle_begin = SDL_GetTicksNS(); - } - - if (data->paused) { - // Wait for the resume signal - while (data->paused) { - WaitForSingleObject(data->signal_event, INFINITE); - } - - // Flush raw input queued while paused - WIN_PollRawInput(_this, SDL_GetTicksNS(), false); - } + // Reset idle_begin for the next go-around + idle_begin = SDL_GetTicksNS(); } if (_this->internal->raw_input_fake_pen_id) { @@ -155,7 +141,6 @@ static DWORD WINAPI WIN_RawInputThread(LPVOID param) static void CleanupRawInputThreadData(RawInputThreadData *data) { if (data->thread != INVALID_HANDLE_VALUE) { - data->paused = false; data->done = true; SetEvent(data->signal_event); WaitForSingleObject(data->thread, 3000); @@ -174,20 +159,10 @@ static void CleanupRawInputThreadData(RawInputThreadData *data) } } -bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags, bool force) +bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags) { bool result = false; - if (thread_data.thread != INVALID_HANDLE_VALUE && !force) { - if (flags) { - thread_data.paused = false; - } else { - thread_data.paused = true; - } - SetEvent(thread_data.signal_event); - return true; - } - CleanupRawInputThreadData(&thread_data); if (flags) { @@ -248,13 +223,14 @@ static bool WIN_UpdateRawInputEnabled(SDL_VideoDevice *_this) if (data->raw_keyboard_flag_inputsink) { flags |= RAW_KEYBOARD_FLAG_INPUTSINK; } + + // Leave the thread running, as it takes several ms to shut it down and spin it up. + // We'll continue processing them so they don't back up in the thread event queue, + // but we won't deliver raw events in the application. + flags |= (data->raw_input_enabled & (ENABLE_RAW_MOUSE_INPUT | ENABLE_RAW_KEYBOARD_INPUT)); + if (flags != data->raw_input_enabled) { - bool force = false; - if ((flags ^ data->raw_input_enabled) & (RAW_KEYBOARD_FLAG_NOHOTKEYS | RAW_KEYBOARD_FLAG_INPUTSINK)) { - // The keyboard flags have changed - force = true; - } - if (WIN_SetRawInputEnabled(_this, flags, force)) { + if (WIN_SetRawInputEnabled(_this, flags)) { data->raw_input_enabled = flags; } else { return false; @@ -338,7 +314,7 @@ bool WIN_SetRawKeyboardFlag_Inputsink(SDL_VideoDevice *_this, bool enabled) #else -bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags, bool force) +bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags) { return SDL_Unsupported(); } diff --git a/src/video/windows/SDL_windowsrawinput.h b/src/video/windows/SDL_windowsrawinput.h index e970666a9a..1adeb45f05 100644 --- a/src/video/windows/SDL_windowsrawinput.h +++ b/src/video/windows/SDL_windowsrawinput.h @@ -23,7 +23,7 @@ #ifndef SDL_windowsrawinput_h_ #define SDL_windowsrawinput_h_ -extern bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags, bool force); +extern bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags); extern bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled); extern bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled); extern bool WIN_SetRawKeyboardFlag_NoHotkeys(SDL_VideoDevice *_this, bool enabled); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index edb28e838b..019a23c2d6 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -671,7 +671,9 @@ void WIN_VideoQuit(SDL_VideoDevice *_this) SDL_RemoveHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL); SDL_RemoveHintCallback(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, UpdateWindowFrameUsableWhileCursorHidden, NULL); - WIN_SetRawInputEnabled(_this, 0, true); + WIN_SetRawMouseEnabled(_this, false); + WIN_SetRawKeyboardEnabled(_this, false); + WIN_SetRawInputEnabled(_this, 0); WIN_QuitGameInput(_this); #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)