diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index 31f17c13c..693a8df10 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -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; } diff --git a/qt/src/wayland/SubsurfacePresenter.cpp b/qt/src/wayland/SubsurfacePresenter.cpp index 04f8d94e6..90999ce62 100644 --- a/qt/src/wayland/SubsurfacePresenter.cpp +++ b/qt/src/wayland/SubsurfacePresenter.cpp @@ -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 diff --git a/qt/src/wayland/SubsurfacePresenter.h b/qt/src/wayland/SubsurfacePresenter.h index b6dcfc907..7d874570d 100644 --- a/qt/src/wayland/SubsurfacePresenter.h +++ b/qt/src/wayland/SubsurfacePresenter.h @@ -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.