From e32576316e4386c28e1fa547cbf1b8776a162f56 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:18:30 -0600 Subject: [PATCH] perf(ui): cache-rendering-hint on static shell chrome (#617) cache-rendering-hint: true on HeaderBar, NowPlayingBar and Sidebar roots so femtovg caches those subtrees to a persistent texture instead of re-tessellating them on every repaint. Sidebar root is safe: the Large-NPB spectrum dock is an AppShell overlay, not a Sidebar child. SMOKE-WATCH: playlists-list scroll (Flickable-inside-cache); narrow to nav-only if it regresses. Authored by Claude (cc) in the repaint-cache-hint worktree; committed as-is by km so both #617 lines share one canonical commit. km --- crates/qbz-ui/ui/shell/HeaderBar.slint | 9 +++++++++ crates/qbz-ui/ui/shell/NowPlayingBar.slint | 11 +++++++++++ crates/qbz-ui/ui/shell/Sidebar.slint | 15 +++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/crates/qbz-ui/ui/shell/HeaderBar.slint b/crates/qbz-ui/ui/shell/HeaderBar.slint index 317861870..f27c37707 100644 --- a/crates/qbz-ui/ui/shell/HeaderBar.slint +++ b/crates/qbz-ui/ui/shell/HeaderBar.slint @@ -548,6 +548,15 @@ export component HeaderBar inherits Rectangle { // `header-menu-navigate` route handler the dropdown menus use. callback navigate(string /* route */); + // #617 per-frame cost: femtovg has no partial rendering, so the animated + // now-playing equalizer bars repaint the WHOLE window ~8x/sec. This header is + // static at idle (no internal scroll; the search cortinilla is a separate + // AppShell overlay), so cache its subtree to a persistent texture: every + // repaint then skips the header's tree-walk + path re-tessellation and blits + // the cache instead. Invalidates only when something inside changes (typing, + // hover, resize) — not per frame. See spec 2026-07-19-cpu-idle-repaint-617 §9.2. + cache-rendering-hint: true; + height: Layout.header-height; background: ShellState.app-background-active ? Theme.surface-card.with-alpha(AppearanceState.app-background-surface-alpha) diff --git a/crates/qbz-ui/ui/shell/NowPlayingBar.slint b/crates/qbz-ui/ui/shell/NowPlayingBar.slint index 77ccbe147..dd1b256f0 100644 --- a/crates/qbz-ui/ui/shell/NowPlayingBar.slint +++ b/crates/qbz-ui/ui/shell/NowPlayingBar.slint @@ -19,6 +19,17 @@ export component NowPlayingBar inherits Rectangle { callback media-action(string, string, string); callback settings-bool(string, bool); + // #617 per-frame cost: the always-visible bottom bar is a high-item-count + // subtree (SongCard cover, seek line, transport controls, quality badges, + // volume) that is static except for the ~1 Hz seek push (verified: SeekBar's + // only animates are hover-opacity one-shots; progress = whole-second position). + // Caching it drops the bar's tree-walk + path re-tessellation from ~8x/sec + // (driven by the eq-bar full-window repaints) to ~1x/sec; the other 7 frames + // blit the cached texture. See spec 2026-07-19-cpu-idle-repaint-617 §9.2. + // NOTE: Large mode's sidebar spectrum dock is NOT inside this component; it is + // in Sidebar and must stay un-cached (30 fps visualizer). + cache-rendering-hint: true; + // EXPLICIT mode-aware height on the wrapper itself. The bottom bar lives in // AppShell's outer VerticalLayout slot, which sizes a plain Rectangle child // to its CONTENT height — and a Rectangle does NOT auto-adopt a conditional diff --git a/crates/qbz-ui/ui/shell/Sidebar.slint b/crates/qbz-ui/ui/shell/Sidebar.slint index b7ce75f73..d484929ba 100644 --- a/crates/qbz-ui/ui/shell/Sidebar.slint +++ b/crates/qbz-ui/ui/shell/Sidebar.slint @@ -693,6 +693,21 @@ export component Sidebar inherits Rectangle { clip: true; animate width { duration: 160ms; easing: ease-in-out; } + // #617 per-frame cost: cache this whole subtree to a persistent texture so + // the eq-bar's ~8x/sec full-window repaints skip the sidebar's tree-walk + + // path re-tessellation (nav rows + playlists list) and blit the cache instead. + // SAFE to cache the root here: the animated Large-NPB spectrum dock is NOT a + // child of the Sidebar — it is an AppShell-root overlay (see the dock-band + // RESERVATION comment ~line 1120); everything actually inside the Sidebar is + // static at idle (the scrollbar thumb only animates on hover; the sort/context + // menus are separate PopupWindow surfaces, not part of this texture). + // Invalidates on: playlist scroll (transient), width toggle (160ms transient), + // hover, list changes. SMOKE-WATCH: scroll the playlists list and confirm no + // clipping/offset artifact from the Flickable-inside-cache interaction; if it + // regresses, narrow the hint to the static nav section only. + // See spec 2026-07-19-cpu-idle-repaint-617 §9.2. + cache-rendering-hint: true; + VerticalLayout { // Per-side padding so Large can zero ONLY the bottom: the reserved dock // band (and the AppShell overlay aligned to it) then reaches the true