Course outline
WebGPURenderer and Its Fallback
In one line
WebGPURenderer from three/webgpu uses WebGPU when the browser has it and WebGL 2 when it does not, with the same code. It starts asynchronously, so you await renderer.init() before the first frame and read renderer.backend to see what you got.
Think of it like this
A restaurant has a new kitchen with better equipment, but some days the new ovens are not ready. A good head chef checks the kitchen first, then uses the new ovens if they work and the old ones if they do not. The customers get the same menu either way. WebGPURenderer is that chef: it checks for WebGPU, and falls back to WebGL 2 without you changing your scene code.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- WebGPU
- A newer browser API for the graphics chip. It is explicit and modern, and it supports compute shaders.
- backend
- The layer that talks to the browser's graphics API.
WebGPURendererhas a WebGPU backend and a WebGL 2 backend. - fallback
- The plan B that runs when plan A is missing. Here, WebGL 2 runs when WebGPU is not available.
- adapter
- The browser's handle for one physical or software GPU.
navigator.gpu.requestAdapter()returns one, ornull. - init()
- The async method that chooses the backend and creates the GPU device. Nothing can be drawn until it finishes.
- output pass
- The last small draw that copies the frame to the canvas, applying tone mapping and colour space.
Step by step
01Import from three/webgpu
import * as THREE from "three/webgpu" replaces from "three". It carries the same classes plus the new renderer. If you only need a few names, import { WebGPURenderer, Scene } from "three/webgpu" works the same.
02Create, then await init
new THREE.WebGPURenderer({ antialias: true }) creates the object at once, with nothing chosen yet. await renderer.init() is where the browser is asked. Top-level await works in a module, as here and in Vite.
03Read the backend
After init(), renderer.backend.isWebGPUBackend tells which one is in use. Log it once at start-up and put it in bug reports: "it looks wrong" means two different things on two backends.
04Draw and count
The rest is the code you know: scene, camera, setAnimationLoop, render. The first example prints one frame's draw calls (2) and triangles (13): the cube's 12, plus the output pass's 1.
05Plan for the missing case
Nothing extra is needed for a browser without WebGPU: the fallback is automatic. What you must plan for is the features that only WebGPU has, such as workgroup memory in compute (Topic 13.5), and the visitor without WebGL 2 either, which Phase 1's support check already handles.
Try it yourself
- 1
Force WebGL 2
In the first example change the constructor to
new THREE.WebGPURenderer({ antialias: true, forceWebGL: true }). The console printsbackend: WebGL 2 (fallback)and no warning line. The label says "fallback" because the code only testsisWebGPUBackend: a forced WebGL 2 is not a fallback, so check what you passed if the difference matters. - 2
Use a bigger scene
Change
BoxGeometry(1, 1, 1)toSphereGeometry(0.8, 64, 32). The triangle count in the console becomes 3,969: the sphere's 3,968 triangles plus the output pass's 1. Draw calls stay at 2.
Code & diagrams
No `output` is set because the line differs by browser. In a browser with WebGPU it prints `backend: WebGPU` and no warning. In the checker's browser it prints the fallback warning and `backend: WebGL 2 (fallback)`. Look at the status bar under the canvas: it names the backend too.
Console
Run the scene to see its console.`forceWebGL: true` gives the same result in every browser, so this example has a fixed output. The error text is three.js's own.
Console
Run the scene to see its console.Expected console output
too early: THREE.Renderer: .render() called before the backend is initialized. Use "await renderer.init();" before rendering.
backend: WebGL 2
first frame: draw calls 2 | triangles 13The example hides `navigator.gpu` to act like such a browser. The renderer does not throw: it warns and switches. The output is the same in every browser here, including the warning line.
Console
Run the scene to see its console.Expected console output
navigator.gpu: undefined
THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.
backend: WebGL 2 (fallback)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget await renderer.init()
In the first example delete the await renderer.init(); line.
Break #2
Use the WebGL renderer name
In the first example change new THREE.WebGPURenderer( to new THREE.WebGLRenderer(.
Myth vs fact
Myth
WebGPURenderer needs WebGPU, so I must keep a second code path for the other browsers.
Fact
It has the WebGL 2 path built in. You keep one scene. You only need extra code for features that exist on WebGPU alone, such as compute with shared memory.
Myth
WebGPU is always faster.
Fact
Not for every scene. A simple scene can run the same on both, and the WebGPU renderer starts slower and downloads more. Measure your own scene on both backends with the status bar.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
three.js requests every optional feature the adapter offers, but no extra limits. A limit such as
maxComputeInvocationsPerWorkgrouptherefore stays at the WebGPU default (256), below what the hardware can do. To get more, passrequiredLimitsin the renderer options (the request fails if the adapter cannot grant it). - ▸
If the GPU device is lost while the page runs (a driver reset, for example),
WebGPURendererstops drawing, andrender()and compute calls return without doing anything. It does not recover by itself: the hook isrenderer.onDeviceLost. Topic 18.2 covers loss and recovery. - ▸
renderer.debug.getShaderAsync(scene, camera, object)returns the exact vertex and fragment shader text three.js made for an object, in WGSL or GLSL depending on the backend. Topic 13.3 uses it to read what TSL becomes.
Remember this
- 1
The problem this solves: WebGL 2 follows OpenGL ES 3.0 (2012), an older design that cannot do everything modern ones can, such as general-purpose compute. WebGPU can. But WebGPU is not in every browser. If you wrote only for WebGPU, some visitors would see nothing. One renderer that covers both lets you move forward without dropping them.
- 2
Where it lives.
import * as THREE from "three/webgpu"gives you everything inthreeplusWebGPURendererand the node materials. Classes are shared with"three": aMeshfrom either import is the same class, because both entry points import one shared core file, so your other modules,OrbitControlsand the loaders keep working. The exception isWebGLRenderer, whichthree/webgpudoes not export:new THREE.WebGLRenderer()there throwsTHREE.WebGLRenderer is not a constructor. - 3
It starts asynchronously. Asking the browser for a GPU takes time, so the renderer cannot be used the moment you create it.
await renderer.init()picks the backend and creates the device. Callrender()before it finishes and three.js throws:THREE.Renderer: .render() called before the backend is initialized. Use "await renderer.init();" before rendering.setAnimationLoopis more forgiving, but wait forinit()anyway when you want to readrenderer.backend. - 4
Which backend am I on? After
init(),renderer.backend.isWebGPUBackendistruefor WebGPU. When WebGPU cannot start (nonavigator.gpu, no adapter, a blocklisted driver), three.js logs the warningTHREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.and uses its WebGL 2 backend.new WebGPURenderer({ forceWebGL: true })skips WebGPU on purpose, which is useful for testing the fallback and for comparing. - 5
What is different when you measure.
renderer.info.render.drawCallsandtrianglesdescribe the current frame, butinfo.render.callscountsrender()calls since the start, not draw calls as inWebGLRenderer. A frame with one cube shows 2 draw calls and 13 triangles. The extra call is a full-screen triangle: the scene is drawn into a hidden texture, and this triangle copies it to the canvas while applying tone mapping and the output colour space. - 6
What it costs. The smallest scene built with Vite 8.3.4 is 524.77 kB of JavaScript (130.25 kB gzip) with
WebGLRendererand 781.97 kB (212.90 kB gzip) withWebGPURenderer, measured for this course. Start-up takes longer too, because the device and pipelines are created asynchronously. Choose it for what it gives you: node materials, TSL and compute.
Explain it without notes
Why must you await renderer.init() with WebGPURenderer, and what happens if you do not?
A visitor reports the scene looks different in Firefox on Linux. What do you check first?
Practice
Write a function createRenderer(container) that returns a started WebGPURenderer and logs the backend name. What must it be?
Using the first example, make a version that stays on WebGL 2 when the URL contains ?gl=1.
Trade-offs
- ↔
Pick
WebGPURendererwhen you want TSL, node materials or compute. PickWebGLRendererwhen you rely onShaderMaterial,onBeforeCompileorEffectComposer, or when the smaller download matters more.
Done when you can
Start a WebGPURenderer with
await renderer.init()and read which backend it choseExplain the fallback warning and when you see it
Force WebGL 2 with
forceWebGLto test the fallbackRead
info.render.drawCallsand explain why one cube shows 2