perf(qt): one repaint pulse for the whole shell (GPU 95% -> 25%)

Qt Quick has no partial redraws: any dirty item presents the WHOLE
window, and on the owner's hybrid laptop each full-window present costs
~1.2% GPU flat (KWin composite + dGPU scanout; area-independent, while
the app's own render is 0.08 ms/frame). The bill was presents/s, and
the shell ran unsynchronised ~30 Hz clocks plus two leaks:

- QbzShell.pulseMs: ONE Rust-driven 33 ms edge that every continuous
  animator ticks on (knob QBZ_PULSE_MS). VizSettle now stashes on
  publish and applies on the pulse (its 16 ms interpolation timer is
  gone); AmbientField and ImmersiveAtmosphere ride the same edge.
- VizSettle writes NOTHING while not live: the always-mounted but
  invisible immersive panels were dirtying 28 bar rects per FFT
  publish (named via QT_LOGGING_RULES=qt.quick.dirty=true).
- AmbientField renders inline again: ShaderEffectSource
  scheduleUpdate() double-presents (minimal repro fbo_repro.qml in the
  investigation folder).
- Background mode 2 (ImmersiveAtmosphere) composites its four drifting
  layers + base + dim + scrim in ONE opaque ShaderEffect
  (atmosphere.frag, baked by build.rs); numeric parity vs the Image
  stack verified (atmo-shader-parity-check.py, delta 0.0). The
  four-Image stack stays as the software/failure fallback.

Measured on the owner's machine: 93-97% -> 25-26% GPU, ~34 W -> 12-13 W,
~33 presents/s in both background modes. Full story:
qbz-nix-docs/qt-frontend/2026-08-11-scenegraph-batches/GPU-COST-INVESTIGATION.md

km
This commit is contained in:
vicrodh committed 2026-08-13 13:01:50 -06:00
1 parent b136c9cf5d
commit f46c5c95d4
9 files changed
+814 -259

No files matched your search

+5 -3
View File
@@ -17,9 +17,9 @@
// the fill is the SAME vertical topColor->bottomColor gradient plus the 1px
// radius the delegates had (rounded-rect SDF with a 1px AA edge, including
// Rectangle's clamp of r to half the short side). Heights are pushed in ONE
// JS pass per VizSettle re-evaluation — the push hooks the exact same
// JS pass per VizSettle application — the push hooks the exact same
// from/to/progress triplet the delegates' at(i) bindings used to capture —
// so a 16 ms tick dirties ONE node instead of 28-48. No Canvas anywhere:
// so a shell-pulse edge dirties ONE node instead of 28-48. No Canvas anywhere:
// Canvas is CPU raster and is banned on this path.
//
// THE SHADER IS A .qsb, NOT AN INLINE STRING. Qt 6's ShaderEffect takes the
@@ -104,7 +104,9 @@ Rectangle {
visible: band.shown
radius: theme.radiusMd
color: "#59000000"
clip: true
// No clip: the shader arm is anchors.fill, and the fallback bars take
// settle.at(i) hard-clamped to 0..1 (VizSettle.qml:86-91) against a
// hardcoded 42px band, so nothing can poke out.
// Album-derived gradient, reusing the ambient triad (ambient_qt.rs) instead
// of a second album-color pipeline — the Slint dock reads