Skip to content

Bounds Guards

Volten normally protects kernels from extra invocations at the edge of a dispatch.

const doubleValues = kernel({
shader: `
fn main(gid: vec3u) {
data[gid.x] = data[gid.x] * 2.0;
}
`
});

If data.count is 100 and the workgroup size is 64, WebGPU must dispatch two workgroups. That creates up to 128 invocations. Volten injects a guard so invocations 100..127 return before calling your code.

Most kernels are written against the logical data size, not the rounded-up dispatch size.

fn main(gid: vec3u) {
data[gid.x] = data[gid.x] * 2.0;
}

Without a guard, gid.x could go past the end of data.

Advanced kernels can opt out.

const doubleWithManualBounds = kernel({
shader: `
fn main(gid: vec3u) {
if (gid.x >= count) {
return;
}
data[gid.x] = data[gid.x] * 2.0;
}
`,
unsafeManualBounds: true,
threads: 128
});
const count = new Uniform(data.count, 'u32');
const node = doubleWithManualBounds({ data, count });

Use this only when your WGSL handles every out-of-range invocation correctly.

Kernels that use workgroupBarrier() or storageBarrier() need special care.

fn main(gid: vec3u, lid: u32) {
workgroupBarrier();
}

Volten’s automatic guard returns early before your code runs. That is unsafe for some barrier patterns, because not every invocation reaches the same barrier.

If a barrier kernel would need a partial guarded workgroup, Volten throws. Fix it by making the thread count an exact multiple of workgroupSize, or by using unsafeManualBounds: true and handling bounds manually.