Why a WebGPU device was destroyed on iPhone: too many shaders
· 2 min read · webgpu, ios, three.js, performance, debugging
On an older iPhone, the WebGPU device of Threejs-Punk was destroyed a second or so after the render loop started: GPUDeviceLost, reason destroyed, no message, and every later frame threw "Unable to make command encoder". No texture or vertex budget moved it.
The cause: the number of programs #
three keys programs by the text of the WGSL. Two InstancedMeshes with the same material and layout differ only in counters, such as the id of the instance-matrix storage buffer and a few temporary variable names. The metro's roughly 200 instanced meshes made about 200 modules and pipelines for about 70 different programs.
The fix: canonical shaders #
A small pass renumbers those identifiers by first appearance before the program lookup. Bindings go by index, never by name, so it is safe. On the metro at the lite phone budget:
- modules 370 to 90,
- render pipelines 332 to 66,
- the warm-up's wait 211 to 21 pipelines,
- same frame.
On desktop it also helped the cold start: compile 21.2 to 14.3 s (-32%), modules 640 to 216.
How it was found #
Bisecting on the device. Cutting the buildings and towers kept it alive; cutting one small layer did not. The Web Inspector over USB shows GPUDevice.lost, so log it. The check for a new look is to wrap createShaderModule and compare the count of modules with the count of distinct codes once digits are stripped.
Other phone fixes from the same device #
- Wet-asphalt maps became one shared texture on phones, 1024 instead of three 2048 copies.
- glTF image textures are redrawn at 512 before upload.
- A title-screen effect that compiled on the first frames is off on iPhone and iPad.
A related trap #
On iPhone, the page's DOM and its filters share the GPU process with WebGPU. filter, drop-shadow, backdrop-filter and blurred shadows on elements that change per frame can destroy the device too. In this game the cure was no filters or animated shadows over the canvas. More on startup in the compile-time post, and the project is on its project page.
Related projects
- Threejs-Punk — Fully featured Racing Game