WebGPU Basics: First Steps Beyond WebGL
AI generated
JS
() =>
JavaScript · Graphics · GPU Programming
WebGPU Basics
First steps beyond WebGL

WebGPU is the web's modern answer to direct GPU programming and is gradually replacing WebGL as the standard for heavy graphics and computation work in the browser. Once you understand the core concepts, you gain access to compute shaders, more efficient memory management, and a much more modern API.

17 min read GPUDevice WGSL Compute Shaders

1. Why WebGL hits its limits

WebGL is based on OpenGL ES, an API designed for the graphics hardware and programming models of the early 2010s. Modern GPUs today work with quite different concepts such as explicit command buffers, pipeline objects, and parallel command generation, which WebGL can only map awkwardly or not at all.

In practice this shows up as CPU overhead: WebGL calls like gl.drawArrays() trigger a lot of internal validation and state management work in the driver, which becomes a bottleneck in complex scenes with many draw calls. WebGPU was designed from the ground up for modern graphics APIs like Vulkan, Metal, and Direct3D 12, and adopts their more efficient underlying principles.

2. GPUDevice as the central entry point

Getting started with WebGPU begins with requesting a GPUAdapter via navigator.gpu.requestAdapter(), which represents the available GPU hardware. From this adapter you can then request a GPUDevice, the actual interface through which all further operations run. Both the adapter and device requests are asynchronous and can be parameterized with optional feature and limit requests, so an application specifically asks only for the capabilities it actually needs.

The device encapsulates resources such as buffers, textures, and pipelines, and provides queues through which commands are sent to the GPU. Unlike WebGL, where a global context implicitly holds state, in WebGPU every resource is explicitly created and referenced, which reduces sources of error and makes browser-side optimizations easier.


async function initWebGPU() {
  if (!navigator.gpu) {
    throw new Error('WebGPU is not supported by this browser');
  }

  const adapter = await navigator.gpu.requestAdapter();
  if (!adapter) {
    throw new Error('No suitable GPU adapter found');
  }

  const device = await adapter.requestDevice();
  const canvas = document.querySelector('#gpu-canvas');
  const context = canvas.getContext('webgpu');

  context.configure({
    device,
    format: navigator.gpu.getPreferredCanvasFormat(),
  });

  return { device, context };
}

3. Command buffers and explicit command creation

Instead of sending commands to the GPU individually and immediately, WebGPU collects them in a GPUCommandEncoder, which records drawing, compute, and copy operations. At the end, a GPUCommandBuffer is created from this and submitted for execution as a batch via a GPUQueue.

This model lets the application prepare commands in parallel across multiple CPU threads before submitting them collectively, a pattern borrowed from native graphics programming that simply did not exist in WebGL. For typical web applications this mainly means: less CPU time per frame and more predictability in complex scenes. Even when a given application does not actively use multiple threads for command creation, it still benefits from the batching principle alone through fewer individual driver calls per frame.


function renderFrame(device, context, pipeline) {
  const encoder = device.createCommandEncoder();
  const textureView = context.getCurrentTexture().createView();

  const renderPass = encoder.beginRenderPass({
    colorAttachments: [{
      view: textureView,
      clearValue: { r: 0.05, g: 0.05, b: 0.08, a: 1 },
      loadOp: 'clear',
      storeOp: 'store',
    }],
  });

  renderPass.setPipeline(pipeline);
  renderPass.draw(3);
  renderPass.end();

  device.queue.submit([encoder.finish()]);
}

4. Writing shaders in WGSL

WebGPU uses WGSL, the WebGPU Shading Language, its own text-based language for vertex, fragment, and compute shaders, instead of the GLSL known from WebGL. WGSL was specifically designed for safety and straightforward translation into the shader languages of the underlying native APIs, which helps browsers guarantee consistent behavior across platforms.

Syntactically, WGSL resembles a mix of Rust and C, with explicit type annotations and clearly defined input and output structures for each shader stage. Anyone already familiar with GLSL will find their way around quickly, but needs to get used to the stricter type rules and explicit struct declarations. Many tooling providers, including editor extensions for syntax highlighting and linting, now support WGSL as well, which has noticeably eased the learning curve compared to the language's early days.


const shaderCode = `
@vertex
fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
  var positions = array<vec2f, 3>(
    vec2f( 0.0,  0.5),
    vec2f(-0.5, -0.5),
    vec2f( 0.5, -0.5)
  );
  return vec4f(positions[index], 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4f {
  return vec4f(0.2, 0.7, 1.0, 1.0);
}
`;

const shaderModule = device.createShaderModule({ code: shaderCode });

5. Compute shaders for work beyond graphics

Besides classic graphics rendering, WebGPU offers full-fledged compute shaders that let you run arbitrary parallel computations directly on the GPU, such as physics simulations, image processing, or even smaller machine learning inference tasks. WebGL practically did not have this capability, workarounds via textures as data storage were required.

A compute shader is written in WGSL similarly to a graphics shader, but operates on work groups of parallel threads declared via @compute @workgroup_size(...). Results land in storage buffers, which can then be read back to the CPU or reused directly for further rendering.

6. Buffers, bind groups, and explicit memory management

Unlike WebGL, where uniforms and attributes are addressed through implicit locations, WebGPU manages memory resources via explicit GPUBuffer objects and GPUBindGroup structures that precisely define which resource is available at which binding slot in the shader. A buffer is created with a fixed size and a usage flag such as GPUBufferUsage.VERTEX or GPUBufferUsage.STORAGE, so the browser and ultimately the driver know from the start how the resource should ideally be placed in memory.

