Restore WebGL reveal timing diagnostics

This commit is contained in:
2026-06-10 08:09:02 +02:00
parent 10bf23b10b
commit 5a84923884
4 changed files with 157 additions and 20 deletions
+51 -2
View File
@@ -4,7 +4,7 @@ import { RenderPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postproces
import { SSAOPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SSAOPass.js';
import { SMAAPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SMAAPass.js';
import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/OutputPass.js';
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260610-book-timeline-a';
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260610-book-timeline-b';
const canvas = document.getElementById('scene');
canvas.style.cursor = 'grab';
@@ -163,6 +163,7 @@ let fpsDisplay = null;
let fpsWindowStartedAt = performance.now();
let fpsWindowFrames = 0;
const lastFrameTiming = {};
const slowFrameLog = [];
const loaderTimings = {};
const pageTextureTimings = [];
@@ -610,6 +611,14 @@ window.BookLabDebug = {
lastFlipTexturePreflight
};
},
getBenchmarkState() {
return {
frameTiming: { ...lastFrameTiming },
slowFrames: slowFrameLog.slice(-20),
pageTextureTimings: pageTextureTimings.slice(-40),
timeline: window.BookPlaybackTimeline?.getRuntimeState?.()?.benchmark || []
};
},
projectPointerToPage(clientX, clientY) {
return projectPointerToPage(clientX, clientY);
},
@@ -2424,6 +2433,13 @@ function beginPageReveal(side, sourceCanvas, revealDetail = {}) {
shaderReady: Boolean(shader?.uniforms),
started: pageRevealState[side].startedAt != null
});
markPageTextureTiming('revealState:created', {
side,
blockIds: pageRevealState[side].blockIds,
started: pageRevealState[side].startedAt != null,
durationMs: pageRevealState[side].durationMs,
regionCount: Array.isArray(revealDetail.lineRects) ? revealDetail.lineRects.length : 0
});
markPageTextureTiming('revealUpload:end', { side });
}
@@ -2540,6 +2556,7 @@ function startPageRevealForBlock(blockId) {
const id = String(blockId ?? '');
if (!id) return;
if (!activeRevealBlockStarts.has(id)) activeRevealBlockStarts.set(id, performance.now());
let matchedSides = 0;
if (activeFlips.length > 0) {
pendingRevealStartBlockIds.add(id);
markPageTextureTiming('revealStart:deferred-for-flip', {
@@ -2552,11 +2569,18 @@ function startPageRevealForBlock(blockId) {
const state = pageRevealState[side];
if (!state || state.startedAt != null) return;
if (!state.blockIds.map(value => String(value)).includes(id)) return;
matchedSides += 1;
state.pendingStart = true;
state.startedAt = activeRevealBlockStarts.get(id) || performance.now();
const shader = getPageRevealShader(side);
if (shader?.uniforms?.bookRevealElapsedMs) shader.uniforms.bookRevealElapsedMs.value = 0;
});
markPageTextureTiming('revealStart:applied', {
blockId: id,
matchedSides,
hasLeftState: Boolean(pageRevealState.left),
hasRightState: Boolean(pageRevealState.right)
});
}
function fastForwardPageReveals(blockIds = []) {
@@ -4353,6 +4377,7 @@ function animate(now = performance.now()) {
const delta = Math.min(0.1, frameElapsedMs / 1000);
clock.getDelta();
const t = clock.elapsedTime;
const updateStartedAt = performance.now();
updateCameraRig(delta);
scene.traverse((object) => {
if (!object.userData?.light) return;
@@ -4382,10 +4407,15 @@ function animate(now = performance.now()) {
}
});
const hadActiveFlips = activeFlips.length > 0;
const flipStartedAt = performance.now();
updateActiveFlips(performance.now());
lastFrameTiming.flips = performance.now() - flipStartedAt;
if (hadActiveFlips) markStaticSceneBuffersDirty();
const revealStartedAt = performance.now();
updatePageRevealAnimations(now);
lastFrameTiming.reveal = performance.now() - revealStartedAt;
updateCandleShadowUniforms();
lastFrameTiming.update = performance.now() - updateStartedAt;
renderedFrameCount += 1;
const shadowStartedAt = performance.now();
updateBookShadowMaps();
@@ -4405,7 +4435,26 @@ function animate(now = performance.now()) {
renderer.render(scene, camera);
}
lastFrameTiming.render = performance.now() - renderStartedAt;
lastFrameTiming.total = lastFrameTiming.shadows + lastFrameTiming.reflection + lastFrameTiming.render;
lastFrameTiming.total = lastFrameTiming.update + lastFrameTiming.shadows + lastFrameTiming.reflection + lastFrameTiming.render;
if (frameElapsedMs > targetFrameDurationMs * 1.75 || lastFrameTiming.total > targetFrameDurationMs * 1.25) {
slowFrameLog.push({
at: Math.round(now),
frameElapsedMs: Math.round(frameElapsedMs * 100) / 100,
activeFlips: activeFlips.length,
revealActive: Boolean(pageRevealState.left || pageRevealState.right),
timings: {
update: Math.round(lastFrameTiming.update * 100) / 100,
flips: Math.round(lastFrameTiming.flips * 100) / 100,
reveal: Math.round(lastFrameTiming.reveal * 100) / 100,
shadows: Math.round(lastFrameTiming.shadows * 100) / 100,
reflection: Math.round(lastFrameTiming.reflection * 100) / 100,
render: Math.round(lastFrameTiming.render * 100) / 100,
total: Math.round(lastFrameTiming.total * 100) / 100
}
});
while (slowFrameLog.length > 80) slowFrameLog.shift();
document.documentElement.dataset.webglSlowFrames = JSON.stringify(slowFrameLog.slice(-20));
}
if (refreshStaticSceneBuffers && activeFlips.length === 0) {
staticSceneBuffersDirty = false;
}