perf(ui): cooperative shared-memory tile culling in UI compute shaders #85

Merged
catbot merged 2 commits from claude/issue-46 into master 2026-06-16 18:03:16 +02:00
Member

Summary

Each 8×8-tile workgroup previously had all 64 threads walk the entire item list — re-reading every item from the SSBO 64× and running the per-pixel reject even for items that never touch the tile. That is O(tiles × N) item loads, dominated by SSBO traffic (32,400 tiles at 1080p).

This adds a cooperative shared-memory tile cull (the interim, hazard-free fix called out in the issue): the workgroup streams the item list in chunks of 64, each thread loads one item and tests its AABB against the whole tile, and survivors are compacted into shared memory. Every thread then runs the per-pixel accumulate over only those survivors.

  • SSBO traffic ~64× lower — each item is read once per workgroup instead of once per pixel.
  • Inner loop shrinks to the items that actually overlap the tile.

Correctness (load-bearing)

Draw order is preserved exactly — chunks run in array order and the in-chunk compaction is a stable in-order scan, so later items still overdraw earlier ones. No atomic-append nondeterminism (the blending hazard the issue warns about). The inner per-pixel loop body is byte-for-byte the original, and the tile cull is a conservative superset of the per-pixel reject (it can only keep more items, never drop one a pixel needs), so output is pixel-identical. Threads no longer early-return, so every thread reaches the workgroup barriers.

Applied to ui-quads, ui-circles, ui-images, ui-text; shared helpers (uiTileBounds / uiAabbOverlapsTile) added to ui-shared.glsl.

Scope is the GLSL/Vulkan shaders named in the issue; the WebGPU/WGSL mirror is unchanged and could be a follow-up.

Testing

  • crafter-build test → 3 passed (MouseScroll, BLASBuildOptions, PushConstantRewrite).
  • All four shaders compile under glslang.
  • Ran the HelloUI example natively (Vulkan) — output is identical to the pre-change baseline, no validation errors. All four shaders exercised (button + text, slider track/fill quads, slider thumb + mouse circle, progress bar).

Screenshots

HelloUI rendered with tile-culled shaders

Resolves #46

## Summary Each 8×8-tile workgroup previously had all 64 threads walk the **entire** item list — re-reading every item from the SSBO 64× and running the per-pixel reject even for items that never touch the tile. That is O(tiles × N) item loads, dominated by SSBO traffic (32,400 tiles at 1080p). This adds a **cooperative shared-memory tile cull** (the interim, hazard-free fix called out in the issue): the workgroup streams the item list in chunks of 64, each thread loads one item and tests its AABB against the whole tile, and survivors are compacted into shared memory. Every thread then runs the per-pixel accumulate over only those survivors. - **SSBO traffic ~64× lower** — each item is read once per workgroup instead of once per pixel. - **Inner loop shrinks** to the items that actually overlap the tile. ## Correctness (load-bearing) Draw order is preserved exactly — chunks run in array order and the in-chunk compaction is a **stable in-order scan**, so later items still overdraw earlier ones. No atomic-append nondeterminism (the blending hazard the issue warns about). The inner per-pixel loop body is byte-for-byte the original, and the tile cull is a conservative *superset* of the per-pixel reject (it can only keep more items, never drop one a pixel needs), so output is **pixel-identical**. Threads no longer early-return, so every thread reaches the workgroup barriers. Applied to `ui-quads`, `ui-circles`, `ui-images`, `ui-text`; shared helpers (`uiTileBounds` / `uiAabbOverlapsTile`) added to `ui-shared.glsl`. Scope is the GLSL/Vulkan shaders named in the issue; the WebGPU/WGSL mirror is unchanged and could be a follow-up. ## Testing - `crafter-build test` → 3 passed (MouseScroll, BLASBuildOptions, PushConstantRewrite). - All four shaders compile under glslang. - Ran the HelloUI example natively (Vulkan) — output is identical to the pre-change baseline, no validation errors. All four shaders exercised (button + text, slider track/fill quads, slider thumb + mouse circle, progress bar). ## Screenshots ![HelloUI rendered with tile-culled shaders](https://forgejo.catcrafts.net/attachments/53ccab23-70c3-4c15-8219-007c87309bf3) Resolves #46
Each 8×8-tile workgroup previously had all 64 threads walk the entire
item list, re-reading every item from the SSBO 64× and running the
per-pixel reject for items that never touch the tile — O(tiles × N)
item loads dominated by SSBO traffic.

The workgroup now streams the item list in chunks of 64: each thread
loads one item, tests its AABB against the whole tile, and the survivors
are compacted — in original buffer order — into shared memory. Every
thread then runs the unchanged per-pixel accumulate over only those
survivors. This drops SSBO traffic ~64× (each item read once per
workgroup instead of once per pixel) and shrinks the inner loop to the
items that actually overlap the tile.

Draw order is preserved exactly: chunks run in array order and the
in-chunk compaction is a stable in-order scan, so later items still
overdraw earlier ones (no atomic-append nondeterminism). The inner loop
body is byte-for-byte the original per-pixel logic, so output is
pixel-identical — the cull only decides which items reach it. Threads
no longer early-return so every thread reaches the workgroup barriers.

Applied to ui-quads, ui-circles, ui-images and ui-text; shared helpers
(uiTileBounds / uiAabbOverlapsTile) live in ui-shared.glsl.

Resolves #46

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
catbot merged commit 9ffc609a92 into master 2026-06-16 18:03:16 +02:00
catbot deleted branch claude/issue-46 2026-06-16 18:03:16 +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!85
No description provided.