If you already make mipmaps another way, this is what changes when you switch, and where we chose to do something different. Each point links to the full record in DIVERGES.md, with the measurements behind it.
- Coming from render-pass mipmaps (three.js, WebGL, most engines)
- Coming from AMD SPD, FSR or webgpu-spd
- Coming from nvpro_pyramid
- Building a Hi-Z pyramid
- What we don't do (yet)
Your mipmaps look the same. By default every level is what one render pass per level gives you, including the odd sizes. You can swap downsampler in without a visual change.
It's faster, except for sRGB on NVIDIA. A full chain takes:
| GPU | rgba8 | rgba16float | sRGB colour |
|---|---|---|---|
| Apple M5 Pro | 3–27× faster | 4.5× faster at 1024² | faster up to 720p, slower from 1080p |
| NVIDIA RTX 3060 Laptop | 0.77–0.95× the time | 0.77–1.01× | 2–3× slower |
| Intel UHD | 0.48–1.05× | 0.62–0.96× | not measured |
Times are for screen sizes from 960×540 to 2560×1440, plus 1024² and 4096². Cube maps gain the most (3–4× on the RTX, 27× on the M5) because every face goes through the same dispatches. sRGB textures can't be written by compute shaders, so they take a slower path; on NVIDIA, keep three.js's own mipmaps for them. (#16, #17)
The texture needs STORAGE_BINDING. That's how compute shaders write the levels. It costs nothing when you sample
the texture later: we measured that on Apple, NVIDIA and Intel. (#18)
Half-float textures come out slightly more accurate. On Windows and Apple, the GPU's own conversion to half floats
truncates. We round to nearest, so every GPU gives the same result. It costs 3–9 % of an rgba16float chain;
f16Rounding: 'device' turns it off. (#25)
Results can differ by a step between GPUs. That's true of render passes too: each vendor's sampler rounds its
filter weights a little differently. If you need identical bits everywhere, use averageAccess: 'load'
(getting started).
The kernel is SPD's idea, a 64 × 64 tile reduced through six levels in workgroup memory, written independently in WGSL. Notes for SPD users map every SPD option to ours. The differences that matter:
- Two dispatches, not one. SPD's last workgroup finishes the chain using an atomic counter. WGSL doesn't guarantee that handoff works, and on Apple we saw webgpu-spd return stale levels in up to 77 % of frames. Two dispatches are also no slower. (#1)
- Odd sizes match render passes by default. SPD drops the last row or column. If you want SPD's results (it's
the fastest), use
oddSizes: 'spd'. (#3) - Min/max stays conservative. SPD's dropped rows and columns make its Hi-Z miss texels; ours covers every texel at every size. (#4)
- No zero reads past the edge. SPD reads zeros there unless you pad the texture; AMD's own D3D12 sample darkens the 1×1 that way. We clamp. (#5)
- Each level is rounded as it's stored, so a chain equals level-by-level generation. Under
oddSizes: 'spd'we keep SPD's rounding. (#7) - No size cap. SPD stops at 4096² and 12 levels. (#21)
- Partial updates are exact. SPD snaps a sub-rectangle to 64-texel tiles; we rewrite only the texels that depend on it. (#15)
- Integer formats average exactly. webgpu-spd's integer averages truncate and overflow. (#24)
oddSizes: 'box' gives nvpro's area weights, so every level keeps the image's mean; it takes 1.2–2× the time of the
default. We don't carry odd sizes inside one dispatch by default: the halo option does, as nvpro's halo does, and
it's 0–15 % faster on NVIDIA but not on Intel, so it stays opt-in for now. (#2, #3)
Many engines build a power-of-two pyramid from a screen-size depth buffer with one 2 × 2 sample per texel. At
1920 → 1024 a texel's area covers up to 3 depth texels per axis, so that shortcut misses some and can cull visible
objects. resample: 'area' takes every depth texel each pyramid texel touches. (#23)
| Not supported | Why | Workaround |
|---|---|---|
| Wrap edges (tiling textures) | Clamp only; wrap would change odd-size borders and break render-pass parity | A faint seam at odd sizes only (#6) |
| Alpha-coverage preservation | Not built; it fits with the transform hooks that are planned | Cutout foliage may thin out in the distance (#9) |
Video frames (texture_external) as a source |
Not built | Copy the frame into a texture first (#14) |
| Subgroup or fp16 kernels | Measured: not faster, and fp16 loses precision | — (#10, #11) |
AMD and mobile GPUs haven't been measured yet. If you try it there, npm run lab runs the test suite on your device.