perf(ui): add additive DispatchFused to collapse consecutive UI passes (#47)
Add a fifth UI compute path — DispatchFused — alongside the four per-element Dispatch* calls. It composites quads → circles → images → text (canonical back-to-front order) in ONE dispatch that loads the destination image once and stores once, eliminating the per-pass load+store and the inter-pass VkMemoryBarriers a run of consecutive Dispatch* calls would pay. The win materialises at >= 2 non-empty categories; an absent category is a zero-trip, push-constant-uniform loop (~free). The new uber-kernel (shaders/ui-fused.comp.glsl) reuses each category's exact cooperative shared-memory tile-cull + per-pixel accumulate, so a fused category is pixel-identical to its standalone pass. Categories run sequentially per thread and share one set of shared-memory scratch, so the VGPR/shared high-water mark stays at the per-category level, not the sum. Additive and non-breaking: the per-element Dispatch* calls and the frozen 48-byte UIDispatchHeader are untouched. DispatchFused gets its own 128-byte push-constant layout (UIFusedHeader: four uvec4 headers + four per-category clip vec4s). To interleave a custom ui.Dispatch() between standard passes, bracket it with two DispatchFused calls — the dispatch boundary is the explicit, app-declared flush point. WebGPU (Vulkan-second): DispatchFused falls back to the per-element Dispatch* calls in canonical order — one texture/sampler per dispatch there can't feed the bindless image phase — so the result matches; only the load/store fusion is Vulkan-only. HelloUI now composites its background quads, mouse circle, and label text in a single DispatchFused. Verified rendering identical on a live Vulkan/Wayland session. Resolves #47 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c58b68ec40
commit
574242dd30
8 changed files with 720 additions and 8 deletions
|
|
@ -128,6 +128,19 @@ bool uiResolveScreenPixel(UIDispatchHeader hdr, out ivec2 screenPx) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Per-category clip test for the fused kernel (issue #47). The fused shader
|
||||
// loads/stores the destination image once for the whole surface but each
|
||||
// category carries its own clip rect, so the clip can only gate compositing —
|
||||
// not the shared load/store. This mirrors the integer-pixel comparison
|
||||
// uiResolveScreenPixel does (compare the pixel's top-left, not its center), so
|
||||
// a fused category is pixel-identical to its standalone Dispatch* pass.
|
||||
bool uiPixelInClipRect(uvec2 px, vec4 clip) {
|
||||
if (float(px.x) < clip.x || float(px.y) < clip.y) return false;
|
||||
if (float(px.x) >= clip.x + clip.z) return false;
|
||||
if (float(px.y) >= clip.y + clip.w) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Non-premultiplied "src over dst" blend. Both operands and result are
|
||||
// straight-alpha vec4. Use this when iterating items in a loop with a local
|
||||
// accumulator.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue