Files
deepgrain-foundry/tools/perf-measure.js
T

85 lines
3.7 KiB
JavaScript

// 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) };
}
async 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;
}
const light = 0;
// 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), frameStart = performance.now();
try { for (let k = 0; k < n; k++) frame(lastT + 1000 / 60); }
finally { restore(); }
const frame_ms = (performance.now() - frameStart) / n;
// One completed timestamp sample for this configuration, outside the CPU measurements.
let gpu_ms = null;
if (GPU.queries) {
await GPU.timingPromise;
PERF_MARKS = true; drawWorld();
await GPU.timingPromise;
PERF_MARKS = false; gpu_ms = GPU.gpuMs; marks.gpu = gpu_ms; marks.gpuLight = GPU.lightMs;
}
return { frame_ms, 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: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms };
} 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: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms: GPU.gpuMs });
}, seconds * 1000);
});
}