fix(viewer): drive the frame loop with a timer when ?disable=draw (#483)
With no real frames submitted, Chromium's no-damage scheduler throttles requestAnimationFrame to 1Hz on Linux — measured in the headless bake worker: every useFrame system ticked once per second, so a heavy scene needed 300+ seconds of wall time just to run ~300 frames of build work (and exceeded the capture deadline on the worker's slower cores). The empty-scene clear from #482 avoids this on macOS but not on Linux. When `draw` is disabled, drive advance() with setInterval instead of rAF — plain timers are never throttled on a visible page, so the loop pacing is deterministic (fps prop) regardless of compositor heuristics. Normal rendering paths are untouched. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bbe0e2b0b5
commit
aa30899090
@@ -6,6 +6,20 @@ type FrameLimiterProps = {
|
|||||||
fps?: number
|
fps?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `?disable=draw` (see post-processing.tsx): the page renders no real frames,
|
||||||
|
// and Chromium's no-damage scheduler then throttles requestAnimationFrame to
|
||||||
|
// 1Hz on Linux (measured in the headless bake worker — every useFrame system
|
||||||
|
// ticked once per second and a heavy scene took 300+ seconds to settle). A
|
||||||
|
// plain timer is never throttled on a visible page, so drive the loop with
|
||||||
|
// setInterval instead of rAF when nothing is drawn.
|
||||||
|
const DRAW_DISABLED =
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
new Set(
|
||||||
|
(new URLSearchParams(window.location.search).get('disable') ?? '')
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim()),
|
||||||
|
).has('draw')
|
||||||
|
|
||||||
const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
|
const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
|
||||||
const { advance, set, frameloop: initFrameloop, scene, clock } = useThree()
|
const { advance, set, frameloop: initFrameloop, scene, clock } = useThree()
|
||||||
const renderer = useThree((state) => state.gl)
|
const renderer = useThree((state) => state.gl)
|
||||||
@@ -18,6 +32,7 @@ const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
|
|||||||
let then = 0
|
let then = 0
|
||||||
let i = 0
|
let i = 0
|
||||||
let raf: number | null = null
|
let raf: number | null = null
|
||||||
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
const interval = 1000 / fps
|
const interval = 1000 / fps
|
||||||
function tick(t: DOMHighResTimeStamp) {
|
function tick(t: DOMHighResTimeStamp) {
|
||||||
raf = requestAnimationFrame(tick)
|
raf = requestAnimationFrame(tick)
|
||||||
@@ -30,13 +45,23 @@ const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
|
|||||||
}
|
}
|
||||||
// Set frameloop to never, it will shut down the default render loop
|
// Set frameloop to never, it will shut down the default render loop
|
||||||
set({ frameloop: 'never' })
|
set({ frameloop: 'never' })
|
||||||
// Kick off custom render loop
|
if (DRAW_DISABLED) {
|
||||||
raf = requestAnimationFrame(tick)
|
timer = setInterval(() => {
|
||||||
|
i += interval / 1000
|
||||||
|
advance(i)
|
||||||
|
}, interval)
|
||||||
|
} else {
|
||||||
|
// Kick off custom render loop
|
||||||
|
raf = requestAnimationFrame(tick)
|
||||||
|
}
|
||||||
// Restore initial setting
|
// Restore initial setting
|
||||||
return () => {
|
return () => {
|
||||||
if (raf) {
|
if (raf) {
|
||||||
cancelAnimationFrame(raf)
|
cancelAnimationFrame(raf)
|
||||||
}
|
}
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
set({ frameloop: initFrameloop })
|
set({ frameloop: initFrameloop })
|
||||||
}
|
}
|
||||||
}, [fps, advance, set, initFrameloop, renderPaused])
|
}, [fps, advance, set, initFrameloop, renderPaused])
|
||||||
|
|||||||
Reference in New Issue
Block a user