Skipping pixels and tearing down a 4K frame

We play and test Wreckwing on a 4K display. In the largest city at 4K, on the High preset, in Low Power Mode, the graphics card takes about 30 ms to draw a frame in our benchmark, which is about 33 frames a second. The biggest single step of that frame is the one that works out what every pixel looks like: what surface it shows, and how that surface is lit. It took 13.1 ms of the 30.
Taking the frame apart
That step runs in two stages. First every pixel records its surface (color, roughness, which way it faces, how far away), which graphics programmers call the G-buffer. Then a second pass lights each pixel from those records: the sun and its shadows, the sky, the street’s light bounced up onto walls, light from rooms behind broken windows, and the small moving lights of missiles, engines and fires.
To find where the 13.1 ms went, the benchmark can now leave one part out at a time. The picture comes out wrong, but the timing shows what that part costs. Each run flies the camera down the same avenue for 20 seconds, twice, in alternating order.
| Left out | Time for the step | So that part costs |
|---|---|---|
| Nothing | 13.1 ms | |
| All the lighting | 8.7 ms | 4.5 ms |
| The small moving lights | 10.7 ms | 2.5 ms |
| How much sky each spot sees | 12.3 ms | 0.9 ms |
| The street’s bounced light | 12.7 ms | 0.5 ms |
| Room lights, and softening the sun’s shadow edges | 13.2 ms | nothing measurable |
More than half of the lighting’s cost was the small moving lights. There are up to twelve of them, and every one of the eight million pixels on the screen checked each of them three times: for the light itself, for its glint on shiny surfaces, and for its glow in smoky air.
Most pixels are nowhere near a light
Each of these lights reaches from a few meters out to 16 m, a missile’s light the farthest. Seen from the street, almost every pixel is far outside every one of those reaches, so each pixel checked twelve lights and found that none of them touched it.
A light’s reach is a ball around it. Before the frame is drawn, the game now works out the rectangle on the screen that the ball could cover, padded a little so that it never misses a pixel. A pixel checks only the lights whose rectangle it falls inside.

A light added nothing to the pixels outside its rectangle before this change either, so the picture is unchanged. A test draws a wall lit by three lights (one in front of it, one 30 m off to the side, one behind the camera) both ways, and the two frames are identical to the byte. The step went from 13.8 to 11.8 ms on average over two runs each, about 2 ms of the 30 ms frame.
The recording also shows the limit. When the camera is inside a light’s reach, which happens often with the player’s own engines just ahead of the chase camera, that light’s rectangle is the whole screen and every pixel checks it. The other lights are still skipped.
What is left
With the lights done, we split the surface records the same way:
| Left out | Saves |
|---|---|
| Every material’s detail (all surfaces plain gray) | 2.9 ms |
| Color variation between buildings | 0.55 ms |
| Weathering | 0.3 ms |
| Settled dust | 0.3 ms |
| Broken glass on the ground, cracked pavement | about 0.1 ms each |
Part of the 2.9 ms is the noise that gives concrete its stains and mottling, worked out fresh at every pixel from several layers of random patterns. A throwaway build with the noise held constant ran about 1.3 ms faster. Reading the noise from a baked, tiling texture would save most of that, with a different pattern of stains.
Another 3.5 ms goes to drawing the city’s three million triangles into the G-buffer before any pixel is shaded. A second throwaway build skipped the buildings off the screen for that stage and saved about 0.4 ms, so most of the 3.5 ms is spent on what is in view. An earlier try at the same skip shaded 75 pixels differently, and 0.4 ms is not worth that, so it stays out.
The baked noise and fewer samples of how much sky each spot sees both change the picture, so each will be compared with before-and-after stills before it goes in.
How this post was made
- Drafted by
- A Claude Code session in the trilithon.games repository, October 13, 2026, working from the game's dev journal, engine notes and commit messages.
- What it did
- Drafted this post from the game session's journal entry of October 12, updated it with two measurements made after the entry, and checked each number against the commit messages and engine notes.
- Reviewed by
- Alex van Rossum,
Linear MIP2-549commits bf73714, f798b75, c882348