Migration Guide: WebGL to WebGPU
Fresh 🌱Source: From WebGL to WebGPULast Updated: September 16, 2025
WebGL and WebGPU share many core concepts (shaders, vertex/fragment pipelines, textures, buffers) but differ significantly in architecture. This guide covers the key differences.
Quick Comparison Table
| Concept | WebGL | WebGPU |
|---|---|---|
| State model | Global mutable state | Stateless immutable pipelines |
| Sync/Async | Synchronous (pipeline stalls) | Fully asynchronous |
| Compute | Not supported | Native compute shaders |
| Shader language | GLSL | WGSL |
| Error messages | Cryptic | Clear with call stacks |
| Canvas limit | 16 (Chrome/Safari), 200 (Firefox) | Unlimited |
| Uniform naming | By string name | By binding index |
| Mipmap generation | gl.generateMipmap() | Manual |
| Buffer/texture size | Mutable | Immutable after creation |
| Z clip space | -1 to 1 | 0 to 1 |
| Storage buffers | Not available | 128MB+, writable |
| Multi-canvas | One device per canvas | Many canvases per device |
1. Global State → Stateless Pipelines
WebGL (problematic global state):
javascript
// WebGL state persists globally — bugs are common
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// Forgetting to reset state causes hard-to-find bugsWebGPU (immutable pipelines):
javascript
// All state is explicit and locked into the pipeline at creation time
const pipeline = device.createRenderPipeline({
// blend, topology, vertex layout, shader — all here, immutable
primitive: { topology: 'triangle-list' },
// ...
});
// To change blend mode, create a new pipeline
const blendedPipeline = device.createRenderPipeline({
fragment: {
targets: [{
format,
blend: {
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
alpha: { srcFactor: 'one', dstFactor: 'zero', operation: 'add' }
}
}]
}
// ...
});2. Synchronous → Asynchronous
WebGL sync stalls:
javascript
// gl.getError() requires sync IPC between JS and GPU process → pipeline bubble
const error = gl.getError(); // SLOW: synchronous round-tripWebGPU async (no bubbles):
javascript
// Errors surfaced asynchronously
device.pushErrorScope('validation');
// ... do work ...
const error = await device.popErrorScope();
if (error) console.error(error.message);
// Texture creation appears to succeed immediately, error surfaced later
const texture = device.createTexture(descriptor); // doesn't throw synchronously3. Compute Shaders (WebGPU-only)
javascript
// WebGL: no compute shaders
// WebGPU: first-class GPGPU support
const computePipeline = device.createComputePipeline({
layout: 'auto',
compute: {
module: device.createShaderModule({ code: computeWGSL }),
entryPoint: 'main'
}
});See GPU Compute SOP for full example.
4. GLSL → WGSL
glsl
// WebGL GLSL
attribute vec3 a_position;
uniform mat4 u_mvp;
void main() {
gl_Position = u_mvp * vec4(a_position, 1.0);
}wgsl
// WebGPU WGSL
@group(0) @binding(0) var<uniform> mvp: mat4x4f;
struct VertexInput { @location(0) position: vec3f }
@vertex
fn main(in: VertexInput) -> @builtin(position) vec4f {
return mvp * vec4f(in.position, 1.0);
}5. Uniform Names → Binding Indexes
javascript
// WebGL: uniforms by name (string-based)
const loc = gl.getUniformLocation(program, 'u_color'); // typos silently return null
gl.uniform4fv(loc, [1, 0, 0, 1]);
// WebGPU: everything by index
// In WGSL: @group(0) @binding(0) var<uniform> color: vec4f;
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: colorBuffer } }]
// binding index 0 must match @binding(0) in WGSL — keep in sync manually
});6. Mipmap Generation
javascript
// WebGL: automatic
gl.generateMipmap(gl.TEXTURE_2D);
// WebGPU: must implement yourself
// Options:
// A) Use webgpu-utils library: https://github.com/greggman/webgpu-utils
// B) Implement via successive downsampling render passes
// C) Use pre-generated mipmaps from texture atlases
import { generateMipmap } from 'webgpu-utils';
generateMipmap(device, texture);7. Storage Buffers (New in WebGPU)
wgsl
// WebGL: no storage buffers
// Uniform buffers only (read-only, max 64KB)
// WebGPU: storage buffers (read/write, 128MB+)
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
data[id.x] = data[id.x] * 2.0; // in-place mutation
}8. Z Clip Space
WebGL: z_clip range = [-1, 1] (OpenGL/GLSL convention)
WebGPU: z_clip range = [0, 1] (Metal/D3D convention)When migrating projection matrices, adjust the near/far clip computation. Libraries like gl-matrix have WebGPU-compatible variants.