Skip to content

Web Development · Browser APIs

WebGPU in 2026: What You Can Actually Build With GPU Compute in the Browser

WebGPU is now available across Chrome, Firefox, and Safari. It's not just a graphics API. It's a general-purpose GPU compute layer that changes what's possible in a browser tab.

Anurag Verma

Anurag Verma

7 min read

WebGPU in 2026: What You Can Actually Build With GPU Compute in the Browser

Sponsored

Share

For years, GPU access in the browser meant WebGL. WebGL was designed for 3D graphics in 2009 and was never meant to be a general-purpose compute platform. Using it for machine learning inference or image processing required translating your computation into vertex and fragment shaders, which is roughly as natural as writing a spreadsheet formula in assembly.

WebGPU changes this. It’s a modern GPU API designed from scratch with both graphics and compute in mind. It’s available in Chrome (since version 113), Firefox (behind a flag that became default in late 2025), and Safari (enabled by default in Safari 17.4). For most web users today, WebGPU just works.

What WebGPU Gives You

WebGPU exposes two things you couldn’t do cleanly with WebGL:

Compute shaders. A compute shader runs arbitrary parallel computation on the GPU. No triangle rasterization, no fragment pipeline. You write a function that runs thousands of times in parallel, feed it data, and get results back. This is the basis of GPU-accelerated machine learning, image processing, physics simulations, and any other task that benefits from data parallelism.

A saner graphics API. For graphics work, WebGPU’s render pipeline is closer to Vulkan and Metal than to OpenGL. Explicit resource management, no global state, no driver-specific behavior quirks. If you’ve written Vulkan code, WebGPU will feel familiar.

Most of the practical use cases in web development are on the compute side, so that’s where this guide focuses.

Your First Compute Shader

Here’s the minimum needed to run a compute shader in WebGPU. This example multiplies every element in an array by 2:

// Check for WebGPU support
if (!navigator.gpu) {
  throw new Error('WebGPU not supported');
}

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

// Input data
const inputArray = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);

// Create a buffer on the GPU
const inputBuffer = device.createBuffer({
  size: inputArray.byteLength,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});

// Upload data to the GPU buffer
device.queue.writeBuffer(inputBuffer, 0, inputArray);

// Create an output buffer
const outputBuffer = device.createBuffer({
  size: inputArray.byteLength,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});

// A buffer we can read back from (GPU -> CPU transfer requires MAP_READ)
const readbackBuffer = device.createBuffer({
  size: inputArray.byteLength,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});

// Write the compute shader in WGSL (WebGPU Shading Language)
const shaderModule = device.createShaderModule({
  code: `
    @group(0) @binding(0) var<storage, read> input: array<f32>;
    @group(0) @binding(1) var<storage, read_write> output: array<f32>;

    @compute @workgroup_size(64)
    fn main(@builtin(global_invocation_id) id: vec3<u32>) {
      let i = id.x;
      if (i < arrayLength(&input)) {
        output[i] = input[i] * 2.0;
      }
    }
  `,
});

// Create the compute pipeline
const pipeline = device.createComputePipeline({
  layout: 'auto',
  compute: { module: shaderModule, entryPoint: 'main' },
});

// Bind the buffers to the pipeline
const bindGroup = device.createBindGroup({
  layout: pipeline.getBindGroupLayout(0),
  entries: [
    { binding: 0, resource: { buffer: inputBuffer } },
    { binding: 1, resource: { buffer: outputBuffer } },
  ],
});

// Record and submit commands
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(inputArray.length / 64));
pass.end();

// Copy output to readback buffer
encoder.copyBufferToBuffer(outputBuffer, 0, readbackBuffer, 0, inputArray.byteLength);
device.queue.submit([encoder.finish()]);

// Read results back to CPU
await readbackBuffer.mapAsync(GPUMapMode.READ);
const result = new Float32Array(readbackBuffer.getMappedRange());
console.log([...result]); // [2, 4, 6, 8, 10, 12, 14, 16]
readbackBuffer.unmap();

A few things to note:

  • WGSL (WebGPU Shading Language) is the shader language for WebGPU. It’s statically typed and easier to debug than GLSL.
  • The @workgroup_size(64) means 64 threads run in each workgroup. dispatchWorkgroups(N) launches N workgroups.
  • The CPU-to-GPU and GPU-to-CPU data transfers (writeBuffer and mapAsync) are the main performance cost. Minimize how often you move data across the bus.

