mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
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:
1 parent
9f4f6a0ffc
commit
963abf2e99
1 file changed
+37
-4
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user