From 43e90c7b1cc73745657d607f685ccabf7ca21e47 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Mon, 16 Mar 2026 16:24:35 -0700 Subject: [PATCH] Make changing raw input mode on Windows a very fast operation Previously we would spin up and shut down a thread every time we changed raw input mode, which can take several ms. Now we'll just put the raw input thread to sleep when disabling raw input and wake it up when enabling raw input. --- src/video/windows/SDL_windowsevents.c | 4 +- src/video/windows/SDL_windowsevents.h | 2 +- src/video/windows/SDL_windowsrawinput.c | 91 +++++++++++++++++-------- src/video/windows/SDL_windowsrawinput.h | 1 + src/video/windows/SDL_windowsvideo.c | 3 +- 5 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 19f49e9b36..ea57c1ca42 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) +void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start, bool process_input) { SDL_VideoData *data = _this->internal; UINT size, i, count, total = 0; @@ -832,7 +832,7 @@ void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start) } } - if (total > 0) { + if (total > 0 && process_input) { 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 45f5527dc6..21bd7a420c 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); +extern void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start, bool process_input); 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 cada2b6d63..3c5d80ab2b 100644 --- a/src/video/windows/SDL_windowsrawinput.c +++ b/src/video/windows/SDL_windowsrawinput.c @@ -40,13 +40,15 @@ typedef struct { bool done; + bool paused; Uint32 flags; HANDLE ready_event; - HANDLE done_event; + HANDLE signal_event; HANDLE thread; } RawInputThreadData; static RawInputThreadData thread_data = { + false, false, 0, INVALID_HANDLE_VALUE, @@ -104,22 +106,34 @@ static DWORD WINAPI WIN_RawInputThread(LPVOID param) // Tell the parent we're ready to go! SetEvent(data->ready_event); - 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->done_event, FALSE, INFINITE, QS_RAWINPUT) == WAIT_OBJECT_0 + 1))) { + 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_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); + WIN_PollRawInput(_this, poll_start, true); - // Reset idle_begin for the next go-around - idle_begin = SDL_GetTicksNS(); + // 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); + } } if (_this->internal->raw_input_fake_pen_id) { @@ -141,8 +155,9 @@ 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->done_event); + SetEvent(data->signal_event); WaitForSingleObject(data->thread, 3000); CloseHandle(data->thread); data->thread = INVALID_HANDLE_VALUE; @@ -153,16 +168,26 @@ static void CleanupRawInputThreadData(RawInputThreadData *data) data->ready_event = INVALID_HANDLE_VALUE; } - if (data->done_event != INVALID_HANDLE_VALUE) { - CloseHandle(data->done_event); - data->done_event = INVALID_HANDLE_VALUE; + if (data->signal_event != INVALID_HANDLE_VALUE) { + CloseHandle(data->signal_event); + data->signal_event = INVALID_HANDLE_VALUE; } } -static bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags) +bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags, bool force) { 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) { @@ -176,8 +201,8 @@ static bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags) } thread_data.done = false; - thread_data.done_event = CreateEvent(NULL, FALSE, FALSE, NULL); - if (thread_data.done_event == INVALID_HANDLE_VALUE) { + thread_data.signal_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (thread_data.signal_event == INVALID_HANDLE_VALUE) { WIN_SetError("CreateEvent"); goto done; } @@ -216,15 +241,20 @@ static bool WIN_UpdateRawInputEnabled(SDL_VideoDevice *_this) } if (data->raw_keyboard_enabled) { flags |= ENABLE_RAW_KEYBOARD_INPUT; - if (data->raw_keyboard_flag_nohotkeys) { - flags |= RAW_KEYBOARD_FLAG_NOHOTKEYS; - } - if (data->raw_keyboard_flag_inputsink) { - flags |= RAW_KEYBOARD_FLAG_INPUTSINK; - } + } + if (data->raw_keyboard_flag_nohotkeys) { + flags |= RAW_KEYBOARD_FLAG_NOHOTKEYS; + } + if (data->raw_keyboard_flag_inputsink) { + flags |= RAW_KEYBOARD_FLAG_INPUTSINK; } if (flags != data->raw_input_enabled) { - if (WIN_SetRawInputEnabled(_this, flags)) { + 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)) { data->raw_input_enabled = flags; } else { return false; @@ -308,6 +338,11 @@ bool WIN_SetRawKeyboardFlag_Inputsink(SDL_VideoDevice *_this, bool enabled) #else +bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags, bool force) +{ + return SDL_Unsupported(); +} + bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled) { return SDL_Unsupported(); diff --git a/src/video/windows/SDL_windowsrawinput.h b/src/video/windows/SDL_windowsrawinput.h index 2ede2eba44..e970666a9a 100644 --- a/src/video/windows/SDL_windowsrawinput.h +++ b/src/video/windows/SDL_windowsrawinput.h @@ -23,6 +23,7 @@ #ifndef SDL_windowsrawinput_h_ #define SDL_windowsrawinput_h_ +extern bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags, bool force); 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 c6997dbf65..edb28e838b 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -671,8 +671,7 @@ 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_SetRawMouseEnabled(_this, false); - WIN_SetRawKeyboardEnabled(_this, false); + WIN_SetRawInputEnabled(_this, 0, true); WIN_QuitGameInput(_this); #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)