Skip to content

Commit

Permalink
Fix rawmouse wrong timestamp (#11553)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
expikr authored Dec 4, 2024
1 parent 830b132 commit d320d71
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 23 deletions.
32 changes: 12 additions & 20 deletions src/video/windows/SDL_windowsevents.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/video/windows/SDL_windowsevents.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 9 additions & 2 deletions src/video/windows/SDL_windowsrawinput.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit d320d71

Please sign in to comment.