perf(qt): stage visualizer publishes, commit them in the tick (macOS F1b)

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
This commit is contained in:
vicrodh committed 2026-08-02 10:22:06 -06:00
1 parent 447efe2cd7
commit f2be1ccee8
1 file changed
+22 -3
+22 -3
View File
@@ -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