winraw: resolve the deferred cursor position once per frame

winraw_update_mouse_state() called GetCursorPos() from the wndproc, so
once per raw input report. GetCursorPos has no user-mode path on any
Windows version - it is 'mov edx,1; jmp win32u!NtUserGetCursorPos' on
24H2, an NtUserCallOneParam syscall on XP, and a 32->16 FT_Thunk on 9x -
so that is an unconditional kernel transition per report. A 1000 Hz
mouse at 60 fps makes about sixteen of them per frame where one would
do. ScreenToClient on the following line is free everywhere: it is two
subtractions against the client-side window mapping.

Only the value in place when winraw_poll() takes the frame snapshot is
ever read. mouse->x is write-only between snapshots in this branch -
the MOUSE_MOVE_ABSOLUTE branch and the non-cursor branch are the only
readers and neither runs here - so every query but the last is
overwritten unobserved.

Set a per-mouse pos_pending flag instead and resolve it in
winraw_poll(), at most once per frame however many reports asked and
however many mice are attached. The sample is taken at poll time rather
than at the last report, so it is fresher, not staler. Moving the call
across threads is safe: under video_threaded the wndproc runs on the
video thread while winraw_poll runs on the main thread, and
NtUserGetCursorPos reads global state with no message-queue or TEB
involvement, unlike GetKeyState.

pos_pending is drained with InterlockedExchange, matching how dlt_x and
whl_u already cross the same thread boundary, and calloc zeroes it at
device enumeration.

One case changes. A frame whose reports switch from wanting the cursor
to not wanting it - the menu closing between two reports - now advances
from the previous frame's position rather than from a cursor sample
taken mid-frame. The divergence is bounded by one frame of pointer
motion and only occurs on the transition. Frames that consistently want
the cursor, which is the normal case, are unaffected apart from the
fresher sample.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-01 06:24:13 +02:00
1 parent 9f4f6a0ffc
commit 963abf2e99
1 file changed
+37 -4
+37 -4
View File
@@ -63,6 +63,9 @@ typedef struct
HANDLE hnd;
LONG x, y, dlt_x, dlt_y;
LONG whl_u, whl_d;
/* Set by the wndproc when this mouse needs its position taken from
* the system cursor, drained once per frame by winraw_poll(). */
LONG pos_pending;
int device;
uint8_t flags;
} winraw_mouse_t;
@@ -521,13 +524,20 @@ static void winraw_update_mouse_state(winraw_input_t *wr,
InterlockedExchangeAdd(&mouse->dlt_x, state->lLastX);
InterlockedExchangeAdd(&mouse->dlt_y, state->lLastY);
if (!GetCursorPos(&crs_pos))
RARCH_DBG("[WinRaw] GetCursorPos failed with error %lu.\n", GetLastError());
else if (!ScreenToClient((HWND)video_driver_window_get(), &crs_pos))
RARCH_DBG("[WinRaw] ScreenToClient failed with error %lu.\n", GetLastError());
/* Defer the cursor query to winraw_poll(). GetCursorPos() is an
* unconditional kernel transition on every Windows version, and
* this runs once per raw input report - about sixteen times per
* frame with a 1000 Hz mouse at 60 fps. Only the value in place
* at the frame snapshot is ever read, so resolving it once per
* frame gives the same result from a fresher sample. */
InterlockedExchange(&mouse->pos_pending, 1);
}
else
{
/* This branch establishes the position itself, so any deferred
* query from an earlier report in this frame is superseded. */
InterlockedExchange(&mouse->pos_pending, 0);
/* Handle different sensitivity for lightguns */
if (mouse->device == RETRO_DEVICE_LIGHTGUN)
{
@@ -769,6 +779,8 @@ error:
static void winraw_poll(void *data)
{
unsigned i;
POINT crs_pos = {0, 0};
bool crs_pos_valid = false;
winraw_input_t *wr = (winraw_input_t*)data;
/* Sync coordinates when window regains focus */
@@ -812,6 +824,27 @@ static void winraw_poll(void *data)
if (!winraw_focus)
g_mice[i].flags = 0;
/* Resolve a deferred cursor position, at most once per frame no
* matter how many reports asked for it or how many mice did. */
if (InterlockedExchange(&g_mice[i].pos_pending, 0))
{
if (!crs_pos_valid)
{
HWND wnd = (HWND)video_driver_window_get();
if (!GetCursorPos(&crs_pos))
RARCH_DBG("[WinRaw] GetCursorPos failed with error %lu.\n", GetLastError());
else if (!wnd || !ScreenToClient(wnd, &crs_pos))
RARCH_DBG("[WinRaw] ScreenToClient failed with error %lu.\n", GetLastError());
else
crs_pos_valid = true;
}
if (crs_pos_valid)
{
g_mice[i].x = crs_pos.x;
g_mice[i].y = crs_pos.y;
}
}
wr->mice[i].x = g_mice[i].x;
wr->mice[i].y = g_mice[i].y;
wr->mice[i].dlt_x = InterlockedExchange(&g_mice[i].dlt_x, 0);