First Vulkan Wins: Four Fixes Worth 2x to 900x
A CPU compositor, a write-combined readback buffer, a debug build flag, and a per-frame bounds walk, each found by measuring before cutting.
CPU compositor to GPU, per frame
about 900x
GPU render path for the playable beach: about 1650 ms/frame (about 0.6 FPS) on the CPU software composite to about 1.8 ms/frame on the GPU, a reduction of about 900x. One CPU core had been rasterizing a 36-primitive palm every frame while the GPU sat idle.
Readback memory type
about 74x
Readback memory type: CPU reads from a write-combined memory type ran at about 137 MB/s, about 27 ms/frame. Switching to a host-cached type measured about 27,400 us/frame to about 368 us/frame (about 74x) on an RTX 5080 at 1920x1080, with byte-identical captured frames.
Compiler optimization (/O2)
2.3x
Compiler optimization: the canonical build had been compiling without optimization (MSVC default /Od). Enabling /O2 cut startup load from 13.4 s to 5.8 s (2.3x) and per-frame render by 1.8x, with determinism golden captures byte-identical.
Shadow-fit bounds memo
about 12-20x
Shadow-fit bounds memo: the shadow fit re-transformed about 59M vertices per frame. Caching per-item world bounds cut whole-frame time from about 391-407 ms/frame to about 20-33 ms/frame (about 12-20x), with the light matrix hash bit-identical across cached and uncached paths.
Empirical Findings Table
Four fixes in the first week of Vulkan rendering| Telemetry Metric | Measured Value | Context & Observability |
|---|---|---|
| CPU compositor to GPU, per frame | about 900x | GPU render path for the playable beach: about 1650 ms/frame (about 0.6 FPS) on the CPU software composite to about 1.8 ms/frame on the GPU, a reduction of about 900x. One CPU core had been rasterizing a 36-primitive palm every frame while the GPU sat idle. |
| Readback memory type | about 74x | Readback memory type: CPU reads from a write-combined memory type ran at about 137 MB/s, about 27 ms/frame. Switching to a host-cached type measured about 27,400 us/frame to about 368 us/frame (about 74x) on an RTX 5080 at 1920x1080, with byte-identical captured frames. |
| Compiler optimization (/O2) | 2.3x | Compiler optimization: the canonical build had been compiling without optimization (MSVC default /Od). Enabling /O2 cut startup load from 13.4 s to 5.8 s (2.3x) and per-frame render by 1.8x, with determinism golden captures byte-identical. |
| Shadow-fit bounds memo | about 12-20x | Shadow-fit bounds memo: the shadow fit re-transformed about 59M vertices per frame. Caching per-item world bounds cut whole-frame time from about 391-407 ms/frame to about 20-33 ms/frame (about 12-20x), with the light matrix hash bit-identical across cached and uncached paths. |
Engineering Action Directives
- Early in an engine's life, the largest wins tend to be configuration and placement mistakes rather than algorithm problems: work on the wrong processor, memory of the wrong type, or a debug compiler setting left on. None of these four required a new algorithm.
- Pairing every speedup with a byte- or bit-identity check made it cheap to accept large changes quickly, because a wrong result would have shown up as a hash mismatch rather than as a subtle visual drift.
Methodology Boundaries & Limits
- These are single-scene measurements of an early, small scene; later scenes are much heavier and the ratios do not transfer.
- Most commits report approximate values (with ~) and do not record run counts, percentiles, or hardware; only the readback fix names the GPU.
- The 900x and 12-20x figures compare against paths that were unusually slow, so they describe how bad the starting point was more than how fast the engine is.