From 61d01bb79c6882c950f45a1176ba1f30fbbd4824 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Sat, 9 Aug 2025 00:57:00 +0200 Subject: Fix interactive-resize border/shadow lag without the OOB risk that sank the first attempt effective_frame_of now returns the live drag target while a window is being interactively resized (same change as the reverted first attempt), but two things make it safe this time instead of reintroducing the out-of-bounds texture sample that reversion was for: - Every src crop rect built from a window's frame width in udev/render.rs and winit/render.rs (titlebar, top border strip, bottom border strip) is now clamped against DecorationSignature's own recorded width/ border_width - the bitmap's actual last-built size - before reaching MemoryRenderBufferRenderElement::from_buffer, which does not itself validate src against the real texture size. This is a structural floor independent of timing, not a repeat of the previous unsafe approach. - handle_pointer_position now calls redraw_decoration_buffer once per resize motion event (throttled to 60Hz via a new CompState::resize_redraw_at), closing the lag at its source instead of only catching up on the next real client commit. This also fixes the shadow bitmap's identical commit-vs-live-position gap for free, since redraw_decoration_buffer rebuilds all three bitmaps together. Updates the TODO.md entry for this bug with the full before/after. --- crates/wayland/src/state/geometry.rs | 43 ++++++++++++++++++++++++++---------- crates/wayland/src/state/mod.rs | 14 ++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) (limited to 'crates/wayland/src/state') diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs index b537412..26c23fe 100644 --- a/crates/wayland/src/state/geometry.rs +++ b/crates/wayland/src/state/geometry.rs @@ -46,22 +46,41 @@ impl CompState { // in a more important way - every caller of this function that // reads a *bitmap*-backed element (the titlebar, the top/bottom // border strip's own rounded-corner bitmap, both built by `redraw_ - // decoration_buffer`, itself only called on a real client *commit*, - // not on every resize step) uses this rect's width/height to size - // the `src` crop rectangle it samples that bitmap with. Making this + // decoration_buffer`) uses this rect's width/height to size the + // `src` crop rectangle it samples that bitmap with. Making this // function return the *live* drag target while the underlying // bitmap was still sized for whatever the *last commit* actually - // was means that crop can end up larger than the real bitmap's own - // stored dimensions - `MemoryRenderBufferRenderElement::from_ + // was meant that crop could end up larger than the real bitmap's + // own stored dimensions - `MemoryRenderBufferRenderElement::from_ // buffer` does not validate `src` against the texture's real size, // so an oversized crop reads as an out-of-bounds texture sample - // (stretched/repeated/garbage pixels, not a clean error) for as - // long as a fast resize keeps outrunning the client's own recommit - // rate - a worse, more visibly broken failure mode than the - // one-frame-stale lag it replaced. Fixing the lag properly needs - // `redraw_decoration_buffer` itself rebuilding on every resize - // step, not just on commit, which is real, separate scope - not - // yet done. + // (stretched/repeated/garbage pixels, not a clean error). + // + // Now reinstated, safely: this returns the *live* drag target + // (`geom`, unmodified) while a resize of this specific window is + // active, same as the reverted attempt did - but two things are + // different this time, together closing the gap that made it + // unsafe rather than just re-taking the risk: + // 1. `input::pointer::handle_pointer_position` now calls + // `redraw_decoration_buffer` on every resize motion tick (see + // its own doc comment), not just on a real client commit, so + // the bitmap itself keeps catching up to this same live value + // almost every frame instead of staying pinned to the last + // commit for the resize's whole duration. + // 2. Independent of how well-synced that keeps the two, every + // render-loop call site that turns this rect's width/height + // into a `src` crop now clamps it against `decoration_ + // signatures`' own recorded `(width, height)` - the bitmap's + // own *actual* last-built size, tracked there already for + // unrelated caching reasons - before handing it to `from_ + // buffer`. That clamp is what actually prevents the out-of- + // bounds read now, structurally, regardless of any remaining + // timing gap between this function and the next rebuild; this + // branch existing is what keeps that gap small in practice + // rather than a full commit-cycle wide. + if wm.borrow().resizing_window() == Some(id) { + return geom; + } let Some(w) = wm.borrow().window(id).cloned() else { return geom }; let Some(dwindow) = id_to_window.get(&id) else { return geom }; // `dwindow.geometry()` - `xdg_surface::set_window_geometry` - is, diff --git a/crates/wayland/src/state/mod.rs b/crates/wayland/src/state/mod.rs index a3e9b1a..0ef4346 100644 --- a/crates/wayland/src/state/mod.rs +++ b/crates/wayland/src/state/mod.rs @@ -417,6 +417,20 @@ pub(crate) struct CompState { /// own blanket call, which never actually checked whether this /// specific window was one of the windows that triggered the tick. pub(crate) decoration_signatures: HashMap, + /// When `handle_pointer_position` last called `redraw_decoration_buffer` + /// for the window currently being interactively resized - throttles + /// that call to once per `RESIZE_REDRAW_INTERVAL` (see `input::pointer`), + /// since a pointer + /// can emit motion events far faster than a titlebar's text and border + /// bitmaps are worth re-rasterizing. Without this the decoration buffer + /// only catches up with `effective_frame_of`'s now-live resize geometry + /// (see that function's own doc comment) once the drag ends and the + /// blanket `sync()` redraw runs - correct, but visibly laggy borders + /// for the whole drag. `None` whenever no resize is in progress; reset + /// there rather than left stale, so a *new* resize's first motion event + /// always redraws immediately instead of inheriting a stale timestamp + /// from a previous drag. + pub(crate) resize_redraw_at: Option, /// Which titlebar button (if any) the pointer is currently over, on /// which window, and *when that hover started* - set from `handle_ /// pointer_position`'s own `hit_test` result, read by `redraw_ -- cgit v1.2.3