perf(qt): throttle the visualizer settle driver to 33 ms (macOS F1)

The 2026-08-02 macOS measurement (2026-08-02-macos-perf-fix-plan.md)
named the actual burn: not the backend (real Metal, DPR 1, GPU at
50-57%) but per-tick invalidation. The VizSettle driver at 16 ms
invalidated the scene ~62x/s; Qt Quick has no partial damage, so each
tick re-rendered the whole 3440x1440 window (~2 Metal render passes),
~6x pricier per frame on the Metal path than on Linux/Mesa. Paused:
0%. The producer publishes at TARGET_FPS = 30 and the Rust EMA
band-limits the signal to ~6 Hz, so 33 ms keeps one interpolated step
per published frame — the render bill halves and the motion difference
is at the edge of perception. Self-parking, the live gate and the Rust
side are untouched. Expected on the Mac: ~30% -> ~15-18% CPU at NPB
Large playing.

km
This commit is contained in:
vicrodh committed 2026-08-02 00:30:26 -06:00
1 parent 7c9e4f1878
commit 9db2176d2e
1 file changed
+15 -7
+15 -7
View File
@@ -29,10 +29,15 @@
// `progress` changes per tick. `to` is materialised into a plain JS array
// ONCE per publish (30/s) so `at()` does not cross into the QList_f32
// sequence wrapper twice per bar per tick — see `onTargetChanged`.
// - The DRIVER IS A 16 ms Timer, not a FrameAnimation. The producer publishes
// - The DRIVER IS A 33 ms Timer, not a FrameAnimation. The producer publishes
// at 30/s; a per-frame trigger fired 180x/s on Linux (100x/s on macOS) for
// it. ~62 Hz keeps two interpolated steps per published frame, which is all
// the motion there is to buy.
// it. It was 16 ms (~62 Hz, two interpolated steps per published frame)
// until the 2026-08-02 macOS measurement (2026-08-02-macos-perf-fix-plan.md
// §1): each tick INVALIDATES THE WHOLE WINDOW — Qt Quick has no partial
// damage — so every driver Hz costs a full-window re-render (~2 passes),
// ~6x pricier per frame on Qt's Metal path than on Linux/Mesa. 33 ms keeps
// one interpolated step per published frame: the render bill halves and
// the motion difference is at the edge of perception.
// - SELF-PARKING, exactly like the Rust drain: the driver starts on a publish
// and stops itself the moment `progress` reaches 1 (within one 33 ms period
// of the last publish). A paused player, a hidden band or a disabled tap
@@ -148,9 +153,12 @@ Item {
// removes). The TRIGGER, however, must not run at the frame rate either —
// the producer publishes at TARGET_FPS = 30 (qbz-audio visualizer/mod.rs:32)
// and FrameAnimation fired 180x/s on Linux and 100x/s on macOS for it,
// i.e. 3-6x the frames the data justifies. 16 ms = ~62 Hz gives two
// interpolated steps per published frame, which is what buys the motion
// (Slint's equivalent is `animate bar-h { duration: 90ms; easing: ease; }`,
// i.e. 3-6x the frames the data justifies. 33 ms = ~30 Hz gives one
// interpolated step per published frame — halved from the original 16 ms
// after the 2026-08-02 macOS measurement showed every tick pays a
// full-window re-render (Qt Quick has no partial damage; ~2 Metal render
// passes at 3440x1440). The motion kept is what buys the effect (Slint's
// equivalent is `animate bar-h { duration: 90ms; easing: ease; }`,
// SidebarNowPlayingDock.slint:45).
//
// `repeat: true` is MANDATORY — Timer defaults to false, and without it the
@@ -163,7 +171,7 @@ Item {
// missed tick degrades to a hold, never a jump.
Timer {
id: driver
interval: 16
interval: 33
repeat: true
running: false
onTriggered: {