From 1ad9898f66b349cd7812bd124f9e128db188fd83 Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Fri, 2 Oct 2026 18:30:29 +0200 Subject: [PATCH] A subsurface maps with the main surface's first buffer, not a frame after it The run loop drew the main surface first and reconciled the subsurfaces afterwards, in every iteration including the first. On the first frame the main surface was therefore committed and mapped on its own, and the subsurfaces only got created, rastered and placed in a later parent commit. The lockscreen shows the problem: its main surface always holds the unlock form, and the idle clock panel that covers it lives on a subsurface, so at greeter startup and whenever a forge session locked, the form was visible for a moment before the idle panel appeared on top. In the greeter, `on_first_frame_committed` also sent the daemon its readiness byte with that frame, so the VT switch showed the form. Until the main surface has committed its first frame, `reconcile_subsurfaces` now also runs before `draw_frame`. It creates each subsurface, rasters it and commits its buffer, which a desynchronised subsurface applies on its own commit, and sets its position, which waits for the parent. In that pass it no longer commits a `Main` parent itself: a bufferless commit is a protocol error on an ext-session-lock surface, and the main surface's first buffer commit, made right after by `draw_frame`, applies the placement anyway, so the main surface and its subsurfaces reach the screen in the same commit. Overlay parents and the reconcile after the draw are unchanged, and once the first frame is out the loop behaves as before. --- src/event_loop/run.rs | 6 ++++++ src/event_loop/subsurface.rs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/event_loop/run.rs b/src/event_loop/run.rs index cb83288..50fb414 100644 --- a/src/event_loop/run.rs +++ b/src/event_loop/run.rs @@ -643,6 +643,12 @@ pub( crate ) fn try_run( mut app: A ) -> Result<(), RunError> // appeared. Specs are re-queried next frame for drawing. reconcile_overlays( &mut data ); + // Subsurfaces must map with the main surface's first buffer, not a frame later. + if !data.first_frame_committed + { + super::subsurface::reconcile_subsurfaces( &mut data ); + } + // Draw any surface that's configured, dirty, and not already waiting // on a frame callback. The compositor decides our cadence — when no // surface qualifies we just loop back to `dispatch(None)` and sleep. diff --git a/src/event_loop/subsurface.rs b/src/event_loop/subsurface.rs index 6301782..e6d184f 100644 --- a/src/event_loop/subsurface.rs +++ b/src/event_loop/subsurface.rs @@ -285,6 +285,8 @@ pub( crate ) fn reconcile_subsurfaces( data: &mut AppData ) // parent buffer — the cheap per-frame move. for ( parent, wl ) in dirty_parents { + // A bufferless commit is a protocol error on a session-lock surface. + if parent == SubsurfaceParent::Main && !data.first_frame_committed { continue; } arm_parent_frame( data, parent, &wl ); wl.commit(); }