revert(qt): restore the 16ms settle driver — frequency lever backfires

Measured on the Mac (samples + QSG_RENDER_TIMING, NPB Large, ambient
off): F0 62fps/155ms-s render / ~2.5ms-frame -> F1 51fps/246ms-s /
~4.8ms -> F1b 29fps/~277ms-s / ~9.5ms. Every frequency cut made each
frame MORE expensive faster than the rate dropped — per-frame cost on
Apple Silicon is dominated by wake/scheduling/power-state overhead
(parking the render thread between frames), not by scene work. The
best measured state is the ORIGINAL 16ms driver, so restore it (this
reverts 9db2176d2 and f2be1cce; the renderer-preference commit stays —
it is a verified no-op here).

The lever that remains is per-frame SCENE cost (F2 render passes /
F3 single-draw spectrum), which is also where the ~6x vs Linux lives
(setShaderResources per batch on Qt's Metal path).

km
This commit is contained in:
vicrodh committed 2026-08-02 10:51:27 -06:00
1 parent f2be1ccee8
commit 273325f151
1 file changed
+10 -37
+10 -37
View File
@@ -29,15 +29,10 @@
// `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 33 ms Timer, not a FrameAnimation. The producer publishes
// - The DRIVER IS A 16 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. 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.
// it. ~62 Hz keeps two interpolated steps per published frame, which is all
// the motion there is to buy.
// - 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
@@ -76,16 +71,6 @@ Item {
property var to: []
property real progress: 1.0
// Staged publish state: written by onTargetChanged, committed to the bound
// outputs ONLY inside the driver tick. A publish must NEVER touch
// from/to/progress directly — those are at()'s ONLY dependencies, so every
// direct write is a full-window invalidation of its own. Measured on macOS
// (2026-08-02, QSG_RENDER_TIMING on the F1 build): publish-write + tick-
// write = 2 invalidations per period ≈ 51 fps; committing in the tick
// makes it 1 ≈ 30 fps.
property var pendFrom: []
property var pendTo: []
// Publish period estimate, ms. Seeded at 1000/TARGET_FPS and tracked with a
// slow EMA so a producer that drifts off 30 Hz still lands on time.
property real periodMs: 33.3
@@ -143,15 +128,10 @@ Item {
for (var i = 0; i < n; ++i)
arr[i] = t[i];
}
// STAGE ONLY — do not touch from/to/progress here (see `pendFrom`:
// every direct write to at()'s dependencies is its own full-window
// invalidation; the tick commits and advances in ONE visual write).
// `progress` is deliberately NOT reset: at() keeps showing the settled
// old frame until the tick, which is pixel-identical to progress=0
// holding `from`.
settle.pendFrom = settle.to;
settle.pendTo = arr;
settle.from = settle.to;
settle.to = arr;
settle.lastTickMs = now;
settle.progress = 0.0;
if (!driver.running)
driver.running = true;
}
@@ -168,12 +148,9 @@ 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. 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; }`,
// 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; }`,
// SidebarNowPlayingDock.slint:45).
//
// `repeat: true` is MANDATORY — Timer defaults to false, and without it the
@@ -186,15 +163,11 @@ Item {
// missed tick degrades to a hold, never a jump.
Timer {
id: driver
interval: 33
interval: 16
repeat: true
running: false
onTriggered: {
var p = (Date.now() - settle.lastTickMs) / settle.periodMs;
// Commit the staged publish and advance in ONE write set — the
// single visual invalidation per period (~30/s), not two.
settle.from = settle.pendFrom;
settle.to = settle.pendTo;
if (p >= 1.0) {
settle.progress = 1.0;
driver.running = false; // park until the next publish