mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 22:10:34 +00:00
✨ Track allocation sizes and Skia cache in GPU memory harness
This commit is contained in:
parent
45077e6608
commit
c77b8798ae
@ -31,6 +31,7 @@ Projects: `gpu-memory-chromium`, `gpu-memory-firefox`, `gpu-memory-webkit`.
|
||||
| `PERF_REPEATS` | `3` | Runs per engine; repeat 0 is dropped as warm-up |
|
||||
| `PERF_ITERATIONS` | `12` | Steps per phase |
|
||||
| `PERF_DPR` | `2` | Device pixel ratio |
|
||||
| `PERF_SKIA_CACHE_MB` | build default | Skia resource cache budget, set after the first render (needs `set_resource_cache_limit_mb` in the wasm build) |
|
||||
| `PERF_PURGE_PROBE` | off | `1` calls `free_gpu_resources` at the end and samples again |
|
||||
| `PERF_PROC_MATCH` | `ms-playwright` | Substring of the browser binary path used to find its processes |
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ const config = {
|
||||
iterations: Number(env.PERF_ITERATIONS ?? 12),
|
||||
settleMs: Number(env.PERF_SETTLE_MS ?? 300),
|
||||
purgeProbe: env.PERF_PURGE_PROBE === "1",
|
||||
skiaCacheMb: env.PERF_SKIA_CACHE_MB ? Number(env.PERF_SKIA_CACHE_MB) : null,
|
||||
outDir: env.PERF_OUT_DIR ?? "playwright/perf/results",
|
||||
};
|
||||
|
||||
@ -121,6 +122,19 @@ for (let repeat = 0; repeat < config.repeats; repeat++) {
|
||||
await workspace.mockGetFile(config.fixture);
|
||||
await workspace.goToWorkspace();
|
||||
await workspace.waitForFirstRenderWithoutUI();
|
||||
if (config.skiaCacheMb) {
|
||||
// Set after the first render, so the load phase still runs on the
|
||||
// build's default budget.
|
||||
const applied = await page.evaluate((mb) => {
|
||||
const mod = globalThis.app?.common?.render_wasm?.wasm?.internal_module;
|
||||
if (typeof mod?._set_resource_cache_limit_mb !== "function")
|
||||
return false;
|
||||
mod._set_resource_cache_limit_mb(mb);
|
||||
return true;
|
||||
}, config.skiaCacheMb);
|
||||
if (!applied)
|
||||
throw new Error("wasm build lacks set_resource_cache_limit_mb");
|
||||
}
|
||||
await sample("load");
|
||||
// Canvas captures from the first repeat show whether a change broke
|
||||
// rendering, not only how much memory it saved.
|
||||
@ -186,6 +200,7 @@ for (let repeat = 0; repeat < config.repeats; repeat++) {
|
||||
repeat,
|
||||
fixture: config.fixture,
|
||||
iterations: config.iterations,
|
||||
skiaCacheMb: config.skiaCacheMb,
|
||||
viewport: base,
|
||||
dpr: testInfo.project.use.deviceScaleFactor ?? 1,
|
||||
renderer,
|
||||
|
||||
@ -71,6 +71,12 @@ const SERIES = [
|
||||
unit: "count",
|
||||
get: (s) => s.web?.allocs?.total,
|
||||
},
|
||||
{
|
||||
key: "skia",
|
||||
title: "Skia resource cache",
|
||||
unit: "bytes",
|
||||
get: (s) => s.web?.skiaCache?.bytes,
|
||||
},
|
||||
{
|
||||
key: "gpuRss",
|
||||
title: "RSS of processes holding the GPU",
|
||||
@ -189,6 +195,18 @@ const METRICS = [
|
||||
unit: "bytes",
|
||||
get: (r) => perCycleGrowth(r, webgl),
|
||||
},
|
||||
{
|
||||
key: "skiaCachePeak",
|
||||
title: "Skia resource cache, peak",
|
||||
unit: "bytes",
|
||||
get: (r) => peak(r, (s) => s.web?.skiaCache?.bytes),
|
||||
},
|
||||
{
|
||||
key: "skiaPurgeableIdle",
|
||||
title: "Skia purgeable bytes, idle",
|
||||
unit: "bytes",
|
||||
get: (r) => lastOf(r, "idle", (s) => s.web?.skiaCache?.purgeableBytes),
|
||||
},
|
||||
{
|
||||
key: "wasmHeapPeak",
|
||||
title: "WASM heap, peak",
|
||||
@ -341,7 +359,7 @@ function fmtDelta(delta) {
|
||||
}
|
||||
|
||||
function pad(s, n) {
|
||||
return String(s).padEnd(n);
|
||||
return `${String(s).padEnd(n - 1)} `;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------- main
|
||||
@ -394,7 +412,7 @@ for (const engine of engines) {
|
||||
` ${label}: ${used}/${n} runs, commit ${meta.commit}, wasm ${meta.wasm}, ${meta.renderer?.renderer ?? "unknown renderer"}`,
|
||||
);
|
||||
}
|
||||
const header = [pad("metric", 38), ...present.map((l) => pad(l, 22))].join(
|
||||
const header = [pad("metric", 38), ...present.map((l) => pad(l, 26))].join(
|
||||
"",
|
||||
);
|
||||
console.log(`\n${header}`);
|
||||
@ -404,9 +422,9 @@ for (const engine of engines) {
|
||||
const cells = present.map((label) => {
|
||||
const st = byLabel[label].metrics[m.key];
|
||||
const value = fmt(st?.median, m.unit);
|
||||
if (label === baseline) return pad(value, 22);
|
||||
if (label === baseline) return pad(value, 26);
|
||||
const v = verdict(base, st, m.informative, m.unit);
|
||||
return pad(`${value} ${fmtDelta(v.delta)} ${v.verdict}`, 22);
|
||||
return pad(`${value} ${fmtDelta(v.delta)} ${v.verdict}`, 26);
|
||||
});
|
||||
console.log([pad(m.title, 38), ...cells].join(""));
|
||||
}
|
||||
|
||||
@ -100,6 +100,17 @@ export function installWebGLMemoryTracker() {
|
||||
};
|
||||
const totals = { texture: 0, renderbuffer: 0, buffer: 0 };
|
||||
let allocatedBytesTotal = 0;
|
||||
// Allocations since the last snapshot, by kind and size: shows which
|
||||
// surfaces a step reallocated.
|
||||
const allocsBySize = new Map();
|
||||
|
||||
function logAlloc(kind, w, h, bytes) {
|
||||
const key = `${kind} ${w}x${h}`;
|
||||
const entry = allocsBySize.get(key) ?? { count: 0, bytes: 0 };
|
||||
entry.count++;
|
||||
entry.bytes += bytes;
|
||||
allocsBySize.set(key, entry);
|
||||
}
|
||||
let contextLosses = 0;
|
||||
|
||||
// texture -> Map("face:level" -> bytes); renderbuffer/buffer -> bytes
|
||||
@ -209,10 +220,14 @@ export function installWebGLMemoryTracker() {
|
||||
});
|
||||
wrap(proto, "texImage2D", (gl, a) => {
|
||||
if (a.length >= 8) {
|
||||
setTextureLevel(gl, a[0], a[1], a[3] * a[4] * bpp(a[2], a[6], a[7]));
|
||||
const bytes = a[3] * a[4] * bpp(a[2], a[6], a[7]);
|
||||
setTextureLevel(gl, a[0], a[1], bytes);
|
||||
logAlloc("texture", a[3], a[4], bytes);
|
||||
} else {
|
||||
const [w, h] = sourceSize(a[5]);
|
||||
setTextureLevel(gl, a[0], a[1], w * h * bpp(a[2], a[3], a[4]));
|
||||
const bytes = w * h * bpp(a[2], a[3], a[4]);
|
||||
setTextureLevel(gl, a[0], a[1], bytes);
|
||||
logAlloc("texture", w, h, bytes);
|
||||
}
|
||||
});
|
||||
wrap(proto, "texImage3D", (gl, a) => {
|
||||
@ -232,6 +247,7 @@ export function installWebGLMemoryTracker() {
|
||||
});
|
||||
wrap(proto, "texStorage2D", (gl, a) => {
|
||||
setTextureStorage(gl, a[0], a[1], a[3], a[4], 1, bpp(a[2]));
|
||||
logAlloc("texture", a[3], a[4], a[3] * a[4] * bpp(a[2]));
|
||||
});
|
||||
wrap(proto, "texStorage3D", (gl, a) => {
|
||||
setTextureStorage(gl, a[0], a[1], a[3], a[4], a[5], bpp(a[2]));
|
||||
@ -250,11 +266,9 @@ export function installWebGLMemoryTracker() {
|
||||
stateOf(gl).renderbuffer = rb;
|
||||
});
|
||||
wrap(proto, "renderbufferStorage", (gl, [, fmt, w, h]) => {
|
||||
setObjectBytes(
|
||||
"renderbuffer",
|
||||
stateOf(gl).renderbuffer,
|
||||
w * h * bpp(fmt),
|
||||
);
|
||||
const bytes = w * h * bpp(fmt);
|
||||
setObjectBytes("renderbuffer", stateOf(gl).renderbuffer, bytes);
|
||||
logAlloc("renderbuffer", w, h, bytes);
|
||||
});
|
||||
wrap(
|
||||
proto,
|
||||
@ -262,6 +276,7 @@ export function installWebGLMemoryTracker() {
|
||||
(gl, [, samples, fmt, w, h]) => {
|
||||
const bytes = w * h * bpp(fmt) * Math.max(1, samples);
|
||||
setObjectBytes("renderbuffer", stateOf(gl).renderbuffer, bytes);
|
||||
logAlloc("renderbuffer", w, h, bytes);
|
||||
},
|
||||
);
|
||||
wrap(proto, "createRenderbuffer", () => counters.renderbuffersCreated++);
|
||||
@ -372,6 +387,15 @@ export function installWebGLMemoryTracker() {
|
||||
};
|
||||
}
|
||||
|
||||
function skiaCache() {
|
||||
const mod = globalThis.app?.common?.render_wasm?.wasm?.internal_module;
|
||||
if (typeof mod?._resource_cache_bytes !== "function") return null;
|
||||
return {
|
||||
bytes: mod._resource_cache_bytes(),
|
||||
purgeableBytes: mod._resource_cache_purgeable_bytes(),
|
||||
};
|
||||
}
|
||||
|
||||
function wasmHeapBytes() {
|
||||
if (wasmMemories.length === 0) return null;
|
||||
return wasmMemories.reduce((acc, m) => acc + m.buffer.byteLength, 0);
|
||||
@ -396,6 +420,11 @@ export function installWebGLMemoryTracker() {
|
||||
rendererInfo,
|
||||
snapshot() {
|
||||
const drawingBuffer = drawingBuffers();
|
||||
const bySize = [...allocsBySize.entries()]
|
||||
.map(([key, v]) => ({ key, ...v }))
|
||||
.sort((a, b) => b.bytes - a.bytes)
|
||||
.slice(0, 12);
|
||||
allocsBySize.clear();
|
||||
return {
|
||||
textures: { count: textureLevels.size, bytes: totals.texture },
|
||||
renderbuffers: {
|
||||
@ -413,9 +442,11 @@ export function installWebGLMemoryTracker() {
|
||||
counters.bufferAllocs,
|
||||
},
|
||||
allocatedBytesTotal,
|
||||
allocsBySize: bySize,
|
||||
contexts: contexts.length,
|
||||
contextLosses,
|
||||
wasmHeapBytes: wasmHeapBytes(),
|
||||
skiaCache: skiaCache(),
|
||||
jsHeapBytes: performance.memory?.usedJSHeapSize ?? null,
|
||||
};
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user