Files
codegirl-games/.github/issues/simplify-draw-sprite-clip-math.md
T
2026-08-09 05:56:12 +00:00

89 lines
3.1 KiB
Markdown

# Reduce repeated clip-space work in `draw_sprite`
## Summary
`draw_sprite` calls `to_clip` four times for an axis-aligned quad:
```odin
p0 := to_clip(x0_px, y0_px, sw, sh)
p1 := to_clip(x1_px, y0_px, sw, sh)
p2 := to_clip(x1_px, y1_px, sw, sh)
p3 := to_clip(x0_px, y1_px, sw, sh)
```
This repeats the same divisions and converts duplicate x/y coordinates. An
axis-aligned sprite has only two unique x values and two unique y values.
This is a measurable optimization, but it is low priority at the current
128-sprite limit because the absolute saving is small.
## Evidence
The checked-in unoptimized profiles report `engine::to_clip` at 5.33% and 5.51%
self time. Those percentages are inflated by the unoptimized profiling build,
so the change was also measured with `-debug -o:speed`.
A temporary benchmark used the real baked toad metadata, changed sprite
position on every iteration, preallocated the queue, and performed two million
draws per mode over seven trials:
| Mode | Median time per draw |
| --- | ---: |
| Current `draw_sprite` | 25.128 ns |
| Precomputed clip scale and reused coordinates | 21.531 ns |
| Same math plus cached `Frame_Def` | 21.778 ns |
Simplifying the math improved isolated draw time by approximately **14.3%**.
Caching the resolved frame did not provide an additional benefit and should not
be included without new evidence.
At `MAX_SPRITES == 128`, the measured math saving is only about 0.46
microseconds per completely full frame. GPU/driver work dominated the
end-to-end benchmark, so this should follow the profiling and deterministic
benchmark improvements.
Follow-up paired full-frame experiments at the 128-sprite cap found no larger
renderer-architecture win: buffer cycling improved median frame time by 0.7%,
GPU instancing by 1.1%, and texture sorting by 1.9%, while culling, queue
repacking, and indexed quads were neutral or slower. The clip-space change
therefore remains the strongest measured optimization specifically inside
`draw_sprite`, although its absolute frame impact is still small.
Environment:
- Odin `dev-2026-05-nightly:ea5175d`
- Optimized with `-debug -o:speed`
- Linux x86-64
## Proposed change
Compute clip scaling once and construct corners from the unique coordinates:
```odin
sx := 2.0 / f32(app.swapchain_w)
sy := 2.0 / f32(app.swapchain_h)
left := x0_px * sx - 1
right := x1_px * sx - 1
top := 1 - y0_px * sy
bottom := 1 - y1_px * sy
p0 := Vec2{left, top}
p1 := Vec2{right, top}
p2 := Vec2{right, bottom}
p3 := Vec2{left, bottom}
```
Keep `to_clip` for general callers and its existing tests; this change only
specializes quad construction inside `draw_sprite`.
## Acceptance criteria
- Existing sprite geometry, camera, UV, and batching tests pass.
- Add or extend a test that compares all four generated corners against
`to_clip` for representative viewport and sprite coordinates.
- Flipped and unflipped sprites produce identical vertices to the current code.
- An optimized deterministic benchmark shows at least a 10% improvement in
isolated `draw_sprite` time under comparable conditions.
- Do not add a per-sprite frame cache as part of this issue.