← /research

WebGPU vs WebGL

Processing · Reading Notes Created Jan 4, 2025

Source

WebGPU API - MDN — MDN / W3C (Article)
View source →
Project: web-graphics-research
graphicswebgpuwebglperformance

WebGPU is the successor to WebGL. If you’re building a graphics-heavy app in 2025+, this is the API to target.

Browser Support (2025)

BrowserStatus
Chrome/Edge 113+✓ Windows, macOS, ChromeOS
Chrome 121+✓ Android 12+ (Qualcomm/ARM)
Firefox 141+✓ Windows
Firefox 145+✓ macOS (ARM)
Safari 18.2+✓ macOS, iOS, iPadOS, visionOS

WebGPU is now baseline across major browsers.

Performance Gains

Benchmarks show 2-3x speedups over WebGL:

  • Google saw 3x speedup on diffusion models
  • Babylon.js Snapshot Rendering: ~10x faster with GPU Render Bundles
  • More consistent frame rates (less CPU bottlenecking)

Key Differences

1. Compute Shaders

WebGL restricts GPU to graphics only. Physics, particles, post-processing must run on CPU.

WebGPU has first-class GPGPU support:

// Particle simulation - impossible in WebGL without hacks
@group(0) @binding(0) var<storage, read_write> particles: array<Particle>;

struct Particle {
    position: vec3<f32>,
    velocity: vec3<f32>,
}

@compute @workgroup_size(256)
fn update(@builtin(global_invocation_id) id: vec3<u32>) {
    let i = id.x;
    if (i >= arrayLength(&particles)) { return; }

    var p = particles[i];
    p.velocity.y -= 9.8 * 0.016;  // Gravity
    p.position += p.velocity * 0.016;
    particles[i] = p;
}

Practical impact:

  • Physics: 10k particles at 60fps → 100k+ particles
  • Post-processing: Screen-space effects without render-to-texture hacks
  • AI inference: Matrix operations on GPU (WebGPU + ONNX Runtime)

2. Multi-threaded Command Preparation

WebGL: Single thread. Every draw call, state change, resource upload is sequential. CPU often bottlenecks at 40% GPU usage.

WebGPU: Prepare render commands across multiple threads. Keep the GPU fed.

3. Reduced CPU Overhead

WebGL’s state machine model requires many API calls per draw. WebGPU uses:

  • Render Bundles: Pre-record draw commands, replay cheaply
  • Bind Groups: Batch resource bindings
  • Pipeline State Objects: Pre-validate GPU state

WebGL state machine (many API calls):

// Every frame, for every object:
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(textureLocation, 0);
gl.uniformMatrix4fv(mvpLocation, false, mvpMatrix);
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
// State persists — must reset for next draw

WebGPU bind groups (batched):

// Once at setup:
const bindGroup = device.createBindGroup({
  layout: pipeline.getBindGroupLayout(0),
  entries: [
    { binding: 0, resource: { buffer: uniformBuffer } },
    { binding: 1, resource: texture.createView() },
    { binding: 2, resource: sampler },
  ]
});

// Every frame:
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.draw(vertexCount);
// State doesn't leak — explicit and isolated

Performance difference:

  • WebGL: ~500 draw calls/frame before CPU bottleneck
  • WebGPU: ~10,000+ draw calls/frame, GPU-bound

4. Power Efficiency

Less CPU overhead = less heat = more consistent mobile performance.

A game that drops from 60→30 FPS after 5 minutes on WebGL might hold 60 FPS on WebGPU.

Shading Language

WebGL uses GLSL. WebGPU uses WGSL (WebGPU Shading Language):

GLSL (WebGL)

#version 300 es
precision highp float;

in vec2 vTexCoord;
uniform sampler2D uTexture;
uniform float uTime;

out vec4 fragColor;

void main() {
    vec2 uv = vTexCoord + 0.01 * sin(uTime + vTexCoord.y * 10.0);
    fragColor = texture(uTexture, uv);
}

WGSL (WebGPU)

struct Uniforms {
    time: f32,
}

@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var myTexture: texture_2d<f32>;
@group(0) @binding(2) var mySampler: sampler;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) texCoord: vec2<f32>,
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let uv = in.texCoord + 0.01 * sin(uniforms.time + in.texCoord.y * 10.0);
    return textureSample(myTexture, mySampler, uv);
}

WGSL Advantages

  1. Explicit resource binding — No implicit global state
  2. Strong typingvec4<f32> not just vec4
  3. Struct-based I/O — Clear data flow
  4. Compute shader support — Same language for graphics and compute
  5. Better tooling — Static analysis, IDE support

WGSL Limitations

  • Verbose compared to GLSL
  • No GLSL-style swizzle chains (rgba only, not xyzw)
  • Different texture sampling syntax
  • Still evolving (spec not frozen)

Migration Strategy

If supporting both WebGL and WebGPU (like Figma):

  1. Abstract rendering backend
  2. Maintain shaders in both GLSL and WGSL (or use transpiler)
  3. Feature-detect and fall back gracefully
  4. Leverage compute shaders only on WebGPU path

When to Use What

Use CaseRecommendation
New project (2025+)WebGPU first, WebGL fallback
Existing WebGL appMigrate incrementally
Must support old browsersWebGL only
Compute-heavy (AI, sim)WebGPU required

The Future

WebGL is frozen — no new features. WebGPU will continue evolving.

Browser vendors have signaled WebGPU as the long-term standard.

Sources:

Related: building figma today, vello gpu vector graphics, webgpu future roadmap