From 3d1064001da7d86c8f65e3925637d7c214aaeb88 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 6 Aug 2026 16:17:23 +1000 Subject: [PATCH] [client] scheduler: qualify presentation feedback Track cadence, interruption, and calibration wake reasons independently across X11 and Wayland. Feed phase correction back only when a pure cadence wake renders the exact frame that was queued at that wake. Propagate EGL swap results through the display-server boundary. Reject failed presentations and keep pending Wayland HDR descriptions inactive until the preceding surface commit succeeds. --- client/displayservers/Wayland/gl.c | 14 +++++-- client/displayservers/Wayland/wayland.h | 8 ++-- client/displayservers/Wayland/window.c | 19 +++++++--- client/displayservers/X11/x11.c | 32 +++++++++++----- client/displayservers/X11/x11.h | 1 + client/include/app.h | 3 +- client/include/eglutil.h | 4 +- client/include/interface/displayserver.h | 19 +++++++--- client/renderers/EGL/egl.c | 12 ++++-- client/src/app.c | 5 ++- client/src/eglutil.c | 11 ++---- client/src/main.c | 48 +++++++++++++++++------- 12 files changed, 119 insertions(+), 57 deletions(-) diff --git a/client/displayservers/Wayland/gl.c b/client/displayservers/Wayland/gl.c index bef88d87..3719c805 100644 --- a/client/displayservers/Wayland/gl.c +++ b/client/displayservers/Wayland/gl.c @@ -228,8 +228,11 @@ static const struct wp_image_description_v1_listener hdrImageDescListener = .ready2 = hdrImageDescReadyV2, }; -void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) +bool waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, + const struct Rect * damage, int count) { + bool result = false; + // EGL presentation sends a batch of Wayland requests ending in a surface // commit. A concurrent commit would apply a partial batch and, when explicit // sync is active, omit one of the required acquire/release timeline points. @@ -248,8 +251,10 @@ void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct waylandPresentationFrame(); applyHDRPending(); - swapWithDamage(&wlWm.swapWithDamage, display, surface, damage, count); - activateReadyHDRImageDesc(); + result = swapWithDamage( + &wlWm.swapWithDamage, display, surface, damage, count); + if (result) + activateReadyHDRImageDesc(); }); if (wlWm.needsResize) @@ -305,6 +310,7 @@ void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct } wlWm.desktop->shellAckConfigureIfNeeded(); + return result; } #endif @@ -750,6 +756,6 @@ void waylandGLSetSwapInterval(int interval) void waylandGLSwapBuffers(void) { - waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0); + (void)waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0); } #endif diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h index f3992f30..44fd8bb2 100644 --- a/client/displayservers/Wayland/wayland.h +++ b/client/displayservers/Wayland/wayland.h @@ -267,7 +267,8 @@ struct WaylandDSState enum WaylandHDRPendingAction pendingHDRAction; struct WaylandHDRParameters pendingHDR; - LGEvent * frameEvent; + _Atomic(unsigned) frameEventFlags; + LGEvent * frameEvent; struct wl_list poll; // WaylandPoll::link struct wl_list pollFree; // WaylandPoll::link @@ -333,7 +334,8 @@ void waylandCursorScaleChange(void); #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) bool waylandEGLInit(int w, int h); EGLDisplay waylandGetEGLDisplay(void); -void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); +bool waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, + const struct Rect * damage, int count); #endif #ifdef ENABLE_EGL @@ -421,7 +423,7 @@ void waylandWindowFree(void); void waylandWindowUpdateScale(void); void waylandSetWindowSize(int x, int y); bool waylandIsValidPointerPos(int x, int y); -bool waylandWaitFrame(void); +LG_DSWaitFrameResult waylandWaitFrame(void); void waylandSkipFrame(void); void waylandStopWaitFrame(void); void waylandNeedsResize(void); diff --git a/client/displayservers/Wayland/window.c b/client/displayservers/Wayland/window.c index a09edd6b..2bd367c2 100644 --- a/client/displayservers/Wayland/window.c +++ b/client/displayservers/Wayland/window.c @@ -113,6 +113,13 @@ static const struct wl_surface_listener wlSurfaceListener = { .leave = wlSurfaceLeaveHandler, }; +static void waylandSignalFrame(LG_DSWaitFrameResult result) +{ + atomic_fetch_or_explicit( + &wlWm.frameEventFlags, result, memory_order_release); + lgSignalEvent(wlWm.frameEvent); +} + static void fractionalScalePreferredScale(void * data, struct wp_fractional_scale_v1 * fractionalScale, uint32_t scale) { @@ -133,7 +140,7 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen, DEBUG_ERROR("Failed to initialize event for waitFrame"); return false; } - lgSignalEvent(wlWm.frameEvent); + waylandSignalFrame(LG_DS_WAIT_FRAME_INTERRUPTED); if (!wlWm.compositor) { @@ -220,7 +227,7 @@ bool waylandIsValidPointerPos(int x, int y) static void frameHandler(void * opaque, struct wl_callback * callback, unsigned int data) { - lgSignalEvent(wlWm.frameEvent); + waylandSignalFrame(LG_DS_WAIT_FRAME_CADENCE); wl_callback_destroy(callback); } @@ -228,9 +235,11 @@ static const struct wl_callback_listener frame_listener = { .done = frameHandler, }; -bool waylandWaitFrame(void) +LG_DSWaitFrameResult waylandWaitFrame(void) { lgWaitEvent(wlWm.frameEvent, TIMEOUT_INFINITE); + const LG_DSWaitFrameResult result = atomic_exchange_explicit( + &wlWm.frameEventFlags, LG_DS_WAIT_FRAME_NONE, memory_order_acquire); INTERLOCKED_SECTION(wlWm.surfaceLock, { @@ -239,7 +248,7 @@ bool waylandWaitFrame(void) wl_callback_add_listener(callback, &frame_listener, NULL); }); - return false; + return result; } void waylandSkipFrame(void) @@ -253,5 +262,5 @@ void waylandSkipFrame(void) void waylandStopWaitFrame(void) { - lgSignalEvent(wlWm.frameEvent); + waylandSignalFrame(LG_DS_WAIT_FRAME_INTERRUPTED); } diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index 018fa62f..48b0d96f 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -95,6 +95,13 @@ static void x11XPresentEvent(XGenericEventCookie *cookie); static void x11UpdateKeyboardGroup(void); static void x11GrabPointer(void); +static void x11SignalFrame(LG_DSWaitFrameResult result) +{ + atomic_fetch_or_explicit( + &x11.frameEventFlags, result, memory_order_release); + lgSignalEvent(x11.frameEvent); +} + static uint64_t x11ModePeriod( const XRRScreenResources * resources, RRMode modeID) { @@ -849,7 +856,7 @@ static void x11Startup(void) static void x11Shutdown(void) { if (x11.jitRender) - lgSignalEvent(x11.frameEvent); + x11SignalFrame(LG_DS_WAIT_FRAME_INTERRUPTED); } static void x11Free(void) @@ -1595,7 +1602,7 @@ static void x11XPresentEvent(XGenericEventCookie *cookie) x11DoPresent(e->msc); atomic_store(&x11.presentMsc, e->msc); atomic_store(&x11.presentUst, e->ust); - lgSignalEvent(x11.frameEvent); + x11SignalFrame(LG_DS_WAIT_FRAME_CADENCE); break; } } @@ -1645,14 +1652,14 @@ static EGLNativeWindowType x11GetEGLNativeWindow(void) return (EGLNativeWindowType)x11.window; } -static void x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface, +static bool x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { static struct SwapWithDamageData data = {0}; if (!data.init) swapWithDamageInit(&data, display); - swapWithDamage(&data, display, surface, damage, count); + return swapWithDamage(&data, display, surface, damage, count); } #endif @@ -1692,10 +1699,17 @@ static void x11GLSwapBuffers(void) } #endif -static bool x11WaitFrame(void) +static LG_DSWaitFrameResult x11WaitFrame(void) { /* wait until we are woken up by the present event */ lgWaitEvent(x11.frameEvent, TIMEOUT_INFINITE); + LG_DSWaitFrameResult result = atomic_exchange_explicit( + &x11.frameEventFlags, LG_DS_WAIT_FRAME_NONE, memory_order_acquire); + + /* Interrupts carry no new presentation timestamp and must not perturb the + * calibration state. */ + if (!(result & LG_DS_WAIT_FRAME_CADENCE)) + return result; #define WARMUP_TIME 3000000 //2s #define CALIBRATION_COUNT 400 @@ -1713,7 +1727,7 @@ static bool x11WaitFrame(void) } if (ust < expire) - return false; + return result; warmup = false; DEBUG_INFO("Warmup done, doing calibration..."); @@ -1831,14 +1845,14 @@ static bool x11WaitFrame(void) /* force rendering until we have finished calibration so we can take into * account how long it takes for the scene to render */ if (calibrate < CALIBRATION_COUNT) - return true; + result |= LG_DS_WAIT_FRAME_FORCE_RENDER; - return false; + return result; } static void x11StopWaitFrame(void) { - lgSignalEvent(x11.frameEvent); + x11SignalFrame(LG_DS_WAIT_FRAME_INTERRUPTED); } static void x11GuestPointerUpdated(double x, double y, double localX, double localY) diff --git a/client/displayservers/X11/x11.h b/client/displayservers/X11/x11.h index c3bed111..f7f79ef2 100644 --- a/client/displayservers/X11/x11.h +++ b/client/displayservers/X11/x11.h @@ -81,6 +81,7 @@ struct X11DSState uint32_t presentSerial; Pixmap presentPixmap; XserverRegion presentRegion; + _Atomic(unsigned) frameEventFlags; LGEvent * frameEvent; bool xrandrSupported; diff --git a/client/include/app.h b/client/include/app.h index be956fa2..19b3bd77 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -103,7 +103,8 @@ bool app_getHDRDescFailed(void); #ifdef ENABLE_EGL EGLDisplay app_getEGLDisplay(void); EGLNativeWindowType app_getEGLNativeWindow(void); -void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); +bool app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, + const struct Rect * damage, int count); #endif #ifdef ENABLE_OPENGL diff --git a/client/include/eglutil.h b/client/include/eglutil.h index 8f3cbeeb..08afaed0 100644 --- a/client/include/eglutil.h +++ b/client/include/eglutil.h @@ -35,7 +35,7 @@ struct SwapWithDamageData void swapWithDamageInit(struct SwapWithDamageData * data, EGLDisplay display); void swapWithDamageDisable(struct SwapWithDamageData * data); -void swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, EGLSurface surface, - const struct Rect * damage, int count); +bool swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, + EGLSurface surface, const struct Rect * damage, int count); #endif diff --git a/client/include/interface/displayserver.h b/client/include/interface/displayserver.h index 957f7247..34cf7d5a 100644 --- a/client/include/interface/displayserver.h +++ b/client/include/interface/displayserver.h @@ -103,6 +103,15 @@ typedef enum LG_DSPointer } LG_DSPointer; +typedef enum LG_DSWaitFrameResult +{ + LG_DS_WAIT_FRAME_NONE = 0, + LG_DS_WAIT_FRAME_CADENCE = 1 << 0, + LG_DS_WAIT_FRAME_INTERRUPTED = 1 << 1, + LG_DS_WAIT_FRAME_FORCE_RENDER = 1 << 2, +} +LG_DSWaitFrameResult; + #define LG_POINTER_COUNT (LG_POINTER_NOT_ALLOWED + 1) typedef struct LG_DSInitParams @@ -171,7 +180,8 @@ struct LG_DisplayServerOps /* EGL support */ EGLDisplay (*getEGLDisplay)(void); EGLNativeWindowType (*getEGLNativeWindow)(void); - void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); + bool (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface, + const struct Rect * damage, int count); #endif #ifdef ENABLE_OPENGL @@ -184,11 +194,8 @@ struct LG_DisplayServerOps #endif /* Waits for a good time to render the next frame in time for the next vblank. - * This is optional and a display server may choose to not implement it. - * - * return true to force the frame to be rendered, this is used by X11 for - * calibration */ - bool (*waitFrame)(void); + * This is optional and a display server may choose to not implement it. */ + LG_DSWaitFrameResult (*waitFrame)(void); /* This must be called when waitFrame returns, but no frame is actually rendered. */ void (*skipFrame)(void); diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 46fb47d4..33f23148 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -1689,13 +1689,17 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const uint64_t composeEnd = nanotime(); timing->composeTime += composeEnd - postOverlayStart; - const uint64_t swapStart = nanotime(); - app_eglSwapBuffers(this->display, this->surface, damage, + const uint64_t swapStart = nanotime(); + const bool swapResult = app_eglSwapBuffers( + this->display, this->surface, damage, this->noSwapDamage ? 0 : damageIdx); - const uint64_t swapEnd = nanotime(); + const uint64_t swapEnd = nanotime(); timing->swapTime = swapEnd - swapStart; - return true; + if (!swapResult) + DEBUG_ERROR("Failed to swap EGL buffers (eglError: 0x%x)", eglGetError()); + + return swapResult; } static bool egl_capture(LG_Renderer * renderer, LG_RendererCapture * capture) diff --git a/client/src/app.c b/client/src/app.c index b49d8b72..cbc1e994 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -838,9 +838,10 @@ EGLNativeWindowType app_getEGLNativeWindow(void) return g_state.ds->getEGLNativeWindow(); } -void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) +bool app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, + const struct Rect * damage, int count) { - g_state.ds->eglSwapBuffers(display, surface, damage, count); + return g_state.ds->eglSwapBuffers(display, surface, damage, count); } #endif diff --git a/client/src/eglutil.c b/client/src/eglutil.c index 6b147d91..cd19b735 100644 --- a/client/src/eglutil.c +++ b/client/src/eglutil.c @@ -52,14 +52,11 @@ void swapWithDamageDisable(struct SwapWithDamageData * data) data->func = NULL; } -void swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, EGLSurface surface, - const struct Rect * damage, int count) +bool swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, + EGLSurface surface, const struct Rect * damage, int count) { if (!data->func || !count) - { - eglSwapBuffers(display, surface); - return; - } + return eglSwapBuffers(display, surface) == EGL_TRUE; EGLint rects[count * 4]; for (int i = 0; i < count; ++i) @@ -69,5 +66,5 @@ void swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, EGLSur rects[i * 4 + 2] = damage[i].w; rects[i * 4 + 3] = damage[i].h; } - data->func(display, surface, rects, count); + return data->func(display, surface, rects, count) == EGL_TRUE; } diff --git a/client/src/main.c b/client/src/main.c index 663729e4..a6cd7126 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -394,7 +394,8 @@ static void frameTimingFinishFrame(LG_RendererFrameToken token, } static void frameTimingFinishRender(const LG_RendererFrameTiming * timing, - uint64_t prepareStart, uint64_t prepareTime, uint64_t timestamp) + uint64_t prepareStart, uint64_t prepareTime, uint64_t timestamp, + LG_RendererFrameToken cadenceToken) { uint64_t feedbackFrameSerial = 0; uint64_t feedbackQueueStart = 0; @@ -425,7 +426,8 @@ static void frameTimingFinishRender(const LG_RendererFrameTiming * timing, feedbackGeneration = record->scheduleGeneration; feedbackEpoch = record->scheduleEpoch; feedbackDeadline = record->scheduleDeadlineSerial; - feedbackValid = record->phaseValid; + feedbackValid = record->phaseValid && + timing->frameToken == cadenceToken; feedbackQueueStart = record->queueStart; if (unlikely( @@ -640,9 +642,16 @@ static int renderThread(void * unused) while(likely(app_getState() != APP_STATE_SHUTDOWN)) { + LG_RendererFrameToken cadenceToken = LG_RENDERER_FRAME_TOKEN_NONE; + if (g_state.jitRender) { - const bool forceRender = g_state.ds->waitFrame(); + const LG_DSWaitFrameResult waitResult = g_state.ds->waitFrame(); + const LG_RendererFrameToken queuedAtWake = + waitResult == LG_DS_WAIT_FRAME_CADENCE ? + frameTimingQueuedToken() : LG_RENDERER_FRAME_TOKEN_NONE; + const bool forceRender = + waitResult & LG_DS_WAIT_FRAME_FORCE_RENDER; app_handleRenderEvent(microtime()); const uint64_t pending = @@ -657,11 +666,9 @@ static int renderThread(void * unused) const bool overlayRender = overlayNeeded && (!g_state.lastRenderTimeValid || !periodKnown || elapsed + outputPeriod >= g_state.overlayFrameTime); + const bool clientWake = lgResetEvent(g_state.frameEvent); - if (!lgResetEvent(g_state.frameEvent) - && !forceRender - && !pending - && !overlayRender) + if (!clientWake && !forceRender && !pending && !overlayRender) { if (g_state.ds->skipFrame) g_state.ds->skipFrame(); @@ -669,7 +676,11 @@ static int renderThread(void * unused) } if (pending > 0) + { atomic_fetch_sub(&g_state.pendingCount, 1); + if (!clientWake) + cadenceToken = queuedAtWake; + } } else if (g_params.fpsMin != 0) { @@ -693,8 +704,10 @@ static int renderThread(void * unused) igGetStyle()->FontScaleMain = 1.0f / g_state.windowScale; } - const bool fontDirty = atomic_exchange(&g_state.fontDirty, false); - if (unlikely(fontDirty || g_state.fontScale != g_state.windowScale)) + const bool fontDirty = atomic_exchange(&g_state.fontDirty, false); + const bool fontUpdate = fontDirty || + g_state.fontScale != g_state.windowScale; + if (unlikely(fontUpdate)) { if (!util_buildUIFontAtlas(g_state.io->Fonts, g_params.uiSize * g_state.windowScale, &g_state.fontLarge)) @@ -706,6 +719,9 @@ static int renderThread(void * unused) g_state.fontScale = g_state.windowScale; } + if (unlikely(resize || fontUpdate)) + cadenceToken = LG_RENDERER_FRAME_TOKEN_NONE; + if (unlikely(resize)) { if (g_state.lgr) @@ -721,11 +737,14 @@ static int renderThread(void * unused) renderQueue_process(); - const bool windowInvalid = + const bool windowInvalid = atomic_exchange(&g_state.invalidateWindow, false); - const bool overlayFull = app_overlayNeedsFullRender(); - const bool invalidate = windowInvalid || overlayFull; - const uint64_t prepareTime = nanotime() - prepareStart; + const bool overlayFull = app_overlayNeedsFullRender(); + const bool invalidate = windowInvalid || overlayFull; + const uint64_t prepareTime = nanotime() - prepareStart; + + if (unlikely(invalidate)) + cadenceToken = LG_RENDERER_FRAME_TOKEN_NONE; LG_RendererFrameTiming rendererTiming = {}; if (unlikely(!RENDERER(render, g_params.winRotate, frameTokenLimit, @@ -739,7 +758,8 @@ static int renderThread(void * unused) if (rendererTiming.frameToken != LG_RENDERER_FRAME_TOKEN_NONE) frameTimingFinishRender( - &rendererTiming, prepareStart, prepareTime, renderEnd); + &rendererTiming, prepareStart, prepareTime, renderEnd, + cadenceToken); frameTimingPublishReady(); const uint64_t t = nanotime();