draw() Calls

Draw Calls with draw() and drawIndexed()

draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) runs the vertex shader vertexCount x instanceCount times. drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance) reads vertex numbers from the buffer set with setIndexBuffer(buffer, 'uint16' | 'uint32') and adds baseVertex to each, so many meshes can share one vertex buffer. A cover quad as a triangle list needs 6 vertices (48 bytes at 8 bytes each); indexed, it needs 4 corners (32 bytes) plus 12 bytes of uint16 indices [0, 1, 2, 0, 2, 3], and the GPU's post-transform cache can skip re-shading repeated indices; a grid mesh reuses each vertex about six times. Pass instanceCount rather than looping over draws: First Cover Quad draws six covers with one drawIndexed(6, 6).

The same quads with draw() and with drawIndexed(): six vertices each versus four corners, uint16 indices and a baseVertexHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f4ee; font: 14px system-ui, sans-serif; }
  .stage { position: relative; width: 100%; max-width: 600px; }
  .stage canvas { display: block; width: 100%; }
  .stage canvas + canvas { position: absolute; inset: 0; pointer-events: none; }
</style>
<div class="stage">
  <canvas id="view" width="600" height="340"></canvas>
  <canvas id="labels" width="600" height="340"></canvas>
</div>
<script>
const canvas = document.getElementById('view');
const ink = document.getElementById('labels').getContext('2d');

function showMessage(text) {                     // 2D fallback when WebGPU is missing
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = '#fbeaea'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#8a2b2b'; ctx.font = '18px system-ui, sans-serif'; ctx.textAlign = 'center';
  ctx.fillText(text, canvas.width / 2, canvas.height / 2);
}

const code = /* wgsl */ `
struct Out { @builtin(position) pos: vec4f, @location(0) color: vec3f }
@vertex fn vs(@location(0) p: vec2f, @builtin(instance_index) i: u32) -> Out {
  let tints = array(vec3f(0.08, 0.40, 0.75), vec3f(0.85, 0.40, 0.18), vec3f(0.16, 0.56, 0.30));
  return Out(vec4f(p + vec2f(f32(i) * 0.3, 0), 0, 1), tints[i % 3]);
}
@fragment fn fs(in: Out) -> @location(0) vec4f { return vec4f(in.color, 1); }`;

async function main() {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return showMessage('WebGPU is not available in this browser');
  const device = await adapter.requestDevice();
  const context = canvas.getContext('webgpu');
  const format = navigator.gpu.getPreferredCanvasFormat();
  context.configure({ device, format });
  const B = GPUBufferUsage;
  const upload = (data, usage) => {
    const b = device.createBuffer({ size: Math.ceil(data.byteLength / 4) * 4, usage: usage | B.COPY_DST });
    device.queue.writeBuffer(b, 0, data.buffer, 0, Math.ceil(data.byteLength / 4) * 4);
    return b;
  };
  // Left: a triangle list repeats two corners: 6 vertices x 8 bytes = 48 bytes per quad.
  const listVerts = new Float32Array([-0.95, -0.5, -0.75, -0.5, -0.95, 0.5, -0.75, -0.5, -0.75, 0.5, -0.95, 0.5]);
  // Right: one vertex buffer holding two meshes: a quad (4 corners) and a bookmark (5 corners).
  const sharedVerts = new Float32Array([0.05, -0.5, 0.25, -0.5, 0.25, 0.5, 0.05, 0.5,              // quad, vertices 0-3
                                        0.05, 0.5, 0.05, -0.5, 0.15, -0.3, 0.25, -0.5, 0.25, 0.5]); // bookmark, 4-8
  const indices = new Uint16Array([0, 1, 2, 0, 2, 3,                 // the quad: 12 bytes
                                   0, 1, 2, 0, 2, 4, 2, 3, 4]);      // the bookmark, relative to its first vertex
  const listBuffer = upload(listVerts, B.VERTEX), sharedBuffer = upload(sharedVerts, B.VERTEX);
  const indexBuffer = upload(new Uint16Array([...indices, 0]), B.INDEX);   // padded to 4 bytes

  const module = device.createShaderModule({ code });
  const pipeline = device.createRenderPipeline({ layout: 'auto', fragment: { module, targets: [{ format }] },
    vertex: { module, buffers: [{ arrayStride: 8, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }] }] } });

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(),
    clearValue: [0.97, 0.96, 0.93, 1], loadOp: 'clear', storeOp: 'store' }] });
  pass.setPipeline(pipeline);
  pass.setVertexBuffer(0, listBuffer);
  pass.draw(6, 3);                                   // vertexCount 6, 3 instances
  pass.setVertexBuffer(0, sharedBuffer);
  pass.setIndexBuffer(indexBuffer, 'uint16');
  pass.drawIndexed(6, 1, 0, 0, 0);                   // the quad: indices 0-5, baseVertex 0
  pass.drawIndexed(9, 2, 6, 4, 1);                   // the bookmark: indices 6-14, baseVertex 4, instances 1-2
  pass.end();
  device.queue.submit([encoder.finish()]);

  const toPx = (x, y) => [(x + 1) * 300, (1 - y) * 170];
  ink.font = '10px ui-monospace, monospace';
  const names = new Map();                           // vertices at the same spot share one label
  const add = (x, y, k) => { const key = toPx(x, y).join(); names.set(key, names.has(key) ? names.get(key) + '/' + k : String(k)); };
  for (let k = 0; k < 6; k++) add(listVerts[k * 2], listVerts[k * 2 + 1], k);
  for (let k = 0; k < 9; k++) add(sharedVerts[k * 2] + (k >= 4 ? 0.3 : 0), sharedVerts[k * 2 + 1], k);
  for (const [key, text] of names) {
    const [px, py] = key.split(',').map(Number);
    ink.fillStyle = '#222'; ink.beginPath(); ink.arc(px, py, 4, 0, 7); ink.fill(); ink.fillText(text, px + 5, py - 6);
  }
  ink.font = '12px ui-monospace, monospace'; ink.fillStyle = '#222';
  ink.fillText('draw(6, 3)', 12, 30);
  ink.fillText('drawIndexed(6, 1, 0, 0, 0)', 310, 22);
  ink.fillText('drawIndexed(9, 2, 6, 4, 1)', 310, 40);
  ink.font = '12px system-ui, sans-serif'; ink.fillStyle = '#444';
  ink.fillText('48 bytes per quad: corners 0/3 and 2/5 repeat', 12, 280);
  ink.fillText('quad: 32 bytes of corners + 12 bytes of indices', 310, 280);
  ink.fillText('bookmark: baseVertex 4 is added to each of its indices,', 310, 300);
  ink.fillText('so both meshes share one vertex buffer', 310, 318);
}
main();
</script>