Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active December 5, 2025 22:29
Show Gist options
  • Select an option

  • Save greggman/4650be4a40492e3e9f6ab0a1b28dec6d to your computer and use it in GitHub Desktop.

Select an option

Save greggman/4650be4a40492e3e9f6ab0a1b28dec6d to your computer and use it in GitHub Desktop.
WebGPU: buffer gc test - commandBuffer
/*bug-in-github-api-content-can-not-be-empty*/
/*bug-in-github-api-content-can-not-be-empty*/
const shortSize = (function() {
const suffixes = ['b', 'k', 'mb', 'gb', 'tb', 'pb'];
return function(size) {
const suffixNdx = Math.log2(Math.abs(size)) / 10 | 0;
const suffix = suffixes[Math.min(suffixNdx, suffixes.length - 1)];
const base = 2 ** (suffixNdx * 10);
return `${(size / base).toFixed(0)}${suffix}`;
};
})();
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
device.addEventListener('uncapturederror', e => console.error(e.error.message));
function makeAndBindBuffer(device) {
const module = device.createShaderModule({
code: `
@group(0) @binding(0) var<uniform> u: vec4f;
@vertex fn vs() -> @builtin(position) vec4f {
return u;
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(0, 0, 0, 1);
}
`,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module },
fragment: { module, targets: [{ format: 'rgba8unorm' }] },
primitive: { topology: 'point-list' },
});
const buffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
});
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer }},
],
});
const rbEncoder = device.createRenderBundleEncoder({
colorFormats: ['rgba8unorm'],
});
rbEncoder.setPipeline(pipeline);
rbEncoder.setBindGroup(0, bindGroup);
rbEncoder.draw(1);
const renderBundle = rbEncoder.finish();
const tex = device.createTexture({
size: [1],
format: 'rgba8unorm',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: tex.createView(),
loadOp: 'clear',
storeOp: 'store',
}
],
});
pass.executeBundles([renderBundle]);
pass.end();
const commandBuffer = encoder.finish();
return {
commandBuffer,
texRef: new WeakRef(tex),
renderBundleRef: new WeakRef(renderBundle),
bufRef: new WeakRef(buffer),
bgRef: new WeakRef(bindGroup),
};
}
const size = 1024 * 1024 * 1;
function makeTypedArray() {
return new WeakRef(new Uint8Array(size));
}
const {bufRef, bgRef, renderBundleRef, texRef, commandBuffer} = makeAndBindBuffer(device);
const memRef = makeTypedArray();
function exists(e) {
return e ? 'exists' : 'GCed';
}
const data = new Float32Array(4);
const memory = [];
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
let anythingExists = true;
while (anythingExists) {
await wait(1000);
if (globalThis.gc) {
globalThis.gc();
}
device.queue.submit([]);
memory.push(new Uint8Array(size));
const bufExists = !!bufRef.deref();
const bgExists = !!bgRef.deref();
const texExists = !!texRef.deref();
const bundleExists = !!renderBundleRef.deref();
const memExists = !!memRef.deref();
console.log(`buffer: ${exists(bufExists)}, bg: ${exists(bgExists)}, tex: ${exists(texExists)}, bundle: ${exists(bundleExists)}, mem: ${exists(memExists)}, mem-used: ${shortSize(memory.length * size)}`);
anythingExists = bufExists || bgExists || texExists || bundleExists || memExists;
}
for (let i = 10; i > 0; --i) {
console.log(`wait: ${i}`);
await wait(1000);
}
memory.length = 0;
const memRef2 = makeTypedArray();
anythingExists = true;
while (anythingExists) {
await wait(1000);
if (globalThis.gc) {
globalThis.gc();
}
device.queue.submit([]);
memory.push(new Uint8Array(size));
const memExists = !!memRef2.deref();
console.log(`mem: ${exists(memExists)}, mem-used: ${shortSize(memory.length * size)}`);
anythingExists = memExists;
}
{
device.queue.submit([]);
console.log(`wait: a`);
await wait(1000);
device.queue.submit([]);
console.log(`wait: b`);
await wait(1000);
device.queue.submit([]);
console.log(`wait: c`);
await wait(1000);
console.log('submit command buffer');
device.queue.submit([commandBuffer]);
await wait(1000);
device.queue.submit([]);
}
console.log('stopped');
memory.length = 0;
{"name":"WebGPU: buffer gc test - commandBuffer","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment