Debugging WebGPU

Error handling and debugging tools

WebGPU's validation layer catches mistakes that would cause undefined behavior on the GPU. This is a significant improvement over older APIs where errors manifested as silent corruption, driver crashes, or security vulnerabilities. Understanding how to read error messages and use debugging tools transforms frustrating debugging sessions into straightforward problem-solving.

The Validation Layer

Every WebGPU call passes through a validation layer before reaching the GPU. This layer checks that arguments are valid, resources exist, and operations make sense. When validation fails, you get an error message describing the problem.

// This will produce a validation error
const buffer = device.createBuffer({
  size: 0, // Error: size must be > 0
  usage: GPUBufferUsage.VERTEX,
});
typescript

The browser console will show something like:

Uncaught DOMException: Failed to execute 'createBuffer' on 'GPUDevice':
Buffer size must be greater than 0.
plaintext

Validation happens on the CPU before any GPU work, so errors appear immediately. This differs from GPU execution errors, which may surface later (or not at all—the GPU often just produces garbage output).

Interactive: Common error messages

Problematic Code
device.createBuffer({
  size: 0,
  usage: GPUBufferUsage.VERTEX,
});
Error Message
Buffer size (0) must be greater than 0.
What Went Wrong

You cannot create a zero-sized buffer. Every buffer must have at least one byte.

Fix
device.createBuffer({
  size: 64, // Provide actual size
  usage: GPUBufferUsage.VERTEX,
});

Click through different errors to learn common patterns and their fixes.

Reading Error Messages

WebGPU error messages follow a pattern: they identify the operation that failed and explain why. Learn to parse them:

"Failed to execute 'X' on 'Y'" — The operation X on object Y failed. Look at the arguments you passed to X.

"...must be..." — A parameter violated a constraint. The message tells you what constraint.

"...is invalid..." — You referenced something that doesn't exist or was destroyed.

"...not in the allowed set..." — You used a value not permitted in that context.

Some errors reference validation rules by ID. The WebGPU specification defines these rules, and you can look them up for detailed explanations:

Validation rule [Buffer-Usage] violated: 
COPY_DST usage required for writeBuffer
plaintext

Error Scopes

For finer control over error handling, use error scopes. These let you catch errors from a specific section of code:

device.pushErrorScope('validation');
 
// Operations that might fail
const buffer = device.createBuffer({ ... });
const texture = device.createTexture({ ... });
 
device.popErrorScope().then(error => {
  if (error) {
    console.error('Creation failed:', error.message);
  }
});
typescript

Error scopes are useful for:

  • Testing whether operations succeed without crashing
  • Implementing fallback behavior when optional features fail
  • Collecting errors for logging/telemetry

The scope types are validation (API misuse), out-of-memory (resource allocation failed), and internal (driver/implementation bugs).

Interactive: Validation in action

Validation log will appear here...

Watch validation catch errors in real-time. Failed operations show what went wrong, allowing you to fix and retry.

Browser Developer Tools

Modern browsers include WebGPU debugging support in their developer tools.

Chrome DevTools shows WebGPU calls in the Console and allows inspecting GPU objects. Enable "Verbose" logging level to see all API calls.

Firefox has similar support, with GPU process information visible in about:support.

Third-party tools like WebGPU Inspector provide deeper inspection:

  • View all created resources
  • Inspect buffer contents
  • See bind group layouts
  • Capture and replay frames

To use WebGPU Inspector, install the browser extension, then click its icon while on a page using WebGPU. You'll see a panel listing all GPU objects and their state.

Common Mistakes

Certain errors appear repeatedly when learning WebGPU. Recognizing them saves debugging time.

Interactive: Test your debugging knowledge

