Profiler: real-GPU breakdown by zoom, look and scene, with a baseline (t88 phase 0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// Temporary wrappers live only in the profiling page, never in the simulation bundle.
|
||||
function perfWrap(names, data) {
|
||||
const originals = {};
|
||||
for (const name of names) {
|
||||
if (typeof window[name] !== 'function') throw Error('Missing profiler target: ' + name);
|
||||
const f = originals[name] = window[name];
|
||||
data[name] = { ms: 0, calls: 0 };
|
||||
window[name] = function (...args) {
|
||||
const t = performance.now();
|
||||
try { return f.apply(this, args); }
|
||||
finally { data[name].ms += performance.now() - t; data[name].calls++; }
|
||||
};
|
||||
}
|
||||
return () => { for (const name of names) window[name] = originals[name]; };
|
||||
}
|
||||
function perfSim(names, scene, w, h) {
|
||||
// Five full periods warm even the 120-tick cold pass without forcing conditional calls.
|
||||
for (let k = 0; k < 600; k++) simTick();
|
||||
perfScene(scene, w, h);
|
||||
const start = perfState(), data = {}, restore = perfWrap(names, data), n = 240;
|
||||
const t = performance.now();
|
||||
try { for (let k = 0; k < n; k++) simTick(); }
|
||||
finally { restore(); }
|
||||
const total = (performance.now() - t) / n;
|
||||
const steps = {};
|
||||
for (const [name, d] of Object.entries(data)) steps[name] = {
|
||||
ms_per_tick: d.ms / n, ms_per_call: d.calls ? d.ms / d.calls : null, calls: d.calls
|
||||
};
|
||||
return { total_ms: total, ticks: n, start, end: perfState(), steps,
|
||||
rest_ms: total - Object.values(data).reduce((s, d) => s + d.ms / n, 0) };
|
||||
}
|
||||
function perfRender(lighting, overlays) {
|
||||
SET.lighting = lighting;
|
||||
PERF_MARKS = true;
|
||||
try {
|
||||
for (let k = 0; k < 5; k++) { R.frame++; drawWorld(); }
|
||||
const marks = {}, n = 30;
|
||||
let draw = 0;
|
||||
for (let k = 0; k < n; k++) {
|
||||
R.frame++; drawWorld();
|
||||
draw += perfLast.drawWorld / n;
|
||||
for (const [key, value] of Object.entries(perfMarks)) marks[key] = (marks[key] || 0) + value / n;
|
||||
}
|
||||
for (let k = 0; k < 5; k++) computeLight();
|
||||
const l0 = performance.now();
|
||||
for (let k = 0; k < n; k++) computeLight();
|
||||
const light = (performance.now() - l0) / n;
|
||||
// Exercise the actual overlay calls and their arguments/conditions in frame().
|
||||
PERF_MARKS = false;
|
||||
for (let k = 0; k < 5; k++) frame(lastT + 1000 / 60);
|
||||
const data = {}, restore = perfWrap(overlays, data);
|
||||
try { for (let k = 0; k < n; k++) frame(lastT + 1000 / 60); }
|
||||
finally { restore(); }
|
||||
return { drawWorld_ms: draw, computeLight_ms: light, marks_ms: marks,
|
||||
overlays: Object.fromEntries(Object.entries(data).map(([key, d]) => [key, { ms_per_frame: d.ms / n, calls: d.calls }])),
|
||||
overlay_ms: Object.values(data).reduce((sum, d) => sum + d.ms / n, 0),
|
||||
scale: SM.scale, actual_look: SET.look };
|
||||
} finally { PERF_MARKS = false; }
|
||||
}
|
||||
function perfRun(seconds) {
|
||||
return new Promise(resolve => {
|
||||
SET.lighting = 'full';
|
||||
let warm = 5, previous = null;
|
||||
const samples = [];
|
||||
GAME.paused = false; acc = 0; lastT = performance.now(); lastRender = lastT;
|
||||
PERF_FRAME = s => {
|
||||
if (warm-- > 0) { previous = s.at; return; }
|
||||
samples.push({ interval: s.at - previous, work: s.work, sim: s.sim, steps: s.steps });
|
||||
previous = s.at;
|
||||
};
|
||||
window.__perfStop = false;
|
||||
requestAnimationFrame(frame);
|
||||
setTimeout(() => {
|
||||
window.__perfHalt(); PERF_FRAME = null; GAME.paused = true;
|
||||
resolve({ samples, end: perfState(), scale: SM.scale, actual_look: SET.look });
|
||||
}, seconds * 1000);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user