createBuffer()

Creating Buffers with createBuffer() and Usage Flags

device.createBuffer({ size, usage, mappedAtCreation, label }) allocates zero-filled GPU memory. usage ORs together GPUBufferUsage flags for every way the buffer will be used; anything undeclared fails validation:

GPUBufferUsage flags
Flags (value) Allows Rule
MAP_READ (1), MAP_WRITE (2) mapAsync() from JavaScript Combine only with COPY_DST or COPY_SRC
COPY_SRC (4), COPY_DST (8) Copies, writeBuffer() writeBuffer() needs COPY_DST
INDEX (16), VERTEX (32) setIndexBuffer(), setVertexBuffer() Render Pipelines
UNIFORM (64), STORAGE (128) Bind group buffer bindings Bind Group Layouts
INDIRECT (256), QUERY_RESOLVE (512) Indirect draws, query results Indirect Draws and Bundles, Profiling and Beyond

On this machine MAP_READ | STORAGE failed with "If a buffer usage contains BufferUsage::MapRead the only other allowed usage is BufferUsage::CopyDst", usage 0 failed, and a 1 TiB buffer failed against the default maxBufferSize of 268435456 (256 MiB). Mappable buffers are staging memory, never shader resources: shaders work on a separate STORAGE buffer that you copy in or out of.

createBuffer() usage combinations drawn as flag bits, each checked in an error scopeHTMLLive
<!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="380"></canvas>
  <canvas id="labels" width="600" height="380"></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 flags = ['MAP_READ', 'MAP_WRITE', 'COPY_SRC', 'COPY_DST', 'INDEX', 'VERTEX', 'UNIFORM', 'STORAGE', 'INDIRECT', 'QUERY_RESOLVE'];
const U = GPUBufferUsage ?? {};
const tries = [
  ['storage buffer you can write', 64, () => U.STORAGE | U.COPY_DST],
  ['staging buffer for read-back', 64, () => U.MAP_READ | U.COPY_DST],
  ['mappable and a shader resource', 64, () => U.MAP_READ | U.STORAGE],
  ['no usage at all', 4, () => 0],
  ['mesh data', 64, () => U.VERTEX | U.INDEX | U.COPY_DST],
  ['indirect args written by compute', 16, () => U.INDIRECT | U.STORAGE],
  ['1 TiB of storage', 2 ** 40, () => U.STORAGE]];

// One cell per (row, flag): lit when that bit is set in the row's usage.
const code = /* wgsl */ `
@group(0) @binding(0) var<storage> usage: array<u32>;
struct Out { @builtin(position) pos: vec4f, @location(0) @interpolate(flat) cell: u32 }
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> Out {
  let q = vec2f(f32(v & 1), f32(v >> 1));
  let px = vec2f(190 + f32(i % 10) * 28, 76 + f32(i / 10) * 36) + q * vec2f(24, 27);
  return Out(vec4f(px.x / 300 - 1, 1 - px.y / 190, 0, 1), i);
}
@fragment fn fs(in: Out) -> @location(0) vec4f {
  let on = (usage[in.cell / 10] >> (in.cell % 10)) & 1;
  return select(vec4f(0.89, 0.88, 0.85, 1), vec4f(0.08, 0.40, 0.75, 1), on == 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 results = [];
  for (const [name, size, usage] of tries) {
    device.pushErrorScope('validation');
    device.createBuffer({ size, usage: usage() });   // never throws: problems become GPU errors
    results.push([name, size, usage(), await device.popErrorScope()]);
  }

  const usages = device.createBuffer({ size: tries.length * 4, usage: U.STORAGE | U.COPY_DST });
  device.queue.writeBuffer(usages, 0, new Uint32Array(results.map((r) => r[2])));
  const module = device.createShaderModule({ code });
  const pipeline = device.createRenderPipeline({ layout: 'auto', primitive: { topology: 'triangle-strip' },
    vertex: { module }, fragment: { module, targets: [{ format }] } });
  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.setBindGroup(0, device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: usages } }] }));
  pass.draw(4, tries.length * 10);
  pass.end();
  device.queue.submit([encoder.finish()]);

  ink.save(); ink.font = '10.5px ui-monospace, monospace'; ink.fillStyle = '#444';
  flags.forEach((f, k) => { ink.save(); ink.translate(206 + k * 28, 70); ink.rotate(-Math.PI / 3); ink.fillText(`${f} ${1 << k}`, 0, 0); ink.restore(); });
  ink.restore();
  results.forEach(([name, size, usage, error], i) => {
    const y = 94 + i * 36;
    ink.textAlign = 'right'; ink.font = '12px system-ui, sans-serif'; ink.fillStyle = '#222';
    ink.fillText(name, 182, y);
    ink.font = '10.5px ui-monospace, monospace'; ink.fillStyle = '#777';
    ink.fillText(`usage ${usage}, ${size >= 2 ** 30 ? '1 TiB' : size + ' B'}`, 182, y + 12);
    ink.textAlign = 'left'; ink.font = 'bold 12px system-ui, sans-serif';
    ink.fillStyle = error ? '#b03a28' : '#2a8f4d';
    ink.fillText(error ? 'fails' : 'ok', 478, y + 4);
  });
  const mapErr = results[2][3], big = results[6][3];
  ink.font = '11px system-ui, sans-serif'; ink.fillStyle = '#8a2b2b';
  if (mapErr) ink.fillText(mapErr.message.split('\n')[0].slice(0, 96), 8, 370);
  if (big) { ink.fillStyle = '#555'; ink.fillText(`maxBufferSize on this device: ${device.limits.maxBufferSize.toLocaleString('en-US')} bytes`, 8, 350); }
}
main();
</script>