Question 1 of 5
Score: 0/5
const buffer = device.createBuffer({
  size: 48,
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
What's wrong with this uniform buffer?

Forgetting to map/unmap buffers:

// Wrong: trying to write before mapping
buffer.getMappedRange(); // Error if not mapped
 
// Right: map first
await buffer.mapAsync(GPUMapMode.WRITE);
const range = buffer.getMappedRange();
// ... write data ...
buffer.unmap();
typescript

Mismatched bind group layouts:

// Pipeline expects layout A
const pipeline = device.createRenderPipeline({
  layout: device.createPipelineLayout({
    bindGroupLayouts: [layoutA]
  }),
  ...
});
 
// But you pass a bind group created with layout B
renderPass.setBindGroup(0, bindGroupWithLayoutB); // Error
typescript

Buffer size not aligned:

// Wrong: 13 bytes, but uniform buffers need 16-byte alignment
const buffer = device.createBuffer({
  size: 13,
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
}); // May work, but causes issues
 
// Right: round up to alignment
const buffer = device.createBuffer({
  size: Math.ceil(13 / 16) * 16, // 16 bytes
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
typescript

Using destroyed resources:

buffer.destroy();
// ... later ...
queue.writeBuffer(buffer, 0, data); // Error: buffer is destroyed
typescript

Workgroup size mismatch:

// Shader declares workgroup size 64
@compute @workgroup_size(64)
fn main() { ... }
wgsl
// But dispatch assumes workgroup size 256
const totalThreads = 1024;
pass.dispatchWorkgroups(totalThreads / 256); // Wrong: only 4 workgroups
pass.dispatchWorkgroups(totalThreads / 64);  // Right: 16 workgroups
typescript

Debugging Shaders

Shader bugs are harder to diagnose because you can't use console.log(). Strategies:

Output to a buffer: Write intermediate values to a storage buffer, read them back on CPU.

@group(0) @binding(0) var<storage, read_write> debug: array<f32>;
 
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
    let value = someComputation();
    debug[id.x] = value; // Capture for debugging
}
wgsl

Visualize with color: In fragment shaders, output intermediate values as colors.

@fragment
fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
    let normal = computeNormal(uv);
    
    // Debug: show normals as RGB
    return vec4f(normal * 0.5 + 0.5, 1.0);
    
    // Real output (commented during debugging):
    // return computeLighting(normal);
}
wgsl

Simplify: Comment out sections until the bug disappears. The last section you commented out contains the problem.

Check bounds: Many shader bugs come from out-of-bounds array access. Add explicit bounds checks during debugging:

fn safeRead(index: u32) -> f32 {
    if (index >= arrayLength(&data)) {
        return -999.0; // Obviously wrong value for debugging
    }
    return data[index];
}
wgsl

Device Lost

Sometimes the GPU device becomes unavailable—driver crash, GPU reset, or resource exhaustion. Handle this gracefully:

device.lost.then(info => {
  console.error(`Device lost: ${info.message}`);
  
  if (info.reason === 'destroyed') {
    // You called device.destroy()
  } else {
    // Unexpected loss—try to recover
    reinitializeWebGPU();
  }
});
typescript

Device loss destroys all GPU resources. Your only option is to recreate the device and all resources from scratch. Design your application to support this from the start, rather than retrofitting recovery logic later.

Interactive: Debug checklist

Debug Progress0/15 checked
Setup
Buffers
Shaders
Pipeline
Runtime

Use this checklist when debugging. Click items as you verify them.

Defensive Programming

Catch problems early with assertions and checks:

function createUniformBuffer(device: GPUDevice, data: ArrayBuffer) {
  // Assert alignment
  if (data.byteLength % 16 !== 0) {
    console.warn(`Uniform buffer size ${data.byteLength} not 16-byte aligned`);
  }
  
  const buffer = device.createBuffer({
    size: Math.ceil(data.byteLength / 16) * 16,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });
  
  device.queue.writeBuffer(buffer, 0, data);
  return buffer;
}
typescript

Wrapper functions that validate inputs prevent classes of errors entirely. The upfront cost of writing them pays off in faster debugging.

Key Takeaways

  • WebGPU's validation layer catches errors before they reach the GPU—read error messages carefully
  • Use error scopes to handle errors from specific code sections without crashing
  • Browser dev tools and extensions like WebGPU Inspector help visualize GPU state
  • Common mistakes: unmapped buffers, mismatched layouts, alignment issues, destroyed resources
  • Debug shaders by writing to buffers, visualizing as colors, or simplifying until bugs disappear
  • Handle device loss from the start—design applications to support full reinitialization