[client] wayland: serialize surface commits with EGL swaps

NVIDIA's Wayland EGL implementation sets explicit-sync acquire and
release points while presenting a frame. The requests emitted by
eglSwapBuffers and its final surface commit must be treated as one
logical transaction.

The pointer-warp path can commit the main surface from the input
thread while the render thread is inside eglSwapBuffers. If this
occurs between the explicit-sync requests, the cursor commit applies
incomplete pending state and the compositor disconnects the client
with:

  explicit sync is used, but no release point is set

Add a surface lock and hold it across EGL presentation. Use the same
lock for direct commits, frame callback registration, and
pointer-constraint updates, replacing the narrower confinement lock.
This prevents another thread from splitting the EGL transaction and
removes the need for __NV_DISABLE_EXPLICIT_SYNC=1.
This commit is contained in:
Geoffrey McRae
2026-07-30 11:09:07 +10:00
parent a9b8f437d4
commit f3155647d2
5 changed files with 74 additions and 60 deletions

View File

@@ -230,21 +230,27 @@ static const struct wp_image_description_v1_listener hdrImageDescListener =
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
{ {
if (!wlWm.swapWithDamage.init) // 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.
INTERLOCKED_SECTION(wlWm.surfaceLock,
{ {
if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4) if (!wlWm.swapWithDamage.init)
{ {
DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4"); if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4)
swapWithDamageDisable(&wlWm.swapWithDamage); {
DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4");
swapWithDamageDisable(&wlWm.swapWithDamage);
}
else
swapWithDamageInit(&wlWm.swapWithDamage, display);
} }
else
swapWithDamageInit(&wlWm.swapWithDamage, display);
}
waylandPresentationFrame(); waylandPresentationFrame();
applyHDRPending(); applyHDRPending();
swapWithDamage(&wlWm.swapWithDamage, display, surface, damage, count); swapWithDamage(&wlWm.swapWithDamage, display, surface, damage, count);
activateReadyHDRImageDesc(); activateReadyHDRImageDesc();
});
if (wlWm.needsResize) if (wlWm.needsResize)
{ {

View File

@@ -322,7 +322,8 @@ static const struct wl_keyboard_listener keyboardListener = {
static void waylandCleanUpPointer(void) static void waylandCleanUpPointer(void)
{ {
INTERLOCKED_SECTION(wlWm.confineLock, { INTERLOCKED_SECTION(wlWm.surfaceLock,
{
if (wlWm.lockedPointer) if (wlWm.lockedPointer)
{ {
zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); zwp_locked_pointer_v1_destroy(wlWm.lockedPointer);
@@ -438,15 +439,12 @@ bool waylandInputInit(void)
wl_seat_add_listener(wlWm.seat, &seatListener, NULL); wl_seat_add_listener(wlWm.seat, &seatListener, NULL);
wl_display_roundtrip(wlWm.display); wl_display_roundtrip(wlWm.display);
LG_LOCK_INIT(wlWm.confineLock);
return true; return true;
} }
void waylandInputFree(void) void waylandInputFree(void)
{ {
waylandUngrabPointer(); waylandUngrabPointer();
LG_LOCK_FREE(wlWm.confineLock);
if (wlWm.pointer) if (wlWm.pointer)
waylandCleanUpPointer(); waylandCleanUpPointer();
@@ -482,7 +480,7 @@ void waylandGrabPointer(void)
&relativePointerListener, NULL); &relativePointerListener, NULL);
} }
INTERLOCKED_SECTION(wlWm.confineLock, INTERLOCKED_SECTION(wlWm.surfaceLock,
{ {
if (!wlWm.confinedPointer && !wlWm.lockedPointer) if (!wlWm.confinedPointer && !wlWm.lockedPointer)
{ {
@@ -496,7 +494,7 @@ void waylandGrabPointer(void)
inline static void internalUngrabPointer(bool lock) inline static void internalUngrabPointer(bool lock)
{ {
if (lock) if (lock)
LG_LOCK(wlWm.confineLock); LG_LOCK(wlWm.surfaceLock);
if (wlWm.confinedPointer) if (wlWm.confinedPointer)
{ {
@@ -505,7 +503,7 @@ inline static void internalUngrabPointer(bool lock)
} }
if (lock) if (lock)
LG_UNLOCK(wlWm.confineLock); LG_UNLOCK(wlWm.surfaceLock);
if (!wlWm.warpSupport) if (!wlWm.warpSupport)
{ {
@@ -536,7 +534,7 @@ void waylandCapturePointer(void)
return; return;
} }
INTERLOCKED_SECTION(wlWm.confineLock, INTERLOCKED_SECTION(wlWm.surfaceLock,
{ {
if (wlWm.confinedPointer) if (wlWm.confinedPointer)
{ {
@@ -545,14 +543,14 @@ void waylandCapturePointer(void)
} }
wlWm.lockedPointer = zwp_pointer_constraints_v1_lock_pointer( wlWm.lockedPointer = zwp_pointer_constraints_v1_lock_pointer(
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}); });
} }
void waylandUncapturePointer(void) void waylandUncapturePointer(void)
{ {
INTERLOCKED_SECTION(wlWm.confineLock, INTERLOCKED_SECTION(wlWm.surfaceLock,
{ {
if (wlWm.lockedPointer) if (wlWm.lockedPointer)
{ {
@@ -601,44 +599,43 @@ void waylandWarpPointer(int x, int y, bool exiting)
if (!wlWm.pointerInSurface || wlWm.lockedPointer) if (!wlWm.pointerInSurface || wlWm.lockedPointer)
return; return;
INTERLOCKED_SECTION(wlWm.confineLock, LG_LOCK(wlWm.surfaceLock);
if (wlWm.lockedPointer)
{ {
if (wlWm.lockedPointer) LG_UNLOCK(wlWm.surfaceLock);
{ return;
LG_UNLOCK(wlWm.confineLock); }
return;
}
int width, height; int width, height;
wlWm.desktop->getSize(&width, &height); wlWm.desktop->getSize(&width, &height);
if (x < 0) x = 0; if (x < 0) x = 0;
else if (x >= width) x = width - 1; else if (x >= width) x = width - 1;
if (y < 0) y = 0; if (y < 0) y = 0;
else if (y >= height) y = height - 1; else if (y >= height) y = height - 1;
struct wl_region * region = wl_compositor_create_region(wlWm.compositor); struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
wl_region_add(region, x, y, 1, 1); wl_region_add(region, x, y, 1, 1);
if (wlWm.confinedPointer)
{
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region);
wl_surface_commit(wlWm.surface);
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL);
}
else
{
struct zwp_confined_pointer_v1 * confine;
confine = zwp_pointer_constraints_v1_confine_pointer(
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region,
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
wl_surface_commit(wlWm.surface);
zwp_confined_pointer_v1_destroy(confine);
}
if (wlWm.confinedPointer)
{
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region);
wl_surface_commit(wlWm.surface); wl_surface_commit(wlWm.surface);
wl_region_destroy(region); zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL);
}); }
else
{
struct zwp_confined_pointer_v1 * confine;
confine = zwp_pointer_constraints_v1_confine_pointer(
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region,
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
wl_surface_commit(wlWm.surface);
zwp_confined_pointer_v1_destroy(confine);
}
wl_surface_commit(wlWm.surface);
wl_region_destroy(region);
LG_UNLOCK(wlWm.surfaceLock);
} }
void waylandRealignPointer(void) void waylandRealignPointer(void)

View File

@@ -108,6 +108,7 @@ static bool getCompositor(char * dst, size_t size)
static bool waylandInit(const LG_DSInitParams params) static bool waylandInit(const LG_DSInitParams params)
{ {
memset(&wlWm, 0, sizeof(wlWm)); memset(&wlWm, 0, sizeof(wlWm));
LG_LOCK_INIT(wlWm.surfaceLock);
LG_LOCK_INIT(wlWm.pendingHDRLock); LG_LOCK_INIT(wlWm.pendingHDRLock);
LG_LOCK_INIT(wlWm.hdrLock); LG_LOCK_INIT(wlWm.hdrLock);
wlWm.desktop = WL_Desktops[0]; wlWm.desktop = WL_Desktops[0];
@@ -214,6 +215,7 @@ static void waylandFree(void)
wl_display_disconnect(wlWm.display); wl_display_disconnect(wlWm.display);
LG_LOCK_FREE(wlWm.hdrLock); LG_LOCK_FREE(wlWm.hdrLock);
LG_LOCK_FREE(wlWm.pendingHDRLock); LG_LOCK_FREE(wlWm.pendingHDRLock);
LG_LOCK_FREE(wlWm.surfaceLock);
} }
static bool waylandGetProp(LG_DSProperty prop, void * ret) static bool waylandGetProp(LG_DSProperty prop, void * ret)

View File

@@ -134,6 +134,7 @@ struct WaylandDSState
struct wl_seat * seat; struct wl_seat * seat;
struct wl_shm * shm; struct wl_shm * shm;
struct wl_compositor * compositor; struct wl_compositor * compositor;
LG_Lock surfaceLock;
struct WaylandScale scale; struct WaylandScale scale;
bool fractionalScale; bool fractionalScale;
@@ -192,7 +193,6 @@ struct WaylandDSState
struct zwp_locked_pointer_v1 * lockedPointer; struct zwp_locked_pointer_v1 * lockedPointer;
bool showPointer; bool showPointer;
uint32_t pointerEnterSerial; uint32_t pointerEnterSerial;
LG_Lock confineLock;
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager; struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
struct zwp_idle_inhibitor_v1 * idleInhibitor; struct zwp_idle_inhibitor_v1 * idleInhibitor;

View File

@@ -154,7 +154,10 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen,
title, appId, fullscreen, maximize, borderless, resizable)) title, appId, fullscreen, maximize, borderless, resizable))
return false; return false;
wl_surface_commit(wlWm.surface); INTERLOCKED_SECTION(wlWm.surfaceLock,
{
wl_surface_commit(wlWm.surface);
});
// The initial configure supplies the compositor-selected size for states // The initial configure supplies the compositor-selected size for states
// such as fullscreen. It must be received before the first buffer is made. // such as fullscreen. It must be received before the first buffer is made.
@@ -214,9 +217,12 @@ bool waylandWaitFrame(void)
{ {
lgWaitEvent(wlWm.frameEvent, TIMEOUT_INFINITE); lgWaitEvent(wlWm.frameEvent, TIMEOUT_INFINITE);
struct wl_callback * callback = wl_surface_frame(wlWm.surface); INTERLOCKED_SECTION(wlWm.surfaceLock,
if (callback) {
wl_callback_add_listener(callback, &frame_listener, NULL); struct wl_callback * callback = wl_surface_frame(wlWm.surface);
if (callback)
wl_callback_add_listener(callback, &frame_listener, NULL);
});
return false; return false;
} }
@@ -224,7 +230,10 @@ bool waylandWaitFrame(void)
void waylandSkipFrame(void) void waylandSkipFrame(void)
{ {
// If we decided to not render, we must commit the surface so that the callback is registered. // If we decided to not render, we must commit the surface so that the callback is registered.
wl_surface_commit(wlWm.surface); INTERLOCKED_SECTION(wlWm.surfaceLock,
{
wl_surface_commit(wlWm.surface);
});
} }
void waylandStopWaitFrame(void) void waylandStopWaitFrame(void)