Skip to content

Instantly share code, notes, and snippets.

@greggman
Created November 27, 2025 15:32
Show Gist options
  • Select an option

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

Select an option

Save greggman/a6029309631e7ca1d53aa950ae5ddb07 to your computer and use it in GitHub Desktop.
WebGPU: SetScissorRect with storage texture
html, body { margin: 0; height: 100% }
canvas { width: 100%; height: 100%; display: block; }
#fail {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: red;
color: white;
font-weight: bold;
font-family: monospace;
font-size: 16pt;
text-align: center;
}
/*bug-in-github-api-content-can-not-be-empty*/
// WebGPU Cube
/* global GPUBufferUsage */
/* global GPUTextureUsage */
import {vec3, mat4} from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js';
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
fail('need webgpu');
return;
}
const shaderSrc = `
@vertex
fn myVSMain(@builtin(vertex_index) vNdx: u32) -> @builtin(position) vec4f {
let pos = array(
vec2f(-1, 3),
vec2f( 3, -1),
vec2f(-1, -1),
);
return vec4f(pos[vNdx], 0, 1);
}
@group(0) @binding(0) var tex: texture_storage_2d<rgba8unorm, write>;
@fragment
fn myFSMain(@builtin(position) pos: vec4f) -> @location(0) vec4f {
let t = vec2u(pos.xy);
textureStore(tex, t, (pos + 0.5) * (1.0 / 255.0));
return vec4f(1,0,0,1);
}
`;
const shaderModule = device.createShaderModule({code: shaderSrc});
const tex = device.createTexture({
size: [2, 2, 1],
format: 'rgba8unorm',
usage:
GPUTextureUsage.STORAGE_BINDING |
GPUTextureUsage.COPY_SRC,
});
const destTex = device.createTexture({
size: [2, 2, 1],
format: 'rgba8unorm',
usage:
GPUTextureUsage.RENDER_ATTACHMENT,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module: shaderModule,
},
fragment: {
module: shaderModule,
targets: [
{format: 'rgba8unorm'},
],
},
});
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: tex.createView() },
],
});
const renderPassDescriptor = {
colorAttachments: [
{
view: destTex.createView(),
loadOp: 'clear',
storeOp: 'store',
},
],
};
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.setScissorRect(0,0,1,1);
pass.draw(3);
pass.end();
const resultBuffer = device.createBuffer({
size: 256 * 2,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});
encoder.copyTextureToBuffer(
{ texture: tex },
{ buffer: resultBuffer, bytesPerRow: 256 },
[2, 2],
);
device.queue.submit([encoder.finish()]);
await resultBuffer.mapAsync(GPUMapMode.READ);
const pix = view => [...view].map(v => v.toString(16).padStart(2, '0')).join(' ');
const result = new Uint8Array(resultBuffer.getMappedRange());
for (let y = 0; y < tex.height; ++y) {
for (let x = 0; x < tex.width; ++x) {
const off = y * 256 + x * 4;
console.log(`${x},${y}: ${pix(result.subarray(off, off + 4))}`);
}
}
}
main();
{"name":"WebGPU: SetScissorRect with storage texture","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