WebGPU
WebGPU is a browser-mediated, sandboxed graphics and compute API. It is not “GPU Direct,” and it is not the default answer for a visual web page.
Decide before building
Use WebGPU when at least one of these is true:
- the workload needs compute shaders, storage buffers, or modern explicit GPU pipelines;
- measured CPU or draw-call overhead is the bottleneck in a substantial WebGL workload;
- a renderer already has a tested WebGPU backend and a real fallback;
- the product controls its target browsers and GPUs.
Prefer something simpler when it fits:
- semantic UI, text, forms, and ordinary motion → DOM/CSS;
- custom 2D drawing → Canvas 2D, optionally in a worker;
- portable 3D with broad compatibility → WebGL2 or an engine with WebGL fallback;
- CPU-heavy parsing, codecs, or simulation → Worker/WASM before moving unrelated work to the GPU.
Recent major browser releases include WebGPU, but MDN still marks it limited availability / not Baseline across browser, OS, and device combinations. Treat it as progressive enhancement unless the product has an explicit tested support contract.
Capability gate, not browser detection
Initialize asynchronously and make fallback a first-class result:
type GpuStart = { device: GPUDevice; adapter: GPUAdapter } | null;
export async function startWebGpu(): Promise<GpuStart> {
if (!navigator.gpu) return null;
try {
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) return null; // unsupported, disabled, blocked, or no usable adapter
const device = await adapter.requestDevice();
device.lost.then((info) => {
console.warn(`WebGPU device lost: ${info.reason}`, info.message);
// Stop submissions, dispose app-side state, then retry or enter fallback.
});
return { adapter, device };
} catch {
return null;
}
}
- Check every optional
feature and required limit; never assume the development GPU is typical.
- Request only capabilities the workload uses. A request exceeding adapter limits rejects.
powerPreference is a hint, not a guarantee of a discrete GPU or higher performance.
- WebGPU requires a secure context. Development exceptions do not replace production HTTPS.
Architecture that survives production
- Give one application service ownership of the adapter, device, queue, caches, and loss recovery.
- Keep render/compute code independent from product state. Pass compact frame data across the boundary.
- Create pipelines, bind-group layouts, samplers, and stable resources outside the frame loop.
- Label devices, buffers, textures, pipelines, and passes; validation messages become actionable.
- Make resource lifetime explicit. Destroy large buffers/textures when they are no longer reusable; do not rely on JavaScript GC to understand GPU memory pressure.
- Keep a working Canvas/WebGL/CPU path behind the same high-level renderer contract when broad support matters.
Buffers, textures, and transfers
- Choose usage flags at creation time and keep them as narrow as practical.
- Reuse buffers and textures; avoid per-frame allocation and pipeline compilation.
- Use
queue.writeBuffer() / writeTexture() for bounded uploads. Stage or ring-buffer sustained streaming after measuring.
- Avoid GPU-to-CPU readback in the frame loop; it introduces synchronization and often erases the GPU win.
- Respect alignment, row-pitch, format, feature, and limit requirements from the active device—not a hard-coded “common GPU.”
- Put frequently reused resources into stable bind groups. Rebuild only when their bindings actually change.
WGSL and pipelines
- Keep WGSL deterministic and reviewable; do not construct shaders from untrusted strings.
- Validate shader modules and use
createRenderPipelineAsync() / createComputePipelineAsync() when compilation latency could block interaction.
- Keep shader interfaces and bind-group layouts versioned together.
- Prefer fewer pipeline variants. Branching in a shader can be cheaper than a combinatorial pipeline cache, but benchmark the real workload.
- Capture validation errors around synchronous fallible setup work. Async pipeline creation rejects separately, so handle that rejection rather than leaving an error scope open:
device.pushErrorScope('validation');
const pipeline = device.createRenderPipeline(descriptor);
const validationError = await device.popErrorScope();
if (validationError) throw validationError;
Also listen for uncapturederror during development; do not use it instead of scoped handling for expected failures.
Canvas and frame loop
const context = canvas.getContext('webgpu');
if (!context) return useFallback();
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format, alphaMode: 'premultiplied' });
- Resize the backing store from observed device pixels and clamp resolution when fill-rate is expensive.
- Reconfigure after meaningful size/device changes; avoid doing so every frame.
- Acquire the current texture only for the frame being encoded.
- Encode related passes together and minimize queue submissions; submission boundaries are not free.
- Render on demand when the scene is static. For continuous animation, use
requestAnimationFrame and its timestamp.
Measure the GPU path
- Benchmark the WebGPU path against its fallback on target devices. WebGPU can be slower than WebGL for small or poorly batched workloads.
- Separate CPU frame time, GPU time, upload/readback cost, pipeline creation, and memory pressure.
- Use timestamp queries only after checking the feature; keep a non-timestamp profiling path.
- Watch for pipeline churn, redundant bind groups, too many submissions, oversized textures, overdraw, and hidden readbacks.
- Test integrated GPUs, battery-saving modes, blocklisted adapters, software fallbacks, and device loss—not only a desktop discrete GPU.
Experimental HTML-in-Canvas boundary
The proposed GPUQueue.drawElementImageToTexture() HTML-in-Canvas path (formerly copyElementImageToTexture()) is not a production WebGPU primitive yet. The explainer was rechecked on 9 September 2026; it remains separate from the core WebGPU specification. Keep semantic DOM or another renderer as the shipping path and feature-detect experiments against the current draft. See low-level-web-rendering for the routing and fallback rule. For Vercel's vgpu library and shader-based visual UI, use vgpu; it does not depend on HTML-in-Canvas.
Ship gate
Reference
1---2name: webgpu3description: Implements or debugs raw browser WebGPU/WGSL pipelines, compute, buffers, textures, and device loss. Use for direct GPU API work; vgpu-library APIs and R3F scenes have separate workflows.4license: MIT5---67# WebGPU89WebGPU is a browser-mediated, sandboxed graphics and compute API. It is not “GPU Direct,” and it is not the default answer for a visual web page.1011## Decide before building1213Use WebGPU when at least one of these is true:1415- the workload needs compute shaders, storage buffers, or modern explicit GPU pipelines;16- measured CPU or draw-call overhead is the bottleneck in a substantial WebGL workload;17- a renderer already has a tested WebGPU backend and a real fallback;18- the product controls its target browsers and GPUs.1920Prefer something simpler when it fits:2122- semantic UI, text, forms, and ordinary motion → DOM/CSS;23- custom 2D drawing → Canvas 2D, optionally in a worker;24- portable 3D with broad compatibility → WebGL2 or an engine with WebGL fallback;25- CPU-heavy parsing, codecs, or simulation → Worker/WASM before moving unrelated work to the GPU.2627Recent major browser releases include WebGPU, but MDN still marks it **limited availability / not Baseline** across browser, OS, and device combinations. Treat it as progressive enhancement unless the product has an explicit tested support contract.2829## Capability gate, not browser detection3031Initialize asynchronously and make fallback a first-class result:3233```ts34type GpuStart = { device: GPUDevice; adapter: GPUAdapter } | null;3536export async function startWebGpu(): Promise<GpuStart> {37 if (!navigator.gpu) return null;3839 try {40 const adapter = await navigator.gpu.requestAdapter();41 if (!adapter) return null; // unsupported, disabled, blocked, or no usable adapter42 const device = await adapter.requestDevice();43 device.lost.then((info) => {44 console.warn(`WebGPU device lost: ${info.reason}`, info.message);45 // Stop submissions, dispose app-side state, then retry or enter fallback.46 });47 return { adapter, device };48 } catch {49 return null;50 }51}52```5354- Check every optional `feature` and required `limit`; never assume the development GPU is typical.55- Request only capabilities the workload uses. A request exceeding adapter limits rejects.56- `powerPreference` is a hint, not a guarantee of a discrete GPU or higher performance.57- WebGPU requires a secure context. Development exceptions do not replace production HTTPS.5859## Architecture that survives production6061- Give one application service ownership of the adapter, device, queue, caches, and loss recovery.62- Keep render/compute code independent from product state. Pass compact frame data across the boundary.63- Create pipelines, bind-group layouts, samplers, and stable resources outside the frame loop.64- Label devices, buffers, textures, pipelines, and passes; validation messages become actionable.65- Make resource lifetime explicit. Destroy large buffers/textures when they are no longer reusable; do not rely on JavaScript GC to understand GPU memory pressure.66- Keep a working Canvas/WebGL/CPU path behind the same high-level renderer contract when broad support matters.6768## Buffers, textures, and transfers6970- Choose usage flags at creation time and keep them as narrow as practical.71- Reuse buffers and textures; avoid per-frame allocation and pipeline compilation.72- Use `queue.writeBuffer()` / `writeTexture()` for bounded uploads. Stage or ring-buffer sustained streaming after measuring.73- Avoid GPU-to-CPU readback in the frame loop; it introduces synchronization and often erases the GPU win.74- Respect alignment, row-pitch, format, feature, and limit requirements from the active device—not a hard-coded “common GPU.”75- Put frequently reused resources into stable bind groups. Rebuild only when their bindings actually change.7677## WGSL and pipelines7879- Keep WGSL deterministic and reviewable; do not construct shaders from untrusted strings.80- Validate shader modules and use `createRenderPipelineAsync()` / `createComputePipelineAsync()` when compilation latency could block interaction.81- Keep shader interfaces and bind-group layouts versioned together.82- Prefer fewer pipeline variants. Branching in a shader can be cheaper than a combinatorial pipeline cache, but benchmark the real workload.83- Capture validation errors around synchronous fallible setup work. Async pipeline creation rejects separately, so handle that rejection rather than leaving an error scope open:8485```ts86device.pushErrorScope('validation');87const pipeline = device.createRenderPipeline(descriptor);88const validationError = await device.popErrorScope();89if (validationError) throw validationError;90```9192Also listen for `uncapturederror` during development; do not use it instead of scoped handling for expected failures.9394## Canvas and frame loop9596```ts97const context = canvas.getContext('webgpu');98if (!context) return useFallback();99100const format = navigator.gpu.getPreferredCanvasFormat();101context.configure({ device, format, alphaMode: 'premultiplied' });102```103104- Resize the backing store from observed device pixels and clamp resolution when fill-rate is expensive.105- Reconfigure after meaningful size/device changes; avoid doing so every frame.106- Acquire the current texture only for the frame being encoded.107- Encode related passes together and minimize queue submissions; submission boundaries are not free.108- Render on demand when the scene is static. For continuous animation, use `requestAnimationFrame` and its timestamp.109110## Measure the GPU path111112- Benchmark the WebGPU path against its fallback on target devices. WebGPU can be slower than WebGL for small or poorly batched workloads.113- Separate CPU frame time, GPU time, upload/readback cost, pipeline creation, and memory pressure.114- Use timestamp queries only after checking the feature; keep a non-timestamp profiling path.115- Watch for pipeline churn, redundant bind groups, too many submissions, oversized textures, overdraw, and hidden readbacks.116- Test integrated GPUs, battery-saving modes, blocklisted adapters, software fallbacks, and device loss—not only a desktop discrete GPU.117118## Experimental HTML-in-Canvas boundary119120The proposed `GPUQueue.drawElementImageToTexture()` HTML-in-Canvas path (formerly `copyElementImageToTexture()`) is not a production WebGPU primitive yet. The explainer was rechecked on 9 September 2026; it remains separate from the core WebGPU specification. Keep semantic DOM or another renderer as the shipping path and feature-detect experiments against the current draft. See `low-level-web-rendering` for the routing and fallback rule. For Vercel's vgpu library and shader-based visual UI, use `vgpu`; it does not depend on HTML-in-Canvas.121122## Ship gate123124- [ ] WebGPU is feature-detected and HTTPS is guaranteed.125- [ ] Required features/limits are checked before device creation.126- [ ] Adapter absence, device loss, and validation errors reach a usable fallback.127- [ ] Resources and pipelines are reused; cleanup is explicit.128- [ ] No synchronous readback sits in the hot path.129- [ ] Accessibility and product UI remain in semantic DOM where appropriate.130- [ ] Performance was measured on representative integrated and discrete GPUs.131132## Reference133134- W3C: [WebGPU specification](https://www.w3.org/TR/webgpu/) and [WGSL specification](https://www.w3.org/TR/WGSL/).135- GPU for the Web: [WebGPU explainer](https://gpuweb.github.io/gpuweb/explainer/) and [samples](https://webgpu.github.io/webgpu-samples/).136- MDN: [WebGPU API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API), including compatibility and secure-context status.137- Chrome for Developers: [WebGPU overview](https://developer.chrome.com/docs/web-platform/webgpu/overview), [troubleshooting](https://developer.chrome.com/docs/web-platform/webgpu/troubleshooting-tips), and [WebGL-to-WebGPU migration](https://developer.chrome.com/docs/web-platform/webgpu/from-webgl-to-webgpu).