From d221d5a0cd29e1277944a9be3fe9d8387e88ae4d Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Mon, 1 Jun 2026 21:53:40 +0200 Subject: [PATCH] =?UTF-8?q?app:=20add=20`App::subsurface=5Fmotion=5Fonly`?= =?UTF-8?q?=20so=20a=20slide-to-reveal=20panel=20animates=20without=20re-r?= =?UTF-8?q?astering=20the=20main=20surface=20An=20app=20whose=20gesture/an?= =?UTF-8?q?imation=20only=20moves=20an=20input-transparent=20subsurface=20?= =?UTF-8?q?(a=20slide-to-reveal=20panel=20over=20a=20static=20main=20surfa?= =?UTF-8?q?ce)=20still=20paid=20a=20full=20main-surface=20re-raster=20on?= =?UTF-8?q?=20every=20frame=20of=20the=20slide,=20because=20the=20runtime?= =?UTF-8?q?=20force-dirties=20the=20main=20view=20on=20two=20paths=20it=20?= =?UTF-8?q?cannot=20tell=20apart=20from=20a=20real=20content=20change:=20`?= =?UTF-8?q?MoveOutcome::Swipe`=20calls=20`dirty=5Fcaches()`=20+=20`request?= =?UTF-8?q?=5Fredraw()`=20per=20motion=20sample,=20and=20the=20`WlCallback?= =?UTF-8?q?`=20Main=20handler=20sets=20`view=5Fdirty`=20+=20`request=5Fred?= =?UTF-8?q?raw()`=20on=20every=20frame=20callback=20while=20`is=5Fanimatin?= =?UTF-8?q?g`.=20The=20subsurface=20reconcile=20already=20repositions=20th?= =?UTF-8?q?e=20panel=20with=20a=20cheap=20`set=5Fposition`=20+=20bare=20pa?= =?UTF-8?q?rent=20commit,=20so=20the=20main=20re-raster=20is=20wasted=20wo?= =?UTF-8?q?rk=20=E2=80=94=20and=20on=20slow=20targets=20(Librem5)=20it=20c?= =?UTF-8?q?ompetes=20with=20the=20reposition=20and=20makes=20the=20slide?= =?UTF-8?q?=20stutter.=20New=20opt-in=20`App::subsurface=5Fmotion=5Fonly`?= =?UTF-8?q?=20(default=20`false`,=20so=20existing=20apps=20are=20unaffecte?= =?UTF-8?q?d).=20When=20it=20returns=20`true`:=20-=20the=20swipe=20dispatc?= =?UTF-8?q?h=20(`input/dispatch/outcomes.rs`)=20still=20calls=20the=20`on?= =?UTF-8?q?=5Fswipe=5Fprogress`=20family=20but=20skips=20`dirty=5Fcaches()?= =?UTF-8?q?`=20/=20`request=5Fredraw()`;=20incoming=20motion=20events=20pu?= =?UTF-8?q?mp=20the=20loop=20and=20the=20per-iteration=20`reconcile=5Fsubs?= =?UTF-8?q?urfaces`=20carries=20the=20move.=20-=20the=20frame-callback=20a?= =?UTF-8?q?nimation=20pump=20(`event=5Floop/handlers.rs`)=20keeps=20the=20?= =?UTF-8?q?vsync=20cadence=20without=20a=20re-raster=20by=20requesting=20a?= =?UTF-8?q?=20bare=20`wl.frame()`=20+=20`commit()`=20on=20the=20main=20sur?= =?UTF-8?q?face=20(no=20buffer=20attach)=20instead=20of=20dirtying=20the?= =?UTF-8?q?=20view;=20`poll=5Fexternal`=20advances=20the=20animation=20and?= =?UTF-8?q?=20the=20reconcile=20repositions=20each=20frame.=20The=20main?= =?UTF-8?q?=20surface=20still=20redraws=20for=20genuine=20content=20change?= =?UTF-8?q?s=20(messages,=20resize,=20the=20one-shot=20redraw=20on=20swipe?= =?UTF-8?q?=20release)=20=E2=80=94=20only=20the=20per-frame=20slide=20rast?= =?UTF-8?q?er=20is=20dropped.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 11 +++++++++++ src/event_loop/handlers.rs | 20 ++++++++++++++++++-- src/input/dispatch/outcomes.rs | 16 +++++++++++----- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3ed2ee4..69cf858 100644 --- a/src/app.rs +++ b/src/app.rs @@ -657,6 +657,17 @@ pub trait App: 'static /// The event loop will keep requesting redraws at ~60 fps until this returns `false`. fn is_animating( &self ) -> bool { false } + /// Return `true` when a finger-tracked swipe or an [`Self::is_animating`] + /// slide only repositions the app's input-transparent subsurfaces + /// ([`Self::subsurfaces`]) while the main surface buffer stays unchanged. + /// The runtime then skips the per-frame main re-raster that + /// [`Self::on_swipe_progress`] and `is_animating` would otherwise force, + /// keeping the animation pumped (motion events during the drag, a bare + /// frame callback while `is_animating`) and letting the per-frame + /// subsurface reconcile carry the motion. Use it for a slide-to-reveal + /// panel over a static main surface. + fn subsurface_motion_only( &self ) -> bool { false } + /// Return `true` while the next frame should swap the expensive /// Glass passes for cheap fallbacks — currently the /// `backdrop-filter` blur drops from a 41-tap kernel to a 9-tap diff --git a/src/event_loop/handlers.rs b/src/event_loop/handlers.rs index 1f35282..8b3a01b 100644 --- a/src/event_loop/handlers.rs +++ b/src/event_loop/handlers.rs @@ -620,8 +620,24 @@ impl Dispatch for AppData state.main.frame_pending = false; if is_animating { - state.view_dirty = true; - state.main.request_redraw(); + if state.app.subsurface_motion_only() + { + // The main buffer is unchanged; only the subsurface + // moves (repositioned by the per-frame reconcile). + // Keep the vsync cadence with a bare frame callback + + // commit — no buffer attach, no re-raster. + if let Some( wl ) = state.main.surface.try_wl_surface().cloned() + { + let _ = wl.frame( &state.qh, super::SurfaceFocus::Main ); + wl.commit(); + state.main.frame_pending = true; + } + } + else + { + state.view_dirty = true; + state.main.request_redraw(); + } } } super::SurfaceFocus::Overlay( id ) => diff --git a/src/input/dispatch/outcomes.rs b/src/input/dispatch/outcomes.rs index 4722892..e274c26 100644 --- a/src/input/dispatch/outcomes.rs +++ b/src/input/dispatch/outcomes.rs @@ -50,12 +50,18 @@ impl AppData if let Some( v ) = down { self.app.on_swipe_down_progress( v ); } if let Some( v ) = horizontal { self.app.on_swipe_horizontal_progress( v ); } // Swipe-progress callbacks mutate app state outside - // `update`, so the cached view tree is stale. - self.dirty_caches(); - self.surface_mut( focus ).request_redraw(); - for ss in self.overlays.values_mut() + // `update`, so the cached view tree is stale — unless the + // motion only slides a subsurface over a static main + // surface, in which case the per-frame subsurface reconcile + // carries the move and a main re-raster is wasted work. + if !self.app.subsurface_motion_only() { - ss.request_redraw(); + self.dirty_caches(); + self.surface_mut( focus ).request_redraw(); + for ss in self.overlays.values_mut() + { + ss.request_redraw(); + } } } }