/** * On-screen FPS meter. * * Driven from the viewer's animate loop: call `sample(now)` once per frame with * the requestAnimationFrame timestamp. It computes an instantaneous fps from the * inter-frame delta, smooths it into a rolling average (EMA), and throttles DOM * writes to ~every 250ms so updating the overlay doesn't itself cause jank. * Kept dependency-free in the src/ui/progress.ts style. */ const DOM_INTERVAL_MS = 250; // EMA smoothing factor: higher = more responsive, lower = smoother. const EMA_ALPHA = 0.1; export interface FpsMeter { /** Overlay element; the viewer appends this to its container. */ el: HTMLElement; /** Feed one frame timestamp (DOMHighResTimeStamp). */ sample(now: number): void; /** Current rolling-average fps. */ fps(): number; } /** Create an FPS overlay element + sampler. */ export function createFpsMeter(): FpsMeter { const el = document.createElement("div"); el.className = "fps"; el.textContent = "-- FPS"; let last = 0; let avg = 0; let lastDom = 0; return { el, sample(now: number): void { if (last === 0) { last = now; return; } const dt = now - last; last = now; if (dt <= 0) return; const inst = 1000 / dt; avg = avg === 0 ? inst : avg + EMA_ALPHA * (inst - avg); if (now - lastDom >= DOM_INTERVAL_MS) { lastDom = now; const v = Math.round(avg); el.textContent = `${v} FPS`; el.style.color = v >= 60 ? "#5cff5c" : v >= 30 ? "#ffb74a" : "#ff5c5c"; } }, fps(): number { return avg; }, }; }