From 0e3e2abdb6d63b12159439c3c7f990c0835a9dce Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Fri, 19 Jun 2026 09:45:57 +0200 Subject: [PATCH] Front-load post-processing compile into the loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- public/js/webgl-book-lab.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/public/js/webgl-book-lab.js b/public/js/webgl-book-lab.js index 0e2d33a..8d06795 100644 --- a/public/js/webgl-book-lab.js +++ b/public/js/webgl-book-lab.js @@ -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');