app: add App::subsurface_motion_only so a slide-to-reveal panel animates without re-rastering the main surface
An app whose gesture/animation only moves an input-transparent subsurface (a slide-to-reveal panel over a static main surface) still paid a full main-surface re-raster on every frame of the slide, because the runtime force-dirties the main view on two paths it cannot tell apart from a real content change: `MoveOutcome::Swipe` calls `dirty_caches()` + `request_redraw()` per motion sample, and the `WlCallback` Main handler sets `view_dirty` + `request_redraw()` on every frame callback while `is_animating`. The subsurface reconcile already repositions the panel with a cheap `set_position` + bare parent commit, so the main re-raster is wasted work — and on slow targets (Librem5) it competes with the reposition and makes the slide stutter. New opt-in `App::subsurface_motion_only` (default `false`, so existing apps are unaffected). When it returns `true`: - the swipe dispatch (`input/dispatch/outcomes.rs`) still calls the `on_swipe_progress` family but skips `dirty_caches()` / `request_redraw()`; incoming motion events pump the loop and the per-iteration `reconcile_subsurfaces` carries the move. - the frame-callback animation pump (`event_loop/handlers.rs`) keeps the vsync cadence without a re-raster by requesting a bare `wl.frame()` + `commit()` on the main surface (no buffer attach) instead of dirtying the view; `poll_external` advances the animation and the reconcile repositions each frame. The main surface still redraws for genuine content changes (messages, resize, the one-shot redraw on swipe release) — only the per-frame slide raster is dropped.
This commit is contained in:
11
src/app.rs
11
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`.
|
/// The event loop will keep requesting redraws at ~60 fps until this returns `false`.
|
||||||
fn is_animating( &self ) -> bool { 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
|
/// Return `true` while the next frame should swap the expensive
|
||||||
/// Glass passes for cheap fallbacks — currently the
|
/// Glass passes for cheap fallbacks — currently the
|
||||||
/// `backdrop-filter` blur drops from a 41-tap kernel to a 9-tap
|
/// `backdrop-filter` blur drops from a 41-tap kernel to a 9-tap
|
||||||
|
|||||||
@@ -620,8 +620,24 @@ impl<A: App> Dispatch<WlCallback, super::SurfaceFocus> for AppData<A>
|
|||||||
state.main.frame_pending = false;
|
state.main.frame_pending = false;
|
||||||
if is_animating
|
if is_animating
|
||||||
{
|
{
|
||||||
state.view_dirty = true;
|
if state.app.subsurface_motion_only()
|
||||||
state.main.request_redraw();
|
{
|
||||||
|
// 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 ) =>
|
super::SurfaceFocus::Overlay( id ) =>
|
||||||
|
|||||||
@@ -50,12 +50,18 @@ impl<A: App> AppData<A>
|
|||||||
if let Some( v ) = down { self.app.on_swipe_down_progress( v ); }
|
if let Some( v ) = down { self.app.on_swipe_down_progress( v ); }
|
||||||
if let Some( v ) = horizontal { self.app.on_swipe_horizontal_progress( v ); }
|
if let Some( v ) = horizontal { self.app.on_swipe_horizontal_progress( v ); }
|
||||||
// Swipe-progress callbacks mutate app state outside
|
// Swipe-progress callbacks mutate app state outside
|
||||||
// `update`, so the cached view tree is stale.
|
// `update`, so the cached view tree is stale — unless the
|
||||||
self.dirty_caches();
|
// motion only slides a subsurface over a static main
|
||||||
self.surface_mut( focus ).request_redraw();
|
// surface, in which case the per-frame subsurface reconcile
|
||||||
for ss in self.overlays.values_mut()
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user