Under the Hood
Webgpu

Why WebGPU exists: what was wrong with WebGL

WebGPU is not WebGL 2.0 with nicer syntax — it is a fundamentally different shape built on a different generation of native GPU APIs. This lesson explains the three concrete problems with WebGL's design (a hidden global state machine, per-draw validation cost, and no compute), and how WebGPU's explicit pipelines, command buffers, and single queue answer each one — the mental model the rest of the module fills in.

Why WebGPU exists: what was wrong with WebGL

If you've been through the WebGL lesson in the Three.js module, you already met the thing WebGPU was built to replace: a giant global state machine you configure by binding one thing at a time and then triggering a draw against whatever happens to be bound. That design worked for fifteen years. WebGPU exists because the hardware underneath it, and our demands on it, both moved on — and the old shape started actively costing us. To understand WebGPU you don't start with its API; you start with the three specific things about WebGL that had to change. Every major WebGPU concept is a direct answer to one of them.

The one-line framing for the whole module: WebGPU trades WebGL's convenient-but-hidden global state for explicit, pre-validated objects — pipelines you bake once, resources you group deliberately, and commands you record into a buffer and submit to a single queue. That sentence is abstract now; by the end of this lesson each clause will have a concrete problem attached to it.

Problem 1: the hidden global state machine

Recall how you draw in WebGL: bind a buffer to a slot, set some attribute pointers, use a program, set uniforms, configure depth-testing and blending as global switches, then call drawArrays. The draw doesn't take those things as arguments — it operates on whatever is currently bound and set in the one global context. That's convenient for a first triangle and treacherous at scale.

The trouble is that the entire "how should this draw behave" state is spread across dozens of independent, sticky global settings. Forgetting to reset one after a previous draw silently corrupts the next. Rendering a hundred different objects means a torrent of little state-mutating calls between each draw, and the order matters. Worse, because any of that state could change at any moment right up until the draw call, the driver can't do much preparation ahead of time — it has to re-examine and re-validate a big pile of current state on every single draw to work out what machine configuration you actually want. Which is the second problem.

Problem 2: validation and translation happen per draw, on the hot path

Here's what a WebGL drawArrays actually triggers under the hood. The browser can't just forward your command to the GPU, because it doesn't trust it yet and the native driver needs a fully-specified pipeline configuration. So at draw time it has to gather all the currently-bound state, check it's internally consistent (do the shader's expected attributes match the bound buffers? is the blend state valid with this framebuffer?), and translate that into the underlying native API's notion of a pipeline. Every draw. On the frame's hot path.

Recall from the draw-calls lesson that draw-call overhead — not raw GPU work — is what bottlenecks most scenes, and that it's CPU-side cost. A big chunk of that CPU cost is exactly this per-draw validation-and-translation dance. The state machine's flexibility (you can change anything at any time) is precisely what forces the expensive per-draw re-derivation, because nothing can be settled in advance.

The fix almost writes itself: what if you declared the entire fixed configuration of a draw — its shaders, its vertex layout, its blend and depth state — once, up front, as a single immutable object the browser validates and translates a single time? Then at draw time there's nothing to re-derive; you just say "use pipeline #7" and issue the draw. That object is WebGPU's render pipeline, and baking state into it is the central move of the whole API. The flexibility you lose (you can't tweak one blend setting mid-draw) is exactly the flexibility that was making every draw expensive.

Problem 3: the GPU could only draw, not compute

The third limitation is the biggest in the long run and has nothing to do with performance tuning. WebGL is a rendering API. Its whole pipeline (the one from the GPU pipeline lesson) exists to turn triangles into pixels. But a modern GPU is not a triangle-drawing peripheral — it's a massively parallel general-purpose compute processor that happens to be very good at graphics. Thousands of cores running the same program over different data is exactly as useful for physics simulation, image processing, neural-network inference, and data crunching as it is for shading fragments.

WebGL gave the web no direct way to use that. People faked it — encoding data into textures, running a fragment shader over them, and reading pixels back — but it was a hack around an API that only knew how to draw. WebGPU adds compute shaders and storage buffers as first-class citizens: you can dispatch a grid of parallel work that reads and writes arbitrary GPU memory, with no triangles, no pixels, no rendering involved at all. This is why WebGPU is a bigger deal than "faster WebGL" — it opens general-purpose GPU compute (GPGPU) to the web platform. The compute shaders lesson is where the module lands, and it's the part with no WebGL equivalent to compare against.

The shape that answers all three

Put the three answers together and you have the skeleton of the entire API — which is why the rest of the module is essentially "here is each of these, in detail":

  • Instead of a global context you mutate, there's an explicit device and a single queue — the one channel through which all work reaches the GPU (lesson 2).
  • Instead of hidden driver-managed memory, you allocate buffers with explicit usage declared up front (lesson 3).
  • Instead of re-deriving state every draw, you bake it into a render pipeline once (lesson 4), write shaders in the new WGSL language (lesson 5), and connect resources through explicit bind groups (lesson 6).
  • Instead of each call immediately poking the driver, you record a batch of commands with a command encoder and submit them together (lesson 7) — which is how WebGPU makes the CPU→GPU handoff cheap.
  • And on top of all of it, compute (lesson 8) reuses the same machinery for non-graphics work.

More upfront work, and why it's worth it

Be honest about the tradeoff: WebGPU is more verbose to get a first triangle on screen than WebGL. You have to request a device, create buffers with explicit usage flags, define a pipeline, set up bind groups, encode commands, and submit — where WebGL let you bind-and-draw. That upfront ceremony is the point, not an accident: you are doing the validation and configuration work once, deliberately, at setup time, so that per-frame you're left with the cheap part — "set pipeline, set bind group, draw." WebGL's convenience was a loan against every future frame; WebGPU makes you pay the principal up front and then charges almost nothing per draw. For a static scene the difference is invisible; for a demanding one it's the difference between hitting the frame budget and missing it.

Where this goes next

Everything above is the "why." The rest of the module is the "how," and it starts at the only place it can: getting a connection to the GPU at all. The device, the adapter, and the queue walks the handshake — from navigator.gpu through the adapter (a specific GPU on the machine) to the device (your logical connection to it) and the queue (the single point where work is submitted) — the objects every later lesson assumes you already hold.

Go deeper

  • MDN — WebGPU API The reference for the device/pipeline/queue objects this lesson previewed, and the current browser-support picture.
  • WebGPU Fundamentals Builds the same explicit pipeline/encoder/queue model from scratch with runnable code, the counterpart to the WebGL Fundamentals site.
  • Surma — WebGPU (from a compute angle) A deep, mechanical walk through WebGPU that leads with compute — the capability this lesson argues is WebGPU's real reason to exist.

Check yourself

Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.

  1. Name the three concrete problems with WebGL's design that WebGPU was built to solve.
  2. Explain why WebGL's 'change any state at any time' flexibility is exactly what forces expensive per-draw work in the driver.
  3. What is a render pipeline object, and which WebGL cost does baking state into it eliminate? What flexibility do you give up in exchange?
  4. Tie this back to the draw-calls lesson: which part of per-draw CPU overhead does WebGPU's up-front validation remove?
  5. Why is 'the GPU can only draw' described as WebGL's biggest limitation in the long run, and what two WebGPU features remove it?
  6. WebGPU is more verbose than WebGL for a first triangle. Explain why that upfront cost is the design goal rather than a flaw.
  7. In one sentence each, say what the device+queue, the pipeline, bind groups, and the command encoder replace from the WebGL state-machine model.