Shelf Depth and MSAA

Depth and MSAA on BookNest's Shelf Scene

The shelf now stands in 3D: six covers lean alternately left and right, with a perspective divide by z, so neighbors meet in a zig-zag and the depth test decides which is in front where they overlap. The same scene renders into two canvases, without and with 4x MSAA, and a 2D canvas magnifies the same 72 x 32 pixel crop of each four times:

demos/ch04/depth-msaa-shelf.html: the shelf with depth, without and with 4x MSAAHTMLLive
<canvas id="plain" width="290" height="120"></canvas>
<canvas id="msaa" width="290" height="120"></canvas>
<canvas id="zoom" width="590" height="128"></canvas>
<script type="module">
const code = /* wgsl */ `
const tints = array(vec3f(.08, .40, .75), vec3f(.18, .49, .20), vec3f(.94, .42, 0),
                    vec3f(.42, .11, .60), vec3f(.22, .28, .31), vec3f(.78, .16, .16));
struct Out { @builtin(position) pos: vec4f, @location(0) shade: vec3f }
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) b: u32) -> Out {
  let uv = array(vec2f(0, 0), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1, 1),
                 vec2f(0, 1))[v];
  let angle = select(-0.7, 0.7, b % 2 == 0);        // covers lean left and right
  let x = (uv.x - 0.5) * 1.2;
  let world = vec3f(x * cos(angle) - 2.25 + f32(b) * 0.9, uv.y * 1.8 - 0.9,
                    x * sin(angle) + 4);             // neighbors overlap slightly
  let clip = vec4f(world.x * 2.6 / 2.4, world.y * 2.6, (world.z - 1) / 9 * world.z, world.z);
  let light = 0.65 + 0.35 * uv.y - 0.25 * f32(uv.x < 0.1);
  return Out(clip, tints[b] * light);                // perspective: GPU divides by z
}
@fragment fn fs(in: Out) -> @location(0) vec4f { return vec4f(in.shade, 1); }`;
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
const module = device.createShaderModule({ code });
const pipelines = await Promise.all([1, 4].map((count) => device.createRenderPipelineAsync({
  layout: 'auto', vertex: { module }, fragment: { module, targets: [{ format }] },
  depthStencil: { format: 'depth24plus', depthWriteEnabled: true, depthCompare: 'less' },
  multisample: { count } })));
function drawShelf(canvas, pipeline, sampleCount) {
  const context = canvas.getContext('webgpu');
  context.configure({ device, format });
  const size = [canvas.width, canvas.height], usage = GPUTextureUsage.RENDER_ATTACHMENT;
  const depth = device.createTexture({ size, sampleCount, format: 'depth24plus', usage });
  const target = context.getCurrentTexture().createView();
  const msaa = sampleCount > 1 && device.createTexture({ size, sampleCount, format, usage });
  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [{ clearValue: [0.96, 0.94, 0.90, 1], loadOp: 'clear',
      view: msaa ? msaa.createView() : target, resolveTarget: msaa ? target : undefined,
      storeOp: msaa ? 'discard' : 'store' }],        // MSAA: keep only the resolved image
    depthStencilAttachment: { view: depth.createView(), depthClearValue: 1,
      depthLoadOp: 'clear', depthStoreOp: 'discard' } });
  pass.setPipeline(pipeline), pass.draw(6, 6), pass.end();
  device.queue.submit([encoder.finish()]);
}
drawShelf(plain, pipelines[0], 1);
drawShelf(msaa, pipelines[1], 4);
const ctx = zoom.getContext('2d');                  // same task: before the canvases present
ctx.imageSmoothingEnabled = false;                  // real pixels, magnified 4 times
ctx.drawImage(plain, 40, 12, 72, 32, 0, 0, 288, 128);
ctx.drawImage(msaa, 40, 12, 72, 32, 302, 0, 288, 128);
await device.queue.onSubmittedWorkDone();
window.__done = true;
</script>
Browser output of Listing 4.65
Browser output of 65

Without MSAA (left crop) the top edges climb in one-pixel steps; with 4x MSAA (right) edge pixels blend cover and background by covered samples. The 2D copy runs in the same task as the submits: after a WebGPU canvas presents, drawImage() no longer sees its frame.