From d320d7143d160e7b7446f324df46afcc36f3315f Mon Sep 17 00:00:00 2001
From: expikr <[EMAIL REDACTED]>
Date: Wed, 4 Dec 2024 12:40:34 +0800
Subject: [PATCH] Fix rawmouse wrong timestamp (#11553)
Currently, the rawinput thread incorrectly spreads the timestamps over idle time if the poll interval is less than 100ms, and abruptly switches to lumping all accumulated inputs to happen simultaneously if it exceeds 100ms.
This means that any game which implements retroactive event handling based on timestamps will jarringly snap between the two polar opposite extremes of incorrect behaviour.
This PR replaces the arbitrary 100ms threshold with logic based on measuring the idle start and end time.
If the thread idled for more than 125000 nanoseconds, it is considered to have not had any input in its queue before it entered idle, and the events are spread over the interval between thread wake-up and pump finish, with the final input aligned to the pump finish time.
If the thread idled for less than 125000 nanoseconds, it is considered to have events entered at some point between last pump finish and thread entering sleep, and the events are spread over the full pump-to-pump interval.
---
src/video/windows/SDL_windowsevents.c | 32 ++++++++++---------------
src/video/windows/SDL_windowsevents.h | 2 +-
src/video/windows/SDL_windowsrawinput.c | 11 +++++++--
3 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index b78422e8ae43a..c51ec9202122f 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -740,12 +740,12 @@ static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_VideoData *data, HA
SDL_SendKeyboardKey(timestamp, keyboardID, rawcode, code, down);
}
-void WIN_PollRawInput(SDL_VideoDevice *_this)
+void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start)
{
SDL_VideoData *data = _this->internal;
UINT size, i, count, total = 0;
RAWINPUT *input;
- Uint64 now;
+ Uint64 poll_finish;
if (data->rawinput_offset == 0) {
BOOL isWow64;
@@ -762,6 +762,7 @@ void WIN_PollRawInput(SDL_VideoDevice *_this)
for (;;) {
size = data->rawinput_size - (UINT)((BYTE *)input - data->rawinput);
count = GetRawInputBuffer(input, &size, sizeof(RAWINPUTHEADER));
+ poll_finish = SDL_GetTicksNS();
if (count == 0 || count == (UINT)-1) {
if (!data->rawinput || (count == (UINT)-1 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
const UINT RAWINPUT_BUFFER_SIZE_INCREMENT = 96; // 2 64-bit raw mouse packets
@@ -785,37 +786,28 @@ void WIN_PollRawInput(SDL_VideoDevice *_this)
}
}
- now = SDL_GetTicksNS();
if (total > 0) {
- Uint64 mouse_timestamp, mouse_increment;
- Uint64 delta = (now - data->last_rawinput_poll);
- UINT total_mouse = 0;
+ Uint64 delta = poll_finish - poll_start;
+ UINT mouse_total = 0;
for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
if (input->header.dwType == RIM_TYPEMOUSE) {
- ++total_mouse;
+ mouse_total += 1;
}
}
- if (total_mouse > 1 && delta <= SDL_MS_TO_NS(100)) {
- // We'll spread these events over the time since the last poll
- mouse_timestamp = data->last_rawinput_poll;
- mouse_increment = delta / total_mouse;
- } else {
- // Do we want to track the update rate per device?
- mouse_timestamp = now;
- mouse_increment = 0;
- }
+ int mouse_index = 0;
for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
if (input->header.dwType == RIM_TYPEMOUSE) {
+ mouse_index += 1; // increment first so that it starts at one
RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset);
- mouse_timestamp += mouse_increment;
- WIN_HandleRawMouseInput(mouse_timestamp, data, input->header.hDevice, rawmouse);
+ Uint64 time = poll_finish - (delta * (mouse_total - mouse_index)) / mouse_total;
+ WIN_HandleRawMouseInput(time, data, input->header.hDevice, rawmouse);
} else if (input->header.dwType == RIM_TYPEKEYBOARD) {
RAWKEYBOARD *rawkeyboard = (RAWKEYBOARD *)((BYTE *)input + data->rawinput_offset);
- WIN_HandleRawKeyboardInput(now, data, input->header.hDevice, rawkeyboard);
+ WIN_HandleRawKeyboardInput(poll_finish, data, input->header.hDevice, rawkeyboard);
}
}
}
- data->last_rawinput_poll = now;
+ data->last_rawinput_poll = poll_finish;
}
#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h
index a48b8aaafd60d..8d4e762a0ebb9 100644
--- a/src/video/windows/SDL_windowsevents.h
+++ b/src/video/windows/SDL_windowsevents.h
@@ -30,7 +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_PollRawInput(SDL_VideoDevice *_this);
+extern void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start);
extern void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check);
extern void WIN_PumpEvents(SDL_VideoDevice *_this);
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 9a003af30ea87..1d3a9e98c1939 100644
--- a/src/video/windows/SDL_windowsrawinput.c
+++ b/src/video/windows/SDL_windowsrawinput.c
@@ -93,14 +93,21 @@ static DWORD WINAPI WIN_RawInputThread(LPVOID param)
SetEvent(data->ready_event);
while (!data->done) {
- if (MsgWaitForMultipleObjects(1, &data->done_event, FALSE, INFINITE, QS_RAWINPUT) != (WAIT_OBJECT_0 + 1)) {
+ Uint64 idle_begin = SDL_GetTicksNS();
+ DWORD result = MsgWaitForMultipleObjects(1, &data->done_event, FALSE, INFINITE, QS_RAWINPUT);
+ Uint64 idle_end = SDL_GetTicksNS();
+ if (result != (WAIT_OBJECT_0 + 1)) {
break;
}
// Clear the queue status so MsgWaitForMultipleObjects() will wait again
(void)GetQueueStatus(QS_RAWINPUT);
- WIN_PollRawInput(_this);
+ 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);
}
devices[0].dwFlags |= RIDEV_REMOVE;