What a shard costs, measured

Every piece in a shatter is evaluated at every pixel, so the cost is linear in piece count over the whole screen and everyone knows to be nervous about it. Nobody seems to know the constant. Here it is, measured rather than reasoned about.

Left: 16 pieces. Right: 128. The right half is eight times the pieces and about three and a half times the frame cost, because a fixed floor dominates the low end.

The measurement

A WebGL2 fragment shader, 960×540, one piece being a circle intersected with two half planes, which is the same construction the destruction notes use. Median of 90 frames after a 40 frame warm-up, on an Apple M4 Max.

PiecesNo boundBounding testSaved
10.112 ms0.100 msn/a
40.100 ms0.112 msn/a
80.125 ms0.125 ms0%
160.162 ms0.125 ms23%
320.213 ms0.163 ms23%
640.350 ms0.250 ms29%
1280.563 ms0.438 ms22%

Three things in that table

1. There is a floor, and below it the piece count is irrelevant

One piece and four pieces measure the same, around 0.10ms. That is not the shader, it is the cost of issuing a full-screen draw at all. Anything under about eight pieces is free in the sense that removing them buys you nothing measurable.

Which is a useful thing to know before optimizing. The first instinct on seeing a shatter cost 0.11ms is to reduce the piece count, and at that end of the curve reducing it to zero would save 0.01ms.

2. Above the floor it is exactly linear, at 0.007 ms per piece per megapixel

From 8 pieces to 128 the cost rises 0.438ms over 120 pieces: 0.00365ms per piece at 0.518 megapixels. The fit is close enough to be boring, which is what you want from a cost model. Predicted 32 pieces: 0.213ms. Measured: 0.213ms.

Normalized, 0.0070ms per piece per megapixel. That number travels, which is the point of measuring it:

ResolutionPer piecePieces in a 2ms budget
1280×7200.0065 ms~310
1920×10800.0146 ms~137
2560×14400.0259 ms~77

So a hundred shards at 1080p is about 1.5ms, which is a real cost inside a 16.7ms frame and an entirely affordable one for something that happens for half a second. A thousand shards is 15ms and is not a thing you can do this way.

3. The bounding test buys 22%, and that is the surprising one

The standard advice is to reject each piece with a cheap bounding circle before evaluating its terms, and the mental model behind that advice is that most pixels are far from most pieces, so most of the work disappears. Measured, it removes about a fifth.

Two reasons, and both are worth internalising because they generalise past this case:

That does not make it useless. A fifth off is a fifth off, it costs three lines, and it grows with piece count as more pieces become uniformly distant. It does mean the optimization is not the thing standing between you and a thousand shards.

What actually raises the ceiling

In order of how much they buy:

Rules of thumb

  1. Budget 0.007ms per piece per megapixel for a per-pixel loop, and verify it on your own hardware in an afternoon.
  2. Under about eight pieces, the piece count is not what you are paying for.
  3. A hundred pieces at 1080p is roughly 1.5ms. A thousand is not a per-pixel loop.
  4. A bounding test buys about a fifth, not an order of magnitude, because the test is most of the piece and the branch is per group.
  5. Past a few hundred pieces, switch to one quad per piece. That is a complexity change, not a constant-factor one.
  6. Measure with the queue forced to complete. Timing a draw call without a flush measures how fast commands were queued.

All 61 notes How to use them Credits