Real Use Cases in 2026

Running ML Models in the Browser

The most immediate practical application is running machine learning models without a server. Libraries like @tensorflow/tfjs and onnxruntime-web have had WebGPU backends for over a year. A text embedding model that takes 200ms on CPU takes 8-15ms with WebGPU.

import * as ort from 'onnxruntime-web';

// Force WebGPU backend
const session = await ort.InferenceSession.create('/models/embedding-model.onnx', {
  executionProviders: ['webgpu', 'cpu'],  // fallback to CPU if WebGPU fails
});

const inputTensor = new ort.Tensor('float32', tokenIds, [1, tokenIds.length]);
const output = await session.run({ input_ids: inputTensor });
const embedding = output.last_hidden_state.data;

This pattern is how you build privacy-preserving features where user data never leaves the device: on-device search with semantic embeddings, local content classification, personalization that doesn’t require a server round-trip.

Image Processing

Image convolutions (blur, sharpen, edge detection) are a natural fit for compute shaders. Each output pixel can be computed independently, which is exactly what the GPU excels at.

A Gaussian blur on a 2048x2048 image:

  • JavaScript (CPU): ~400ms
  • WebAssembly (CPU): ~80ms
  • WebGPU compute shader: ~4ms

The speedup isn’t always this dramatic, but image processing consistently benefits from GPU parallelism, especially for real-time effects in video or live camera feeds.

Physics Simulation

For any simulation where N particles interact, the computation scales with N squared. This is a classic case where GPU parallelism pays for itself. Browser-based physics demos and games that run particle systems, cloth simulation, or fluid dynamics benefit from compute shaders more than almost any other web use case.

What WebGPU Is Not Good For

It’s worth being specific about the tradeoffs.

WebGPU adds significant startup overhead. Adapter initialization, device creation, pipeline compilation. This takes 50-200ms on a warm cache and can take longer on first load. For a one-off computation, CPU is faster. WebGPU pays off when you’re processing large amounts of data or running the same pipeline many times.

Data transfer is expensive. Moving data between CPU and GPU memory is slow relative to the computation itself. If your workload is “compute a few numbers and display them,” WebGPU is not the answer. It’s best when you can keep data on the GPU for multiple operations before reading results back.

WGSL has a learning curve. It’s a well-designed language, but it’s not JavaScript. If you’re used to working only with the DOM and web APIs, getting comfortable with shaders takes time. The abstractions are different enough that debugging requires a different mental model.

Mobile support is patchy. Desktop Chrome, Firefox, and Safari all support WebGPU well. Mobile is inconsistent. iOS Safari 17.4+ supports it on devices with sufficient GPU capability, but older iPhones and most mid-range Android devices either don’t support it or support it poorly. Always implement a CPU fallback.

Libraries That Hide the Boilerplate

Writing raw WebGPU is verbose. For most production use cases, a library is the right choice:

TensorFlow.js: The WebGPU backend handles ML inference. You write standard TF.js code; the backend handles shader compilation and buffer management.

WONNX: A Rust-based ONNX runtime compiled to WebAssembly that uses WebGPU internally. Faster than onnxruntime-web for some models.

GPU.js: A JavaScript library that compiles JavaScript functions to GLSL/WGSL. Lower ceiling than raw WebGPU but much faster to write.

WebLLM: Specifically for running large language models in the browser via WebGPU. Runs models like Llama 3.2 and Qwen 2.5 locally with WebGPU acceleration.

A Practical Checklist

Before adding WebGPU to a project:

  1. Check if the computation is actually embarrassingly parallel. If each output depends on every other output, the GPU won’t help much.
  2. Estimate the data transfer cost. If you’re sending 100MB to the GPU and reading back 100MB, that alone takes more time than many CPU computations.
  3. Implement the CPU fallback first. Make sure the feature works without WebGPU, then layer in the GPU optimization.
  4. Test on a device without a dedicated GPU. On integrated graphics, WebGPU is faster than CPU for image processing and ML inference, but the gap is smaller.

WebGPU is not a silver bullet for web performance. It solves a specific class of problems: those that benefit from massive parallelism and can tolerate setup overhead. For those problems, the speedup is real and substantial.

Sponsored

Enjoyed it? Pass it on.

Share this article.

Sponsored

The dispatch

Working notes from
the studio.

A short letter twice a month — what we shipped, what broke, and the AI tools earning their keep.

No spam, ever. Unsubscribe anytime.

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored