qt/wayland: subsurface in sync mode for atomic-with-parent resize

The previous desync mode meant our wl_subsurface commits applied
immediately, independent of Qt's parent surface commits. That's
lower-latency in the steady state but during resize it left a
one-frame window where the parent had already grown to the new
size but our child subsurface was still showing its old-size
buffer — the parent's translucent QWidget background showed
through the gap. The original pre-subsurface QPainter blit didn't
have this issue because everything was on one surface and resize
was inherently atomic.

Sync mode (the wl_subsurface default) restores that atomicity:
our wl_surface.commit on the child caches state until the parent
commits, then both apply in the same compositor frame. Resize
becomes lockstep.

The cost is that frames now need a parent commit to become
visible. drainVulkan now calls update() after each presentDmabuf
to schedule the paintEvent that triggers Qt's backing-store
flush (= parent wl_surface.commit). Latency penalty vs desync:
one event-loop turn (sub-millisecond at idle).

Pairs with b8d2f25cf (synchronous draw in resizeEvent) — that
fix attached the new-size buffer to the subsurface inside
resizeEvent; this fix ensures the attach gets applied atomically
with the parent's next commit.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
Nathan 2026-05-25 10:29:18 -05:00
parent b8d2f25cf5
commit 94c51e227f
2 changed files with 20 additions and 5 deletions

View File

@ -1551,6 +1551,12 @@ void GhosttySurface::drainVulkan() {
m_subsurfacePresenter->presentDmabuf(
frame.fd, frame.drm_format, frame.drm_modifier, frame.width,
frame.height, frame.stride, width(), height());
// The subsurface is in wl_subsurface sync mode, so the buffer
// we just attached only becomes visible when Qt's parent surface
// commits. update() schedules a paintEvent which triggers
// Qt's backing-store flush (= parent wl_surface.commit), at
// which point our cached subsurface state applies atomically.
update();
return;
}

View File

@ -254,11 +254,20 @@ SubsurfacePresenter::tryCreate(QWindow *parent) {
return nullptr;
}
// Independent frame pacing: the renderer's present cadence is
// driven by libghostty's render thread, not the GUI thread's paint
// cycle, so we don't want our wl_subsurface state changes to wait
// for the parent's next commit. `set_desync` is what allows that.
wl_subsurface_set_desync(sub);
// Sync mode (the wl_subsurface default — we don't call set_desync).
// In sync mode our wl_surface.commit caches state until the parent
// surface commits, at which point both apply atomically. That's
// what gives resize its lockstep behavior — parent grows to the
// new size and our subsurface's matching new-size buffer apply in
// the same compositor frame, so there's no transient gap where the
// parent's translucent background shows through.
//
// The cost: our frames need a parent commit to become visible. The
// GhosttySurface caller compensates by calling update() after each
// presentDmabuf — that schedules a paintEvent, which triggers Qt
// to flush the parent surface's backing store (= a wl_surface.commit
// on the parent). Total latency penalty vs desync: one event-loop
// turn, sub-millisecond at idle.
// Subsurface covers the parent at the origin. Phase 4 will keep
// this in sync on splits/tabs/etc.; for now the GhosttySurface