Skip to content

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

ConceptWebGLWebGPU
State modelGlobal mutable stateStateless immutable pipelines
Sync/AsyncSynchronous (pipeline stalls)Fully asynchronous
ComputeNot supportedNative compute shaders
Shader languageGLSLWGSL
Error messagesCrypticClear with call stacks
Canvas limit16 (Chrome/Safari), 200 (Firefox)Unlimited
Uniform namingBy string nameBy binding index
Mipmap generationgl.generateMipmap()Manual
Buffer/texture sizeMutableImmutable after creation
Z clip space-1 to 10 to 1
Storage buffersNot available128MB+, writable
Multi-canvasOne device per canvasMany 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 bugs

WebGPU (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-trip

WebGPU 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 synchronously

3. 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.

See Also

Based on Chrome for Developers WebGPU Documentation