Front-load post-processing compile into the loader

primeSceneForLoader() compiled scene materials and rendered the shadow/reflection passes
once, but never ran the full composer pipeline — so the SSAO and output passes compiled
their programs and allocated their render targets on the first live frame after the loader
faded, tanking FPS for ~1-2s before it climbed to full.

Now the loader runs composer.render() twice during prime, and precompiles the flip page
materials (created lazily on first flip, so previously missed by renderer.compile) via a
throwaway probe mesh. The heavy first-frame work is paid behind the loader overlay instead.

Verified live: loader timings show composerWarmup taking ~1499ms during load (exactly the
cost that used to hit the first frame); after fade-out there are no over-budget tank frames
in the slow-frame log and idle settles at ~72fps. Static suite passes (165).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 09:45:57 +02:00
parent b180637ea7
commit 0e3e2abdb6
+28 -1
View File
@@ -4317,12 +4317,39 @@ function primeSceneForLoader() {
updateTableReflection();
markLoaderTiming('tableReflection:end');
markLoaderTiming('shaderCompile:start');
renderer.compile(scene, camera);
compileFlipMaterialsForLoader();
markLoaderTiming('shaderCompile:end');
// Run the full post-processing pipeline now so the SSAO and output passes compile their
// programs and allocate their render targets during the loader, instead of stalling the
// first live frames after the loader fades out.
markLoaderTiming('composerWarmup:start');
if (composer) {
composer.render();
composer.render();
}
markLoaderTiming('composerWarmup:end');
staticSceneBuffersDirty = false;
markLoaderTiming('primeSceneForLoader:end');
}
// The flipping page mesh is only created on the first page flip, so its materials are not in
// the scene graph that renderer.compile walks. Compile them now via a throwaway probe mesh so
// the first flip does not stutter while the GPU compiles the flip page program.
function compileFlipMaterialsForLoader() {
const probeGeometry = new THREE.PlaneGeometry(0.001, 0.001);
const probes = [materials.flipPageSurface, materials.flipPageBackSurface, materials.flipPageEdge]
.map((material) => {
const probe = new THREE.Mesh(probeGeometry, material);
probe.position.copy(book.position);
probe.visible = true;
book.add(probe);
return probe;
});
renderer.compile(scene, camera);
probes.forEach((probe) => book.remove(probe));
probeGeometry.dispose();
}
function tintAmbientFromCanvas(canvas) {
if (!canvas || !candleBounceLight) return;
const ctx = canvas.getContext('2d');