This explicitness initially feels like more boilerplate compared to the more permissive WebGL model, but it pays off in larger applications: bind groups can be created ahead of time and reused across frames instead of resetting state on every draw call. For teams coming from WebGL, this step is usually the biggest conceptual shift, since it requires defining resource layout and binding structure already at pipeline design time instead of assembling it casually at runtime.

7. When switching from WebGL pays off

For simple 2D visualizations, small 3D scenes, or projects with a limited time budget, WebGL often remains the more pragmatic choice, since the toolchain is more mature and considerably more libraries, tutorials, and ready-made examples exist. WebGPU pays off especially where CPU overhead from many draw calls becomes a real problem or where compute shaders are needed.

Projects that lean heavily on GPU-based physics, particle simulations with hundreds of thousands of elements, or custom rendering pipelines using modern techniques like deferred shading also benefit considerably more from WebGPU than from WebGL. The decision should hinge on actual bottleneck analysis, not mere curiosity about the new API. A pragmatic middle ground is to leave existing WebGL applications unchanged for now and use WebGPU specifically for individual new feature areas, for example a newly added particle simulation, while the rest of the application stays untouched.

8. Current state of browser support

Chrome and Edge now support WebGPU by default on desktop and increasingly on Android, Firefox offers support behind flags or already stable in recent versions, Safari is catching up with its own implementation progress. Support is growing noticeably faster than WebGL's did in its early years.

For production applications, feature detection via if ('gpu' in navigator) combined with a WebGL fallback path is still recommended, especially when the target audience includes older devices or browsers. Libraries like Three.js now offer experimental WebGPU renderers alongside their established WebGL renderer. Caution is also warranted on mobile devices: even when a GPUAdapter can be requested successfully, limits such as maximum buffer size or the number of simultaneous bind groups can differ noticeably between desktop and mobile GPUs, which is why a look at adapter.limits is worthwhile before production use.

9. A minimal, complete triangle

To make the concepts tangible, here is a complete, if minimal, example that assembles the previously described building blocks, device, shader module, render pipeline, and command buffer, into a working triangle. This pattern is the classic entry point in practically every WebGPU tutorial and forms the foundation for more complex scenes.

The table below summarizes the most important conceptual differences between WebGL and WebGPU and helps assess which API is the better foundation for a specific project.


async function createTrianglePipeline(device, format) {
  const module = device.createShaderModule({ code: shaderCode });

  return device.createRenderPipeline({
    layout: 'auto',
    vertex: { module, entryPoint: 'vs_main' },
    fragment: {
      module,
      entryPoint: 'fs_main',
      targets: [{ format }],
    },
    primitive: { topology: 'triangle-list' },
  });
}

const { device, context } = await initWebGPU();
const format = navigator.gpu.getPreferredCanvasFormat();
const pipeline = await createTrianglePipeline(device, format);
renderFrame(device, context, pipeline);
Aspect WebGL WebGPU Impact
Shader language GLSL WGSL Different syntax, stricter typing
Command model Immediate, state-based calls Command buffers, batched submission Less CPU overhead with many draw calls
Compute capability Not supported, texture workarounds Native compute shaders Direct GPU computation without detours
Browser maturity Very broad, stable for many years Growing, Chrome/Edge/Firefox leading WebGL remains a safe fallback

Mironsoft

Modern browser APIs, performance, and maintainable JavaScript

JavaScript that holds up in the real browser, not just in the tutorial?

We review existing frontend code for outdated patterns, unnecessary dependencies, and performance traps, then replace them with modern, native browser APIs that mean less bundle weight and less maintenance burden.

Code Review

Systematically finding outdated patterns, unnecessary dependencies, and memory leaks.

Performance Optimization

Improving bundle size, load time, and runtime performance with modern APIs.

Modernization

Deliberately introducing native browser APIs instead of heavy libraries.

10. Summary

WebGPU: The Essentials at a Glance

Entry point

Request a GPUAdapter, create a GPUDevice from it, the central starting point for all operations.

Commands

Command encoder collects operations, batched submission via the queue reduces CPU overhead.

Shaders

WGSL replaces GLSL, supports both graphics and compute shaders.

Decision

Switching pays off with many draw calls or real compute needs, otherwise WebGL stays pragmatic.

11. FAQ: WebGPU: The Essentials at a Glance

1What is the fundamental difference between WebGL and WebGPU?
WebGPU is based on modern native graphics APIs like Vulkan and Metal and uses an explicit command buffer model, while WebGL builds on the older OpenGL ES.
2What is a GPUDevice?
The GPUDevice is the central interface in WebGPU through which buffers, textures, pipelines, and queues are created and managed.
3What is WGSL?
WGSL is the WebGPU Shading Language, its own text-based language for vertex, fragment, and compute shaders that replaces GLSL in WebGPU.
4Can WebGPU be used for pure computation without graphics?
Yes, compute shaders let you run parallel computations like physics simulations or image processing directly on the GPU.
5Why is CPU overhead lower with WebGPU?
Because commands are batched into command buffers and submitted collectively, instead of triggering immediate state validation in the driver individually as in WebGL.
6Is WebGPU worthwhile for a simple project?
Usually not, for simple 2D or small 3D visualizations, WebGL often remains the more pragmatic choice due to its more mature toolchain.
7What is the current browser support for WebGPU?
Chrome and Edge support WebGPU by default, Firefox increasingly stable, Safari is catching up with its own implementation progress.
8Should WebGL be replaced entirely?
No, a WebGL fallback remains sensible as long as not all target browsers reliably support WebGPU.
9Do existing 3D libraries support WebGPU?
Yes, libraries like Three.js now offer experimental WebGPU renderers alongside their established WebGL renderer.
10How do you check whether a browser supports WebGPU?
With a simple check for 'gpu' in navigator before calling navigator.gpu.requestAdapter().