Auto-throttle visualizer under sustained Opus decode pressure; strip streaming investigation instrumentation
This commit is contained in:
@@ -44,6 +44,8 @@
|
||||
* position while !isPlaying). The loop stops only on tab-hidden (visibilitychange) and dispose.
|
||||
*/
|
||||
|
||||
import { decodePressure } from '../audio/decodePressure.js';
|
||||
|
||||
// ── Tuning anchors (see spec §B). These are the load-bearing constants. ──────────
|
||||
|
||||
/**
|
||||
@@ -148,6 +150,16 @@ const RIBBON_HALF_WIDTH_FRAC = 0.92;
|
||||
*/
|
||||
const MAX_DPR = 2;
|
||||
|
||||
/**
|
||||
* Minimum milliseconds between drawn frames WHILE decode is under sustained pressure (Part 1 —
|
||||
* auto-protect audio). 1000/15 ≈ 66.7 ms caps the loop at ~15 fps, cutting the main-thread WebGL
|
||||
* software-render + physics cost by ~75% so the synchronous WebCodecs Opus decode (which shares the
|
||||
* main thread when HW accel is off) gets the time it needs to keep up. The decodePressure signal is
|
||||
* false in the common case (HW accel on, or lossless), so this cap never applies and the loop draws
|
||||
* every frame at full quality. Tunable; the exact fps that clears starvation is browser-confirmed.
|
||||
*/
|
||||
const PRESSURE_THROTTLE_FRAME_MS = 1000 / 15;
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════
|
||||
// R2 — the wax-blob lava physics (CPU step + uniform upload). The lava is now a real
|
||||
// Lagrangian particle system integrated each frame on the JS side and rendered as
|
||||
@@ -1679,6 +1691,10 @@ export function create(canvas: HTMLCanvasElement): WaveformVisualizerHandle {
|
||||
let rafId: number | null = null;
|
||||
let disposed = false;
|
||||
const startTimeMs = performance.now();
|
||||
// Wall-clock of the last DRAWN continuous-loop frame, for the decode-pressure throttle (Part 1).
|
||||
// While decodePressure.isUnderPressure() the loop draws at most once per PRESSURE_THROTTLE_FRAME_MS
|
||||
// so the main thread yields time back to a starved decode; unthrottled it draws every frame.
|
||||
let lastDrawMs = performance.now();
|
||||
// Wall-clock anchor for the physics dt (separate from the playhead decay clock).
|
||||
let lastPhysicsMs = performance.now();
|
||||
|
||||
@@ -1923,9 +1939,30 @@ export function create(canvas: HTMLCanvasElement): WaveformVisualizerHandle {
|
||||
rafId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-protect audio under decode pressure (Part 1). When the WebCodecs Opus decode pipeline
|
||||
// reports SUSTAINED lag (decodePressure.isUnderPressure()), throttle the draw cadence to
|
||||
// ~PRESSURE_THROTTLE_FRAME_MS so this loop's main-thread GL + physics cost yields time back to
|
||||
// decode; we still reschedule every frame so full cadence resumes the instant decode recovers.
|
||||
// A no-op when decode is healthy — isUnderPressure() stays false, the gate is always open, and
|
||||
// every frame draws exactly as before. Skipping a draw also skips the physics step (it runs
|
||||
// inside draw()), and its dt is clamped to PHYSICS_MAX_DT, so a throttled gap never lurches the
|
||||
// lava. redrawOnce() (idle/control-tweak stills) is intentionally NOT throttled — those are rare
|
||||
// one-shots, not the continuous loop.
|
||||
const nowMs = performance.now();
|
||||
if (!decodePressure.isUnderPressure() || nowMs - lastDrawMs >= PRESSURE_THROTTLE_FRAME_MS) {
|
||||
lastDrawMs = nowMs;
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
/** One drawn continuous-loop frame: the GL draw plus the gated FPS/lava diagnostic tally. */
|
||||
function drawFrame(): void {
|
||||
draw();
|
||||
|
||||
// FPS tally: count this callback, and once per elapsed second emit the rate.
|
||||
// FPS tally: count this drawn frame, and once per elapsed second emit the rate.
|
||||
// performance.now() is cheap (no GPU stall, unlike gl.getError); the gated log
|
||||
// fires at most once/sec, so this adds no meaningful per-frame cost.
|
||||
if (DEBUG) {
|
||||
@@ -1968,10 +2005,6 @@ export function create(canvas: HTMLCanvasElement): WaveformVisualizerHandle {
|
||||
fpsWindowStartMs = nowMs;
|
||||
}
|
||||
}
|
||||
|
||||
// Reschedule unconditionally — the loop runs continuously now (lava reframe Part C); it is
|
||||
// stopped only by dispose() or the tab going hidden, never by audio pausing.
|
||||
rafId = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
// ── Tab-visibility gating (lava reframe Part C power-saving). ────────────────────
|
||||
|
||||
Reference in New Issue
Block a user