mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 16:51:31 +00:00
[client] cursor: pace composited redraws
Coalesce pointer updates to the nominal output cadence instead of waking the renderer for every transport packet. Keep request and presented generations separate so updates racing a render receive a trailing swap. Let JIT consume cursor changes on display callbacks to preserve VRR cadence, and use a deadline worker for non-JIT rendering. Give normal source frames a short grace period so an on-rate feed absorbs the cursor update instead of producing an extra swap. Repaint visibility and cursor-content changes even when movement redraws are disabled, including the transition that hides the cursor.
This commit is contained in:
@@ -398,7 +398,7 @@ static struct Option options[] =
|
|||||||
{
|
{
|
||||||
.module = "input",
|
.module = "input",
|
||||||
.name = "mouseRedraw",
|
.name = "mouseRedraw",
|
||||||
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
|
.description = "Mouse movements trigger display-rate redraws",
|
||||||
.type = OPTION_TYPE_BOOL,
|
.type = OPTION_TYPE_BOOL,
|
||||||
.value.x_bool = true,
|
.value.x_bool = true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,8 +85,22 @@ static int renderThread(void * unused);
|
|||||||
|
|
||||||
static LGEvent *e_startup = NULL;
|
static LGEvent *e_startup = NULL;
|
||||||
static LGEvent *e_spice = NULL;
|
static LGEvent *e_spice = NULL;
|
||||||
|
static LGEvent *e_cursorRepaint = NULL;
|
||||||
static LGThread *t_spice = NULL;
|
static LGThread *t_spice = NULL;
|
||||||
static LGThread *t_render = NULL;
|
static LGThread *t_render = NULL;
|
||||||
|
static LGThread *t_cursorRepaint = NULL;
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
LG_Lock lock;
|
||||||
|
uint64_t requestedSerial;
|
||||||
|
uint64_t presentedSerial;
|
||||||
|
uint64_t lastRenderTime;
|
||||||
|
bool rendering;
|
||||||
|
bool wakeArmed;
|
||||||
|
bool lastRenderCursorOnly;
|
||||||
|
}
|
||||||
|
l_cursorRepaint;
|
||||||
|
|
||||||
_Atomic(enum RunState) p_appState = APP_STATE_RUNNING;
|
_Atomic(enum RunState) p_appState = APP_STATE_RUNNING;
|
||||||
|
|
||||||
@@ -660,6 +674,125 @@ static void preSwapCallback(void * udata)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint64_t cursorRepaintPeriod(void)
|
||||||
|
{
|
||||||
|
uint64_t period = g_state.overlayFrameTime;
|
||||||
|
uint64_t outputPeriod;
|
||||||
|
if (g_state.ds->getFramePeriod &&
|
||||||
|
g_state.ds->getFramePeriod(&outputPeriod) && outputPeriod)
|
||||||
|
period = outputPeriod;
|
||||||
|
|
||||||
|
if (overlaySplash_isFading())
|
||||||
|
period = max(period, g_state.overlayFrameTime);
|
||||||
|
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cursorRepaintNeeded(void)
|
||||||
|
{
|
||||||
|
LG_LOCK(l_cursorRepaint.lock);
|
||||||
|
const bool result =
|
||||||
|
l_cursorRepaint.requestedSerial != l_cursorRepaint.presentedSerial &&
|
||||||
|
!overlaySplash_isFading();
|
||||||
|
LG_UNLOCK(l_cursorRepaint.lock);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cursorRepaintRequest(void)
|
||||||
|
{
|
||||||
|
LG_LOCK(l_cursorRepaint.lock);
|
||||||
|
const bool wasDirty =
|
||||||
|
l_cursorRepaint.requestedSerial != l_cursorRepaint.presentedSerial;
|
||||||
|
++l_cursorRepaint.requestedSerial;
|
||||||
|
LG_UNLOCK(l_cursorRepaint.lock);
|
||||||
|
|
||||||
|
if (!g_state.jitRender && !wasDirty)
|
||||||
|
lgSignalEvent(e_cursorRepaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t cursorRepaintRenderBegin(bool * cursorWake)
|
||||||
|
{
|
||||||
|
LG_LOCK(l_cursorRepaint.lock);
|
||||||
|
const uint64_t serial = l_cursorRepaint.requestedSerial;
|
||||||
|
l_cursorRepaint.rendering = true;
|
||||||
|
|
||||||
|
*cursorWake = l_cursorRepaint.wakeArmed;
|
||||||
|
if (l_cursorRepaint.wakeArmed)
|
||||||
|
l_cursorRepaint.wakeArmed = false;
|
||||||
|
|
||||||
|
LG_UNLOCK(l_cursorRepaint.lock);
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cursorRepaintRenderEnd(
|
||||||
|
uint64_t serial, uint64_t renderTime, bool success, bool cursorOnly)
|
||||||
|
{
|
||||||
|
LG_LOCK(l_cursorRepaint.lock);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
l_cursorRepaint.presentedSerial = serial;
|
||||||
|
l_cursorRepaint.lastRenderTime = renderTime;
|
||||||
|
l_cursorRepaint.lastRenderCursorOnly = cursorOnly;
|
||||||
|
}
|
||||||
|
l_cursorRepaint.rendering = false;
|
||||||
|
|
||||||
|
const bool wake = success && !g_state.jitRender &&
|
||||||
|
l_cursorRepaint.requestedSerial != l_cursorRepaint.presentedSerial;
|
||||||
|
LG_UNLOCK(l_cursorRepaint.lock);
|
||||||
|
|
||||||
|
if (wake)
|
||||||
|
lgSignalEvent(e_cursorRepaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cursorRepaintThread(void * unused)
|
||||||
|
{
|
||||||
|
while (app_getState() != APP_STATE_SHUTDOWN)
|
||||||
|
{
|
||||||
|
lgWaitEvent(e_cursorRepaint, TIMEOUT_INFINITE);
|
||||||
|
|
||||||
|
while (app_getState() != APP_STATE_SHUTDOWN)
|
||||||
|
{
|
||||||
|
uint64_t waitTime = 0;
|
||||||
|
|
||||||
|
LG_LOCK(l_cursorRepaint.lock);
|
||||||
|
if (l_cursorRepaint.requestedSerial ==
|
||||||
|
l_cursorRepaint.presentedSerial ||
|
||||||
|
l_cursorRepaint.rendering || l_cursorRepaint.wakeArmed)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(l_cursorRepaint.lock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t now = nanotime();
|
||||||
|
const uint64_t period = cursorRepaintPeriod();
|
||||||
|
/* Give an on-rate source frame enough time to absorb this cursor update.
|
||||||
|
* Cursor-only rendering retains the full output cadence once needed. */
|
||||||
|
const uint64_t grace =
|
||||||
|
l_cursorRepaint.lastRenderCursorOnly ? 0 : period / 4;
|
||||||
|
const uint64_t deadline =
|
||||||
|
l_cursorRepaint.lastRenderTime + period + grace;
|
||||||
|
if (l_cursorRepaint.lastRenderTime && now < deadline)
|
||||||
|
waitTime = deadline - now;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
l_cursorRepaint.wakeArmed = true;
|
||||||
|
app_invalidateWindow(false);
|
||||||
|
}
|
||||||
|
LG_UNLOCK(l_cursorRepaint.lock);
|
||||||
|
|
||||||
|
if (!waitTime)
|
||||||
|
break;
|
||||||
|
|
||||||
|
struct timespec deadlineTime;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &deadlineTime);
|
||||||
|
tsAdd(&deadlineTime, waitTime);
|
||||||
|
lgWaitEventAbs(e_cursorRepaint, &deadlineTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int renderThread(void * unused)
|
static int renderThread(void * unused)
|
||||||
{
|
{
|
||||||
if (!RENDERER(renderStartup, g_state.useDMA))
|
if (!RENDERER(renderStartup, g_state.useDMA))
|
||||||
@@ -731,8 +864,10 @@ static int renderThread(void * unused)
|
|||||||
(!g_state.lastRenderTimeValid || !periodKnown ||
|
(!g_state.lastRenderTimeValid || !periodKnown ||
|
||||||
elapsed + outputPeriod >= g_state.overlayFrameTime);
|
elapsed + outputPeriod >= g_state.overlayFrameTime);
|
||||||
const bool clientWake = lgResetEvent(g_state.frameEvent);
|
const bool clientWake = lgResetEvent(g_state.frameEvent);
|
||||||
|
const bool cursorRender = cursorRepaintNeeded();
|
||||||
|
|
||||||
if (!clientWake && !forceRender && !pending && !overlayRender)
|
if (!clientWake && !forceRender && !pending && !overlayRender &&
|
||||||
|
!cursorRender)
|
||||||
{
|
{
|
||||||
if (g_state.ds->skipFrame)
|
if (g_state.ds->skipFrame)
|
||||||
g_state.ds->skipFrame();
|
g_state.ds->skipFrame();
|
||||||
@@ -752,6 +887,10 @@ static int renderThread(void * unused)
|
|||||||
lgWaitEventAbs(g_state.frameEvent, &time);
|
lgWaitEventAbs(g_state.frameEvent, &time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cursorWake;
|
||||||
|
const uint64_t cursorSerial = cursorRepaintRenderBegin(&cursorWake);
|
||||||
|
const uint64_t renderStart = nanotime();
|
||||||
|
|
||||||
frameTimingPublishReady();
|
frameTimingPublishReady();
|
||||||
|
|
||||||
int resize = atomic_load(&g_state.lgrResize);
|
int resize = atomic_load(&g_state.lgrResize);
|
||||||
@@ -815,10 +954,14 @@ static int renderThread(void * unused)
|
|||||||
invalidate, preSwapCallback, NULL, &rendererTiming)))
|
invalidate, preSwapCallback, NULL, &rendererTiming)))
|
||||||
{
|
{
|
||||||
LG_UNLOCK(g_state.lgrLock);
|
LG_UNLOCK(g_state.lgrLock);
|
||||||
|
cursorRepaintRenderEnd(cursorSerial, renderStart, false, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const uint64_t renderEnd = nanotime();
|
const uint64_t renderEnd = nanotime();
|
||||||
LG_UNLOCK(g_state.lgrLock);
|
LG_UNLOCK(g_state.lgrLock);
|
||||||
|
cursorRepaintRenderEnd(cursorSerial, renderStart, true,
|
||||||
|
cursorWake &&
|
||||||
|
rendererTiming.frameToken == LG_RENDERER_FRAME_TOKEN_NONE);
|
||||||
|
|
||||||
if (!g_state.jitRender)
|
if (!g_state.jitRender)
|
||||||
frameScheduler_observeCadence();
|
frameScheduler_observeCadence();
|
||||||
@@ -895,6 +1038,7 @@ static int renderThread(void * unused)
|
|||||||
int main_cursorThread(void * unused)
|
int main_cursorThread(void * unused)
|
||||||
{
|
{
|
||||||
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
||||||
|
uint32_t cursorWhiteLevel = 0;
|
||||||
|
|
||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
|
|
||||||
@@ -916,10 +1060,8 @@ int main_cursorThread(void * unused)
|
|||||||
(g_cursor.draw || !g_params.useSpiceInput),
|
(g_cursor.draw || !g_params.useSpiceInput),
|
||||||
g_cursor.guest.x, g_cursor.guest.y,
|
g_cursor.guest.x, g_cursor.guest.y,
|
||||||
g_cursor.guest.hx, g_cursor.guest.hy);
|
g_cursor.guest.hx, g_cursor.guest.hy);
|
||||||
/* The active fade already schedules a render. Coalesce pointer
|
if (!g_state.stopVideo)
|
||||||
* changes into it instead of exposing rapid single-buffer redraws. */
|
cursorRepaintRequest();
|
||||||
if (!g_state.stopVideo && !overlaySplash_isFading())
|
|
||||||
lgSignalEvent(g_state.frameEvent);
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -931,6 +1073,9 @@ int main_cursorThread(void * unused)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool wasRendered = g_cursor.guest.visible &&
|
||||||
|
(g_cursor.draw || !g_params.useSpiceInput);
|
||||||
|
|
||||||
if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID)
|
if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID)
|
||||||
g_cursor.guest.visible =
|
g_cursor.guest.visible =
|
||||||
pointer.flags & LG_TRANSPORT_POINTER_VISIBLE;
|
pointer.flags & LG_TRANSPORT_POINTER_VISIBLE;
|
||||||
@@ -964,10 +1109,16 @@ int main_cursorThread(void * unused)
|
|||||||
g_state.lgr->ops.onMouseColorTransform(g_state.lgr,
|
g_state.lgr->ops.onMouseColorTransform(g_state.lgr,
|
||||||
pointer.colorTransform);
|
pointer.colorTransform);
|
||||||
|
|
||||||
if ((pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID) &&
|
const bool whiteLevelChanged =
|
||||||
pointer.sdrWhiteLevel && g_state.lgr->ops.onMouseWhiteLevel)
|
(pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID) &&
|
||||||
|
pointer.sdrWhiteLevel && pointer.sdrWhiteLevel != cursorWhiteLevel &&
|
||||||
|
g_state.lgr->ops.onMouseWhiteLevel;
|
||||||
|
if (whiteLevelChanged)
|
||||||
|
{
|
||||||
g_state.lgr->ops.onMouseWhiteLevel(g_state.lgr,
|
g_state.lgr->ops.onMouseWhiteLevel(g_state.lgr,
|
||||||
pointer.sdrWhiteLevel);
|
pointer.sdrWhiteLevel);
|
||||||
|
cursorWhiteLevel = pointer.sdrWhiteLevel;
|
||||||
|
}
|
||||||
|
|
||||||
if (pointer.flags & LG_TRANSPORT_POINTER_POSITION)
|
if (pointer.flags & LG_TRANSPORT_POINTER_POSITION)
|
||||||
{
|
{
|
||||||
@@ -990,11 +1141,16 @@ int main_cursorThread(void * unused)
|
|||||||
g_cursor.guest.x, g_cursor.guest.y,
|
g_cursor.guest.x, g_cursor.guest.y,
|
||||||
g_cursor.guest.hx, g_cursor.guest.hy);
|
g_cursor.guest.hx, g_cursor.guest.hy);
|
||||||
|
|
||||||
if ((g_params.mouseRedraw ||
|
const bool isRendered = g_cursor.guest.visible &&
|
||||||
(pointer.flags & LG_TRANSPORT_POINTER_COLOR_TRANSFORM)) &&
|
(g_cursor.draw || !g_params.useSpiceInput);
|
||||||
g_cursor.guest.visible && !g_state.stopVideo &&
|
const bool contentChanged =
|
||||||
!overlaySplash_isFading())
|
(pointer.flags & (LG_TRANSPORT_POINTER_SHAPE |
|
||||||
lgSignalEvent(g_state.frameEvent);
|
LG_TRANSPORT_POINTER_COLOR_TRANSFORM)) || whiteLevelChanged;
|
||||||
|
if (!g_state.stopVideo &&
|
||||||
|
(wasRendered != isRendered ||
|
||||||
|
((wasRendered || isRendered) &&
|
||||||
|
(g_params.mouseRedraw || contentChanged))))
|
||||||
|
cursorRepaintRequest();
|
||||||
|
|
||||||
g_state.transportOps->releasePointer(g_state.transport, &pointer);
|
g_state.transportOps->releasePointer(g_state.transport, &pointer);
|
||||||
}
|
}
|
||||||
@@ -1713,6 +1869,7 @@ static int transportSessionProbe(void * opaque)
|
|||||||
|
|
||||||
static int lg_run(void)
|
static int lg_run(void)
|
||||||
{
|
{
|
||||||
|
LG_LOCK_INIT(l_cursorRepaint.lock);
|
||||||
frameTimingInit();
|
frameTimingInit();
|
||||||
|
|
||||||
#ifdef ENABLE_TESTS
|
#ifdef ENABLE_TESTS
|
||||||
@@ -1967,6 +2124,22 @@ static int lg_run(void)
|
|||||||
// interactivity.
|
// interactivity.
|
||||||
g_state.overlayFrameTime = min(g_state.frameTime, 1000000000ULL / 60ULL);
|
g_state.overlayFrameTime = min(g_state.frameTime, 1000000000ULL / 60ULL);
|
||||||
|
|
||||||
|
if (!g_state.jitRender)
|
||||||
|
{
|
||||||
|
if (!(e_cursorRepaint = lgCreateEvent(true, 0)))
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("failed to create the cursor repaint event");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lgCreateThread("cursorRepaint", cursorRepaintThread, NULL,
|
||||||
|
&t_cursorRepaint))
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("cursor repaint thread creation failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keybind_commonRegister();
|
keybind_commonRegister();
|
||||||
|
|
||||||
if (g_state.jitRender)
|
if (g_state.jitRender)
|
||||||
@@ -2214,6 +2387,9 @@ static void lg_shutdown(void)
|
|||||||
app_setState(APP_STATE_SHUTDOWN);
|
app_setState(APP_STATE_SHUTDOWN);
|
||||||
frameScheduler_stop();
|
frameScheduler_stop();
|
||||||
|
|
||||||
|
if (e_cursorRepaint)
|
||||||
|
lgSignalEvent(e_cursorRepaint);
|
||||||
|
|
||||||
if (t_spice)
|
if (t_spice)
|
||||||
lgJoinThread(t_spice, NULL);
|
lgJoinThread(t_spice, NULL);
|
||||||
|
|
||||||
@@ -2226,6 +2402,19 @@ static void lg_shutdown(void)
|
|||||||
lgJoinThread(t_render, NULL);
|
lgJoinThread(t_render, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (t_cursorRepaint)
|
||||||
|
{
|
||||||
|
lgJoinThread(t_cursorRepaint, NULL);
|
||||||
|
t_cursorRepaint = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e_cursorRepaint)
|
||||||
|
{
|
||||||
|
lgFreeEvent(e_cursorRepaint);
|
||||||
|
e_cursorRepaint = NULL;
|
||||||
|
}
|
||||||
|
LG_LOCK_FREE(l_cursorRepaint.lock);
|
||||||
|
|
||||||
if (g_state.transportOps)
|
if (g_state.transportOps)
|
||||||
g_state.transportOps->destroy(&g_state.transport);
|
g_state.transportOps->destroy(&g_state.transport);
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ All command line options
|
|||||||
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
||||||
| input:rawMouse | | no | Use RAW mouse input when in capture mode (good for gaming) |
|
| input:rawMouse | | no | Use RAW mouse input when in capture mode (good for gaming) |
|
||||||
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
||||||
| input:mouseRedraw | | yes | Mouse movements trigger redraws (ignores FPS minimum) |
|
| input:mouseRedraw | | yes | Mouse movements trigger display-rate redraws |
|
||||||
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
||||||
| input:mouseTrace | | no | Enable mouse input diagnostics |
|
| input:mouseTrace | | no | Enable mouse input diagnostics |
|
||||||
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
|
||||||
|
|||||||
Reference in New Issue
Block a user