# Slice Tilt: from 40 seconds of shader compile to 6

> A first visit spent 35-39 s compiling shaders. Pre-compiling before the first frame and using BatchedMesh cut it to 5-7 s, measured with a cold driver cache.

Canonical page: https://andersonmancini.dev/blog/slice-tilt-compile-time-batchedmesh

Published: 2026-09-12 · 2 min read · Tags: webgpu, three.js, performance, game-dev, batchedmesh

How long [Slice Tilt](https://andersonmancini.dev/blog/slice-tilt.md) takes to become playable is, on a first visit, almost all shader compilation. This is what was slow and what changed.

## Measure with a cold cache

The driver keeps what it compiled, so the second visit of anything is **30 to 150 times faster** than the first. A measurement taken on a machine that has run the page before measures nothing a new player will see. The probe gives every shader a constant that is new in each page load, so the driver cannot answer from its cache.

## What was slow (about 40 s)

1. **Frames ran before the pre-compile did.** `compileAsync(scene)` was called without a camera: it threw, a `.catch` hid the error, and nothing was compiled ahead. 563 pipelines were made on the render path, one at a time.
2. **One shader per `InstancedMesh`.** three keys instanced meshes on the object's uuid, so 186 instanced meshes with one material were 186 builds and 2.5 MB of WGSL.
3. **The ball's reflection probe** draws the slice into a cube, another render context, so 85 more pipelines on the first frames.
4. `compileAsync` itself compiles one at a time and yields a lot.

## What changed

- The canvas does not draw until a custom pre-compile resolves. All pipelines are in flight at once, 16 at a time, including objects out of the frustum, and errors are not swallowed.
- The probe's cube pass is part of the pre-compile.
- About 140 InstancedMeshes became **one `BatchedMesh` per kit** and two for the dressing.

## Results (cold, M2 Max)

| | before | after |
|---|---|---|
| ready | 35-39 s | 5-7 s |
| CPU x4, a phone's main thread | 43 s | 12 s |
| shader modules | 602 | 210 |
| pipelines on the render path | 563 | 26 |

## A trap worth knowing

`NodeMaterial` reads `instanceColor` while it builds, and three's cache key does not include it. An InstancedMesh that gets its colors later, in the first frame, keeps the shader built without them and draws **white**, with no warning. Create colors where the mesh is made.

The same family of work on another game is in [the Threejs-Punk startup post](https://andersonmancini.dev/blog/threejs-punk-startup-compile-time.md). The project is on its [project page](https://andersonmancini.dev/projects/slice-tilt.md).

## Related projects

- [Slice Tilt](https://andersonmancini.dev/projects/slice-tilt.md) — Free pinball on a four-slice pizza
