qt/wayland: stretch subsurface buffer on resize via wp_viewport
Eliminates the resize bleed where the parent QWidget had grown to
its new size but our wl_subsurface still had the old-size buffer
attached, briefly exposing the translucent parent (= compositor
background) in the new area.
Trick: wp_viewport.set_destination is double-buffered on the
child surface. Committing the child with a new destination but
WITHOUT a new buffer makes the compositor stretch the currently-
attached buffer to fill the new extent. We do this at the start
of syncSurfaceSize, before the synchronous ghostty_surface_draw
that renders the proper new-size frame. Sequence per resize:
1. wp_viewport.set_destination(new_w_logical, new_h_logical)
+ wl_surface.commit(child) -> stretch old buffer immediately
2. ghostty_surface_draw + drainVulkan -> attach + commit
proper new-size buffer, replacing the stretched content
3. Qt commits parent surface at new size -> parent grows with
subsurface already filled (step 1) or already correct (step 2)
The only artifact is one frame of brief stretching of old content
instead of a one-frame transparent gap — visually similar to mpv's
vo_dmabuf_wayland behavior on video window resize.
Same idea any subsurface-based compositor client uses; no Wayland
protocol provides true atomic-commit-N-surfaces (researched), so
this visual hide is the universal solution.
Stays in desync mode — sync mode requires the parent to commit
for cached child state to apply, which fails for a translucent
QWidget that has no paint damage.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
8f2bb90ec5
commit
0b32bebaeb
|
|
@ -247,8 +247,18 @@ void GhosttySurface::syncSurfaceSize() {
|
|||
// before the subsurface present path replaced the QImage one.
|
||||
if (m_useSubsurface.load(std::memory_order_acquire) &&
|
||||
m_subsurfacePresenter) {
|
||||
// First: stretch the existing subsurface buffer to the new
|
||||
// logical size by bumping wp_viewport.set_destination + a bare
|
||||
// child commit. In desync mode the compositor applies this
|
||||
// immediately, so the parent surface can grow to the new size
|
||||
// with our subsurface already covering it (briefly stretched)
|
||||
// instead of exposing a transparent gap. mpv's
|
||||
// vo_dmabuf_wayland uses the same pattern for video resize.
|
||||
m_subsurfacePresenter->resizeDestination(width(), height());
|
||||
// Then: render at the new size and commit the proper new-size
|
||||
// buffer, which overwrites the stretched content.
|
||||
ghostty_surface_draw(m_surface);
|
||||
drainVulkan(); // runs presentDmabuf at the new size + commits
|
||||
drainVulkan();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -428,4 +428,22 @@ void SubsurfacePresenter::presentDmabuf(int fd, uint32_t drm_format,
|
|||
}
|
||||
}
|
||||
|
||||
void SubsurfacePresenter::resizeDestination(int dest_width, int dest_height) {
|
||||
if (!m_viewport || !m_childSurface) return;
|
||||
if (dest_width <= 0 || dest_height <= 0) return;
|
||||
if (dest_width == m_lastDestWidth && dest_height == m_lastDestHeight) return;
|
||||
|
||||
// Update destination + commit child WITHOUT attaching a new buffer.
|
||||
// In desync mode the commit applies immediately and the compositor
|
||||
// stretches the currently-attached buffer to the new dest extent.
|
||||
// The next presentDmabuf will overwrite this with a properly-sized
|
||||
// buffer, but until then the subsurface fills the new area instead
|
||||
// of leaving a transparent gap during the parent's resize commit.
|
||||
wp_viewport_set_destination(m_viewport, dest_width, dest_height);
|
||||
m_lastDestWidth = dest_width;
|
||||
m_lastDestHeight = dest_height;
|
||||
wl_surface_commit(m_childSurface);
|
||||
wl_display_flush(m_display);
|
||||
}
|
||||
|
||||
} // namespace wayland
|
||||
|
|
|
|||
|
|
@ -102,6 +102,24 @@ public:
|
|||
// `logical * preferredScale120() / 120` device pixels.
|
||||
uint32_t preferredScale120() const { return m_preferredScale120; }
|
||||
|
||||
// Stretch the existing subsurface buffer to a new destination
|
||||
// size WITHOUT attaching a new buffer. Used at the *start* of a
|
||||
// resize, before the renderer has produced a new-size frame:
|
||||
// wp_viewport.set_destination is double-buffered on the child
|
||||
// surface, so committing the child here in desync mode applies
|
||||
// the new destination immediately and the compositor stretches
|
||||
// the old buffer to fill it. Result: the parent surface can grow
|
||||
// to its new size with the subsurface already covering the new
|
||||
// area (briefly stretched), instead of leaving a one-frame
|
||||
// transparent gap where the translucent parent shows through.
|
||||
//
|
||||
// The next presentDmabuf call (with the real new-size buffer)
|
||||
// replaces the stretched content, ending the brief blur.
|
||||
//
|
||||
// Same pattern mpv's vo_dmabuf_wayland uses for its video
|
||||
// subsurface during resize.
|
||||
void resizeDestination(int dest_width, int dest_height);
|
||||
|
||||
// Called from the wp_fractional_scale_v1.preferred_scale event.
|
||||
// Public so the C-style listener struct at file scope in the .cpp
|
||||
// can name it; not part of the API for other call sites.
|
||||
|
|
|
|||
Loading…
Reference in New Issue