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:
2026-10-27 18:31:43 -04:00
co-authored by Claude Opus 5.5
parent 0c0edf46ca
commit 9781aa3ee1
17 changed files with 38797 additions and 57 deletions
+1
View File
@@ -80,6 +80,7 @@
"ui/preview-selection.js",
"ui/events.js",
"ui/map.js",
"ui/perf.js",
"ui/loop.js"
]
}
+4
View File
@@ -64,6 +64,7 @@ function smoothDraw(lighting, glowOn) {
gl.viewport(0, 0, cw, ch);
gl.useProgram(SM.prog);
const bw = R.bw, bh = R.bh;
const uploadStart = PERF_MARKS ? performance.now() : 0;
smUpload('tCol', bw, bh, smBytes(R.px));
smUpload('tColL', bw, bh, smBytes(R.px));
smUpload('tBack', bw, bh, smBytes(R.backPx));
@@ -71,6 +72,8 @@ function smoothDraw(lighting, glowOn) {
smUpload('tField', bw, bh, smBytes(R.fieldPx));
if (lighting && R.lightImg) smUpload('tLight', R.lw, R.lh, smBytes(R.lightImg.data)); else smUpload('tLight', 1, 1, SM.white);
if (glowOn) smUpload('tGlow', R.glowC.width, R.glowC.height, smBytes(R.glowImg.data)); else smUpload('tGlow', 1, 1, SM.white);
const blitStart = PERF_MARKS ? performance.now() : 0;
if (PERF_MARKS) { perfMarks.upload = blitStart - uploadStart; perfMarks.prepare += uploadStart - t0; }
gl.uniform2f(u.uSize, bw, bh);
gl.uniform2f(u.uOrigin, R.vx0, R.vy0);
gl.uniform4f(u.uView, R.camX - R.vx0, R.camY - R.vy0, R.zp * (ch / R.canvas.height), ch);
@@ -85,6 +88,7 @@ function smoothDraw(lighting, glowOn) {
const ctx = R.ctx;
ctx.imageSmoothingEnabled = true;
ctx.drawImage(cv, 0, 0, cw, ch, 0, 0, R.canvas.width, R.canvas.height);
if (PERF_MARKS) perfMarks.blit = performance.now() - blitStart;
smoothPace(performance.now() - t0);
}
// Keep the frame rate up: drop the GPU pass's resolution when it is slow, raise it again when there
+16 -1
View File
@@ -3,6 +3,8 @@ function fogColor(c, k) { return (c & 0xff000000) | (((c >>> 16 & 255) * k) << 1
// drawWorld: the per-grain pixel buffer (pixel look), also the input of the smooth look.
// ---------- World pixels ----------
function drawWorld() {
const perfStart = PERF_MARKS ? performance.now() : 0;
if (PERF_MARKS) perfMarks.light = 0;
const bw = R.bw, bh = R.bh, px = R.px;
R.vx0 = Math.floor(R.camX); R.vy0 = Math.floor(R.camY);
const vx0 = R.vx0, vy0 = R.vy0;
@@ -31,7 +33,12 @@ function drawWorld() {
const useFog = SET.fog;
const OUT = sm ? 0x04000000 : 0xff000000; // unexplored and off the map: black
const lighting = SET.lighting !== 'off' && !R.lens;
if (lighting && (R.frame % SET.lightEvery === 0 || !R.lw)) computeLight();
if (lighting && (R.frame % SET.lightEvery === 0 || !R.lw)) {
const lightStart = PERF_MARKS ? performance.now() : 0;
computeLight();
if (PERF_MARKS) perfMarks.light = performance.now() - lightStart;
}
const colorStart = PERF_MARKS ? performance.now() : 0;
const grain = SET.grain, incand = SET.incandescence;
const anim = R.frame;
const lens = R.lens, tl = THERM[SET.thermalPalette] || THERM.inferno;
@@ -374,8 +381,15 @@ function drawWorld() {
}
}
}
if (PERF_MARKS) {
perfMarks.color = performance.now() - colorStart;
perfMarks.prepare = colorStart - perfStart - perfMarks.light;
}
if (sm) { smoothDraw(lighting, glowOn); return; }
const uploadStart = PERF_MARKS ? performance.now() : 0;
R.bctx.putImageData(R.img, 0, 0);
const blitStart = PERF_MARKS ? performance.now() : 0;
if (PERF_MARKS) perfMarks.upload = blitStart - uploadStart;
const ctx = R.ctx, zp = R.zp;
ctx.imageSmoothingEnabled = false;
const ox = -(R.camX - vx0) * zp, oy = -(R.camY - vy0) * zp;
@@ -401,4 +415,5 @@ function drawWorld() {
ctx.filter = 'none';
ctx.restore();
}
if (PERF_MARKS) perfMarks.blit = performance.now() - blitStart;
}
+1
View File
@@ -218,6 +218,7 @@ table.mt td.dip { color: var(--melt); }
#perf { position: fixed; right: max(16px, env(safe-area-inset-right, 0px)); bottom: calc(max(12px, env(safe-area-inset-bottom, 0px)) + 4px); font-family: var(--font-mono); font-size: 11px; color: var(--ash); pointer-events: none; text-align: right; white-space: pre; }
#hud .br { bottom: max(12px, env(safe-area-inset-bottom, 0px)); }
#perf:not(:empty) { background: var(--plate); color: var(--bone); padding: 6px 8px; border-radius: 4px; }
#perf:not(:empty) ~ .br { }
/* ---------- modals ---------- */
+11 -3
View File
@@ -19,6 +19,7 @@ function frame(now) {
if (GAME.loading) { acc = 0; return; }
if (!GAME.running) { pollPad(); return; }
const t0 = performance.now();
let frameSim = 0, frameSteps = 0;
const pad = pollPad();
if (GAME.usingPad) { GAME.aim.x = bot.x + bot.w / 2 + GAME.padAim.x; GAME.aim.y = bot.y + bot.h / 2 + GAME.padAim.y; }
else { const w = screenToWorld(GAME.mouse.x, GAME.mouse.y); GAME.aim.x = w.x; GAME.aim.y = w.y; }
@@ -37,7 +38,8 @@ function frame(now) {
GAME.padRun = pm > 0.92 ? GAME.padRun + 1 : 0;
botStep(ix, iy, keys.has('Shift') || GAME.padRun > 24);
toolTick();
const s0 = performance.now(); simTick(); simMs = simMs * 0.9 + (performance.now() - s0) * 0.1;
const s0 = performance.now(); simTick(); const elapsed = performance.now() - s0;
simMs = simMs * 0.9 + elapsed * 0.1; frameSim += elapsed; frameSteps++;
if (devPaused) devSteps--; else acc -= step;
steps++;
GAME.playTime += step;
@@ -56,7 +58,10 @@ function frame(now) {
if (!GAME.drag || Math.hypot(bot.vx, bot.vy) > 0.08) updateCamera(dt, lookX, lookY);
{ const v = viewCells(); setBuildRange(R.camX + v.w / 2, R.camY + v.h / 2, v.w, v.h); }
R.frame++;
if (SET.showPerf) perfLast.computeLight = 0;
drawWorld();
if (SET.showPerf) perfTimes.computeLight = (perfTimes.computeLight || 0) * 0.9 + perfLast.computeLight * 0.1;
const overlayStart = SET.showPerf ? performance.now() : 0;
drawCracks();
if (!weather.seen && (R.rain || 0) > 0.12) {
weather.seen = true;
@@ -81,6 +86,7 @@ function frame(now) {
drawRain();
drawBot();
if (!GAME.hidePreviews) drawPreviews(); // tools/shot.py hides them for clean screenshots
if (SET.showPerf) perfTimes.overlays = (perfTimes.overlays || 0) * 0.9 + (performance.now() - overlayStart) * 0.1;
if (GAME.replace !== GAME.replaceShown) { GAME.replaceShown = GAME.replace; hudDirty = true; }
renderBuildBar();
if (hudDirty || R.frame % 6 === 0) { renderSelCard(); renderBag(); renderWallet(); hudDirty = false; }
@@ -89,11 +95,13 @@ function frame(now) {
updateOverlayUI();
if (now > ambientT) { ambientT = now + 200; auAmbient(scanVisible()); }
updateMap(now);
frameMs = frameMs * 0.9 + (performance.now() - t0) * 0.1;
const frameWork = performance.now() - t0;
frameMs = frameMs * 0.9 + frameWork * 0.1;
if (PERF_FRAME) PERF_FRAME({ at: now, work: frameWork, sim: frameSim, steps: frameSteps });
fpsSm = fpsSm * 0.95 + (1 / Math.max(0.001, dt)) * 0.05;
if (SET.showPerf && R.frame % 15 === 0) {
let aw = 0, ht = 0; for (let c = 0; c < NCH; c++) { if (awake[c]) aw++; if (hot[c]) ht++; }
$('perf').textContent = `${fpsSm.toFixed(0)} fps · frame ${frameMs.toFixed(1)} ms · sim ${simMs.toFixed(1)} ms/tick\nmoving areas ${aw} · hot areas ${ht} / ${NCH} · bars ${ents.length} · view ${R.bw}×${R.bh}`;
$('perf').textContent = `${fpsSm.toFixed(0)} fps · frame ${frameMs.toFixed(1)} ms · sim ${simMs.toFixed(1)} ms/tick (move ${(perfTimes.simMove || 0).toFixed(1)}, heat ${(perfTimes.heatPass || 0).toFixed(1)}, rest ${Math.max(0, simMs - (perfTimes.simMove || 0) - (perfTimes.heatPass || 0)).toFixed(1)})\nworld ${(perfTimes.drawWorld || 0).toFixed(1)} ms · light ${(perfTimes.computeLight || 0).toFixed(1)} ms · overlays ${(perfTimes.overlays || 0).toFixed(1)} ms\nmoving areas ${aw} · hot areas ${ht} / ${NCH} · bars ${ents.length} · view ${R.bw}×${R.bh}`;
}
}
+15
View File
@@ -0,0 +1,15 @@
// Browser-only clocks; simulation code stays usable without a browser or a clock.
let PERF_MARKS = false, PERF_FRAME = null;
const perfMarks = {}, perfTimes = {}, perfLast = {};
for (const name of ['drawWorld', 'computeLight', 'simMove', 'heatPass']) {
const original = window[name];
window[name] = function (...args) {
if (!SET.showPerf && !PERF_MARKS) return original.apply(this, args);
const start = performance.now();
const result = original.apply(this, args);
const ms = performance.now() - start;
perfLast[name] = ms;
if (name !== 'computeLight') perfTimes[name] = (perfTimes[name] || 0) * 0.9 + ms * 0.1;
return result;
};
}