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:
1 file changed
+10
-37
@@ -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
|
||||
|
||||
Reference in new issue
Block a user