A render pass descriptor lists colorAttachments (plus optional depthStencilAttachment, Depth and Multisampling), each with a view, a loadOp ('clear' with a clearValue, or 'load' to keep what is there) and a storeOp ('store' or 'discard'). These choices matter most on tiled mobile GPUs, where load and store cost memory bandwidth. This test reads back a 64 x 1 target after three passes:
<script type="module">
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const module = device.createShaderModule({ code: /* wgsl */ `
@vertex fn vs(@builtin(vertex_index) v: u32) -> @builtin(position) vec4f {
return vec4f(array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3))[v], 0, 1); // covers all
}
@fragment fn fs() -> @location(0) vec4f { return vec4f(0.94, 0.42, 0, 1); }` });
const format = 'rgba8unorm';
const pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { module },
fragment: { module, targets: [{ format }] } });
const target = device.createTexture({ size: [64, 1], format,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC });
const read = device.createBuffer({ size: 256,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ });
async function pass(name, loadOp, storeOp, { draw = false, scissor = 64 } = {}) {
const encoder = device.createCommandEncoder();
const p = encoder.beginRenderPass({ colorAttachments: [{ view: target.createView(),
loadOp, storeOp, clearValue: [0.08, 0.40, 0.75, 1] }] });
if (draw) p.setPipeline(pipeline), p.setScissorRect(0, 0, scissor, 1), p.draw(3);
p.end();
encoder.copyTextureToBuffer({ texture: target }, { buffer: read }, [64, 1]);
device.queue.submit([encoder.finish()]);
await read.mapAsync(GPUMapMode.READ);
const px = new Uint8Array(read.getMappedRange()); // first and last pixel
console.log(`${name.padEnd(28)} left ${px.slice(0, 4)} right ${px.slice(252)}`);
read.unmap();
}
await pass('clear, store', 'clear', 'store');
await pass('load, draw left half, store', 'load', 'store', { draw: true, scissor: 32 });
await pass('load, draw, discard', 'load', 'discard', { draw: true });
</script>clear, store left 20,102,191,255 right 20,102,191,255 load, draw left half, store left 240,107,0,255 right 20,102,191,255 load, draw, discard left 0,0,0,0 right 0,0,0,0
'load' kept the blue while the scissored draw painted orange over half; 'discard' threw the whole result away, and the texture then read as zeros. Clear when you redraw everything, and discard attachments nobody reads later, such as depth or a resolved MSAA texture.