Render Pass Descriptors

Render Pass Descriptors and Load and Store Operations

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:

What clear, load, store and discard leave in a textureHTMLLive
<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>

'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.