From f2be1ccee850395cd88d52df4c349565962e86cf Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:22:06 -0600 Subject: [PATCH] perf(qt): stage visualizer publishes, commit them in the tick (macOS F1b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QSG_RENDER_TIMING on the F1 build (Mac, NPB Large, ambient off, 3.5 min of playback) showed the F1 throttle only bought 62 -> ~51 fps, not 30: the producer publishes at ~30 Hz AND each publish wrote from/to/ progress directly — at()'s only dependencies — so every 33 ms period invalidated the window TWICE (publish + tick). ~51 fps x ~3.6 ms render kept the render thread at ~20-25% of a core. Stage the publish into pendFrom/pendTo (which nothing visual binds to) and commit + advance in the driver tick: ONE visual write per period. Pixel-identical output (the bars hold the settled old frame until the tick, exactly what progress=0 showed), ~30 fps instead of ~51. Audits green (resolution + singleton xref), build green. km --- crates/qbz-qt/qml/shell/VizSettle.qml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/crates/qbz-qt/qml/shell/VizSettle.qml b/crates/qbz-qt/qml/shell/VizSettle.qml index b3c79c1ef..a59a37547 100644 --- a/crates/qbz-qt/qml/shell/VizSettle.qml +++ b/crates/qbz-qt/qml/shell/VizSettle.qml @@ -76,6 +76,16 @@ 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 @@ -133,10 +143,15 @@ Item { for (var i = 0; i < n; ++i) arr[i] = t[i]; } - settle.from = settle.to; - settle.to = arr; + // 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.lastTickMs = now; - settle.progress = 0.0; if (!driver.running) driver.running = true; } @@ -176,6 +191,10 @@ Item { 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