[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.
This commit is contained in:
Geoffrey McRae
2026-08-06 16:17:23 +10:00
parent 515f4ba06f
commit 3d1064001d
12 changed files with 119 additions and 57 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -81,6 +81,7 @@ struct X11DSState
uint32_t presentSerial;
Pixmap presentPixmap;
XserverRegion presentRegion;
_Atomic(unsigned) frameEventFlags;
LGEvent * frameEvent;
bool xrandrSupported;