Commit graph

18 commits

Author SHA1 Message Date
42a479572d SPDX license update 2026-07-22 18:09:06 +02:00
catbot
88541bf44e Merge remote-tracking branch 'origin/master' into claude/issue-123
# Conflicts:
#	implementations/Crafter.Graphics-UI-Shared.cpp
#	interfaces/Crafter.Graphics-UI.cppm
2026-06-18 13:29:49 +00:00
catbot
1d7d9f7b8e perf(ui): LRU-evict the shaped-run cache instead of clearing it (#123)
At kMaxShapedRuns (8192) UIRenderer::ShapeText did a full shapedRuns_.clear().
A stream of unique strings (FPS counters, timers) alongside many stable labels
periodically nuked every stable entry, forcing a full-UI reshape the next frame.

Track recency with an auxiliary std::list<const ShapedRunKey*> (front = most
recently used) holding pointers to the keys owned by the node-based map. A hit
splices its entry to the front; an overflow pops the least-recently-used entry
from the back and erases just that one — both O(1). The hot set of labels,
reshaped every frame, stays at the front and survives, so the churn of unique
strings only recycles the cold tail. Output is byte-identical either way.

InvalidateFont now drops matching entries from both the map and the LRU list.
Adds ShapedRunCacheSize()/IsShapedRunCached() introspection hooks (the policy
isn't observable through output or atlas.dirty) and a ShapeTextCache test that
asserts the cache plateaus at a fixed cap (evict-one, not clear-all), the hot
label survives a churn past the cap, and an untouched cold string is evicted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:23:34 +00:00
catbot
c8495f548b perf(ui): transparent shaped-run cache lookup, no string copy on hit (#122)
ShapeText built ShapedRunKey{..., std::string(utf8)} before find()
unconditionally — even on cache hits, on the per-frame onBuild path.
N labels meant N string copies + hashes (+ frees for non-SSO strings)
every frame, partially defeating the run cache.

Add is_transparent hash + equality functors on shapedRuns_ and probe
with a borrowing ShapedRunViewKey {const Font*, float, array<float,4>,
string_view}. The owning std::string is now materialised only on a
miss, for emplace. Cache hits copy and hash no string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:19:11 +00:00
catbot
9414863cff perf(sync): scope frame-loop barriers to swapchain image + real per-pass stages (#115)
The inter-pass and acquire/present barriers in the frame loop set both
stage masks to ALL_COMMANDS, and the inter-pass dependency used a
queue-wide VkMemoryBarrier — fully serialising against every pipeline
stage and flushing all caches every frame, when all the next pass needs
is the swapchain image the previous one wrote.

Replace the inter-pass global VkMemoryBarrier with an image memory
barrier scoped to the swapchain image's single colour subresource (as
the intra-pass UI barrier already does), and derive the barrier stage
masks per pass: RenderPass::SwapchainStage() is overridden by UIRenderer
(COMPUTE_SHADER) and RTPass (RAY_TRACING_SHADER), so a compute->compute
edge only serialises COMPUTE while an RT pass pulls in RAY_TRACING — the
acquire/present frame-edge masks use the real union of the frame's
passes (SwapchainStageUnion). The base default and the empty-passes
fallback are the conservative COMPUTE | RAY_TRACING | TRANSFER union, so
a polymorphic or un-overridden pass can only be over- not
under-synchronised.

Adds SwapchainBarrierScope (pure CPU) pinning the per-pass derivation,
the union narrowing, and the inter-pass barrier scope; FrameLoopSync
already drives the real GPU frame loop with validation enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:44:33 +00:00
catbot
e8f1c7cff9 Merge remote-tracking branch 'origin/master' into claude/issue-47
# Conflicts:
#	interfaces/Crafter.Graphics-UI.cppm
#	project.cpp
2026-06-16 17:09:06 +00:00
catbot
574242dd30 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>
2026-06-16 17:03:24 +00:00
catbot
65c19ea84a perf(ui): gate per-pixel clip compares behind a flags bit (#50)
uiResolveScreenPixel ran four float compares against hdr.clipRectPx for
every pixel of every UI dispatch, even though the clip rect is the
full-surface default {0,0,1e9,1e9} in the overwhelmingly common case.

Reserve the high bit of the header flags word (UI_FLAG_CLIP) and have
FillHeader set it only when the clip rect is narrower than the surface
(extracted into the unit-testable UIRenderer::ClipFlags). The shader
gates the compares on the bit; the branch is push-constant-uniform so it
never diverges. When the rect covers the surface the skipped compares
could never reject an in-surface pixel, so output is pixel-identical.

Mirrored on the WebGPU/WGSL path (dom-webgpu.js) for parity. Adds the
UIClipFlag CPU test covering the gate decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:57:07 +00:00
catbot
ca48f79465 perf(text): cache shaped runs in ShapeText (#52)
ShapeText re-decoded UTF-8, re-looked-up every glyph, and recomputed
layout every frame for static strings (DrawText / EmitCenteredLabel run
inside per-frame onBuild). DrawText and EmitCenteredLabel also did a
second full pass over the glyphs to apply alignment.

Memoize the shaped run, keyed by (Font*, pxSize, color, utf8), as an
origin-relative vector<GlyphItem>. A cache hit skips decode/lookup/layout
and just translates the cached run into the output buffer by
(x + alignShift, baselineY). The alignment shift (Center = -advance/2,
Right = -advance) is folded into ShapeText's single pass, dropping the
second loop in both callers. TextAlign moves to the :UI partition so
ShapeText can take it.

Pure CPU memoization — the glyph buffer is fully rewritten and re-flushed
every frame, so a hit is byte-equivalent. A miss still calls Ensure so new
glyphs rasterise and dirty the atlas; hits don't (atlas entries are never
evicted). InvalidateFont drops a font's runs before it is destroyed (the
cache, like FontAtlas's glyph cache, keys on the Font pointer). A soft cap
bounds memory against pathological per-frame-unique text.

Adds the ShapeTextCache test (15 checks): hit == miss byte-for-byte, hits
don't touch the atlas, translate/alignment/truncation/InvalidateFont.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:40:52 +00:00
8347467e1e webgpu improvements 2026-05-24 13:32:08 +02:00
64116cd980 custom shader webgpu 2026-05-18 05:39:17 +02:00
dedf6b0467 webgpu support 2026-05-18 04:58:52 +02:00
5352ef69a2 browser DOM support 2026-05-18 02:07:48 +02:00
ac2eb7fb0a new input system 2026-05-12 00:24:48 +02:00
b3db40ebec update 2026-05-05 23:49:29 +02:00
825da78f7f descriptor heap leak fix 2026-05-05 00:02:04 +02:00
1f5697326c UI rewrite 3rd attempt 2026-05-02 21:08:20 +02:00
216972e73a new UI system 2026-05-01 23:35:37 +02:00