perf(ui): skip the destination read-modify-write on empty tiles (#127) #144

Merged
catbot merged 1 commit from claude/issue-127 into master 2026-06-18 15:27:08 +02:00
Member

Summary

The standalone per-category UI compute shaders — ui-quads, ui-circles, ui-images, ui-text — unconditionally imageLoad the destination pixel at the top of main() and imageStore it at the end. On a sparse UI most 8×8 tiles are touched by no item in a given category, so this is a wasted full read-modify-write of the swapchain image per empty tile. The fused uber-kernel (ui-fused) already amortizes a single load/store across all four categories; the standalone Dispatch* path has no such umbrella and pays per pass.

Fix

Defer the imageLoad until just before the first surviving blend (gated by a loaded flag) and skip the trailing imageStore unless at least one item actually blended.

Output is bit-identical:

  • Before the first blend, dst is never read (uiBlendOver is the only consumer), so loading it lazily changes nothing.
  • A skipped store on an untouched pixel leaves it exactly as the old unconditional load → no-op → store would have rewritten it.

Applied to all four standalone shaders; the fused kernel is unchanged.

Testing

crafter-build test — 23/23 passing. The build recompiles all five UI shaders through glslang, and UIFusedShader revalidates the fused kernel; no GLSL or push-constant regressions.

No visible change (pure load/store-traffic reduction), so no screenshot.

Resolves #127

## Summary The standalone per-category UI compute shaders — `ui-quads`, `ui-circles`, `ui-images`, `ui-text` — unconditionally `imageLoad` the destination pixel at the top of `main()` and `imageStore` it at the end. On a sparse UI most 8×8 tiles are touched by no item in a given category, so this is a wasted full read-modify-write of the swapchain image per empty tile. The fused uber-kernel (`ui-fused`) already amortizes a single load/store across all four categories; the standalone `Dispatch*` path has no such umbrella and pays per pass. ## Fix Defer the `imageLoad` until just before the first surviving blend (gated by a `loaded` flag) and skip the trailing `imageStore` unless at least one item actually blended. Output is **bit-identical**: - Before the first blend, `dst` is never read (`uiBlendOver` is the only consumer), so loading it lazily changes nothing. - A skipped store on an untouched pixel leaves it exactly as the old unconditional load → no-op → store would have rewritten it. Applied to all four standalone shaders; the fused kernel is unchanged. ## Testing `crafter-build test` — 23/23 passing. The build recompiles all five UI shaders through glslang, and `UIFusedShader` revalidates the fused kernel; no GLSL or push-constant regressions. No visible change (pure load/store-traffic reduction), so no screenshot. Resolves #127
The standalone per-category UI shaders (quads/circles/images/text)
unconditionally imageLoad the destination pixel up front and imageStore
it at the end, paying a full read-modify-write even on tiles no item
touches — the common case for a sparse UI. The fused uber-kernel already
amortizes a single load/store across all four categories; the standalone
Dispatch* path has no such umbrella.

Defer the imageLoad to just before the first surviving blend (`loaded`
flag) and skip the imageStore unless something was actually blended.
Output is bit-identical: before the first blend `dst` is unused, and a
skipped store leaves the pixel exactly as a no-op load+store would have
rewritten it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
catbot merged commit e1222c0ab7 into master 2026-06-18 15:27:08 +02:00
catbot deleted branch claude/issue-127 2026-06-18 15:27:08 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Catcrafts/Crafter.Graphics!144
No description provided.