web: Mesa's WebGPU call counter only when asked for (?wgpustats=1)

countWebGpu wrapped every WebGPU call Mesa makes with a performance.now
pair and a Map update, always: 2.5% of the compositor worker's time in
Noita's page (F's co1 profile), 13% of a replayed frame's CPU. The stats
line keeps its per-frame call table when the page asks for it. The
eject site's launcher forwards ?glcapture and ?wgpustats to nt.html.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_d2453757b8a84fdbb4ee7c9e093141df
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-07 16:32:24 -04:00
1 parent 5a066f670f
commit 21baa051c1
4 files changed
+22 -6

No files matched your search

+1 -1
View File
@@ -93,7 +93,7 @@
// `?direct=0` composites always.
if (m.direct && q.get('direct') !== '0') p.set('direct', '1');
// Diagnostics for the page itself, passed through.
for (const k of ['present', 'pointerlock', 'wgslcache', 'mesaenv', 'trapstack', 'sharetrace']) if (q.has(k)) p.set(k, q.get(k));
for (const k of ['present', 'pointerlock', 'wgslcache', 'mesaenv', 'trapstack', 'sharetrace', 'glcapture', 'wgpustats']) if (q.has(k)) p.set(k, q.get(k));
location.replace(`nt.html?${p}`);
} catch (e) {
fail(String(e?.message ?? e));
+1
View File
@@ -1404,6 +1404,7 @@ async function startGl(m, canvas) {
env: m.mesaEnv,
capture: m.glCapture,
wgslCache: m.wgslCache,
wgpuStats: m.wgpuStats,
gpu: await requestGexDevice(),
});
if (!glDevice.ok) throw new Error(glDevice.error || 'the GL device would not start');
+5 -1
View File
@@ -73,8 +73,12 @@ export class Compositor {
// `?wgslcache=all|site|0`: which shader translations Mesa starts with
// (mesa-gl.js #loadWgslCache).
const wgslCache = q?.get('wgslcache');
// `?wgpustats=1`: count Mesa's WebGPU calls for the stats line
// (mesa-gl.js countWebGpu); off by default, it costs time.
const wgpuStats = q?.get('wgpustats') === '1';
this.#worker.postMessage({ kind: 'gl', channel, ...opts, ...(mesaEnv ? { mesaEnv } : {}),
...(glCapture ? { glCapture } : {}), ...(wgslCache ? { wgslCache } : {}) });
...(glCapture ? { glCapture } : {}), ...(wgslCache ? { wgslCache } : {}),
...(wgpuStats ? { wgpuStats } : {}) });
});
}
+15 -4
View File
@@ -63,9 +63,10 @@ async function loadGlue(url) {
/** The driver's WebGPU calls by name: [calls, ms inside, bytes, ms waited]
* ("waited" is a promise's time to settle: mapAsync, onSubmittedWorkDone).
* Mesa's own wasm is a few percent of a frame; its cost is these calls, so
* they are counted always -- one performance.now pair per call -- and
* `glState` reports them per frame since it was last asked. */
* `glState` reports them per frame since it was last asked. Only when asked
* for (`?wgpustats=1`, the `wgpuStats` option): the wrapper is a
* performance.now pair and a Map lookup on each of a few thousand calls a
* frame, which was 2.5% of the GL worker's time in Noita's page. */
const wgpuCalls = new Map();
function countWebGpu() {
if (countWebGpu.done || typeof GPUQueue === 'undefined') return;
@@ -124,6 +125,16 @@ function countWebGpu() {
* message to whatever is listening (DevTools, a harness over CDP), on the
* thread that runs Mesa: two thousand batches in a seven-minute Noita run.
* `?gltrace=1` keeps them all. */
/** A page's `?NAME=1`. In a Worker `location` is the worker script's, so
* the compositor passes its page's switches in as options instead. */
function pageFlag(name) {
try {
return globalThis.document !== undefined && new URLSearchParams(location.search).get(name) === '1';
} catch {
return false;
}
}
function quietReports(out) {
let all = false;
try {
@@ -224,7 +235,7 @@ export class MesaGlDevice {
const { adapter, device } = options.gpu ?? await requestGexDevice();
out.#adapter = adapter;
out.#device = device;
countWebGpu();
if (options.wgpuStats ?? pageFlag('wgpustats')) countWebGpu();
// And out of reach of the collector for as long as the page lives.
//
// The fields above were supposed to be enough, and are not: the device