Commit graph

311 commits

Author SHA1 Message Date
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
f07bbc20f1 Merge pull request 'perf(font): cache per-codepoint advances in font units (#57)' (#92) from claude/issue-57 into master 2026-06-16 19:04:09 +02: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
ce1eaf6366 Merge remote-tracking branch 'origin/master' into claude/issue-57
# Conflicts:
#	project.cpp
2026-06-16 17:02:48 +00:00
catbot
c169986835 perf(font): cache per-codepoint advances in font units (#57)
Font::GetLineWidth called stbtt_GetCodepointHMetrics for every glyph on
every invocation. InputField_HitTestCursor rescans the whole field once
per character, making caret hit-testing O(n²) in HMetrics lookups.

Memoise advances per Font via Font::AdvanceUnits: ASCII in a flat array,
the rest in a map. Advances are cached in unscaled font *units* and
rescaled per call — Glyph::advance in the FontAtlas is baked at kBaseSize
and is wrong at any other size, so it cannot be reused. The per-glyph
(int) truncation in GetLineWidth is preserved exactly.

Adds the FontAdvanceCache CPU-only regression test covering size
independence of the cached unit advance, the per-glyph-truncated rescale
pipeline, cache-hit determinism, width scaling with size, and the
non-ASCII map path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:00:48 +00:00
508f119cc6 Merge pull request 'perf(ui): gate per-pixel clip compares behind a flags bit (#50)' (#91) from claude/issue-50 into master 2026-06-16 18:59:48 +02:00
catbot
f2f5a770c0 Merge remote-tracking branch 'origin/master' into claude/issue-50
# Conflicts:
#	project.cpp
2026-06-16 16:59:05 +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
79db987f83 Merge pull request 'perf(input-field): O(n) cursor hit test via Font::NearestCursorByte (#56)' (#90) from claude/issue-56 into master 2026-06-16 18:55:34 +02:00
catbot
d712218f17 perf(input-field): O(n) cursor hit test via Font::NearestCursorByte (#56)
InputField_HitTestCursor mapped a click x to a cursor byte offset by
calling Font::GetLineWidth on every prefix value[0..i] for i=1..size.
Each call re-walks from byte 0, so the hit test was O(n^2) glyph-metric
lookups (each an uncached stbtt cmap binary search). It also iterated
raw byte boundaries, so on multibyte UTF-8 input it could return an
offset splitting a codepoint — an invalid position for the byte-based
cursorPos.

Add Font::NearestCursorByte: one left-to-right cumulative-advance pass
(O(n) metrics, scale computed once) over codepoint boundaries, returning
the byte offset of the boundary nearest the target. Cumulative advance is
monotonic, so the scan stops past the closest boundary. The hit test now
delegates to it.

Adds tests/InputFieldHitTest, a pure-CPU regression test (no Vulkan
device/window): a px sweep across ASCII and multibyte strings compared
against an independent brute-force nearest-boundary oracle, plus the
left/right/empty/end-of-text edge cases and a codepoint-boundary invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:55:03 +00:00
c1bd5fe244 Merge pull request 'perf(ui): scope inter-dispatch barrier to the swapchain image (#48)' (#88) from claude/issue-48 into master 2026-06-16 18:54:33 +02:00
catbot
c1fec9e8af perf(ui): scope inter-dispatch barrier to swapchain image (#48)
UIRenderer::Dispatch inserted a queue-wide VkMemoryBarrier
(SHADER_WRITE -> SHADER_READ|WRITE) before every dispatch after the
first, flushing/invalidating all shader caches even though the only
hazard is the read-modify-write of the single swapchain storage image
that every UI pass shares.

Narrow it to a VkImageMemoryBarrier scoped to that image's subresource
(COMPUTE -> COMPUTE, GENERAL -> GENERAL, no layout transition). Same
correctness — later passes still observe earlier passes' writes — but
only this image's caches are flushed; unrelated resources are no longer
invalidated.

This is a narrower cache flush only: the execution dependency stays
compute->compute queue-wide, so passes still do not overlap. Eliminating
the barriers entirely requires pass fusion (#47) and is intentionally
out of scope here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:53:51 +00:00
c58b68ec40 Merge pull request 'fix(device): preferred mask + required-only fallback for GetMemoryType (#59)' (#87) from claude/issue-59 into master 2026-06-16 18:04:20 +02:00
catbot
294c1378b5 fix(device): add preferred mask + required-only fallback to GetMemoryType (#59)
GetMemoryType was a bare first-superset match that threw whenever no
memory type satisfied the requested property flags. Callers combining a
mandatory flag with a perf-only one (HOST_VISIBLE | DEVICE_LOCAL for the
descriptor heaps) would then fail allocation on any device without a
host-visible device-local heap (no resizable BAR).

It now takes a `preferred` mask distinct from `required`: a type
satisfying both is chosen first, falling back to a required-only match
when the preference is unavailable, and throwing only when even
`required` cannot be met. VulkanBuffer::Create/Resize gain an optional
trailing `preferredPropertyFlags`, and the descriptor heaps now treat
DEVICE_LOCAL as a preference on top of mandatory HOST_VISIBLE.

This does not touch any flush/barrier behaviour — coherency is not
assumed anywhere here.

Adds tests/MemoryTypeFallback, a pure-CPU test that installs synthetic
memory layouts and exercises selection, preference, fallback, and the
unsatisfiable-required throw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:03:50 +00:00
9ffc609a92 Merge pull request 'perf(ui): cooperative shared-memory tile culling in UI compute shaders' (#85) from claude/issue-46 into master 2026-06-16 18:03:16 +02:00
catbot
256ce56f1d Merge remote-tracking branch 'origin/master' into claude/issue-46
# Conflicts:
#	shaders/ui-text.comp.glsl
2026-06-16 16:02:00 +00:00
bbbfacba48 Merge pull request 'fix(image): scope sampled-image upload barriers to the real consumer stage (#54)' (#86) from claude/issue-54 into master 2026-06-16 18:01:50 +02:00
catbot
8cca1dae47 fix(image): scope sampled-image upload barriers to the real consumer stage (#54)
ImageVulkan hardcoded VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR as the
consuming stage in every layout transition. The UI font atlas is sampled by
a *compute* shader, so the upload barrier's dst scope never covered the real
consumer — a correctness gap, not just lost overlap.

Parameterize the consuming stage per image: store it once at Create
(defaulting to RT, preserving existing RT/example callers) and reuse it in
every Update / UpdateRegion / compressed-Update transition. FontAtlas passes
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:01:23 +00:00
catbot
6eb11fbf71 perf(ui): cooperative shared-memory tile culling in UI compute shaders
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>
2026-06-16 15:45:12 +00:00
0b00d9d680 Merge pull request 'perf(text): fold Ensure+Lookup into FontAtlas::EnsureAndGet (#53)' (#84) from claude/issue-53 into master 2026-06-16 17:44:20 +02:00
catbot
77a6501ced perf(text): fold Ensure+Lookup into FontAtlas::EnsureAndGet (#53)
Per glyph, ShapeText did cache_.contains(key) (in Ensure) then
cache_.find(key) (in Lookup) — two independent hashes + node-chases of
the same key. Add EnsureAndGet returning const Glyph* with a single
find, insert-on-miss, returning &it->second. It returns nullptr only on
ShelfPlace failure, preserving the existing `if (g==nullptr) continue;`
behavior. Ensure now delegates to it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:43:58 +00:00
991a39e094 Merge pull request 'perf(font): upload only the dirty sub-rect of the glyph atlas (#51)' (#83) from claude/issue-51 into master 2026-06-16 17:43:23 +02:00
catbot
e4f8b057f3 Merge remote-tracking branch 'origin/master' into claude/issue-51
# Conflicts:
#	project.cpp
2026-06-16 15:43:11 +00:00
1451e3aeb2 Merge pull request 'feat(window): multi-frame-in-flight frame pacing (#40)' (#81) from claude/issue-40 into master 2026-06-16 17:42:13 +02:00
catbot
2d6052ec67 Merge remote-tracking branch 'origin/master' into claude/issue-40
# Conflicts:
#	implementations/Crafter.Graphics-Window.cpp
#	interfaces/Crafter.Graphics-Window.cppm
2026-06-16 15:41:59 +00:00
29eacfb460 Merge pull request 'perf(text): cache shaped runs in ShapeText (#52)' (#82) from claude/issue-52 into master 2026-06-16 17:41:35 +02: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
catbot
97c79c23ee perf(font): upload only the dirty sub-rect of the glyph atlas (#51)
FontAtlas::Update re-uploaded the entire 1024x1024 R8 atlas (1 MiB)
whenever a single glyph was rasterized, so warm-up / language-switch /
size-churn cost roughly one full-atlas copy per frame that added a
codepoint.

Accumulate a dirty bounding box (FontAtlas::DirtyRect) in MarkDirty as
glyphs are placed, and copy only that sub-rect. On Vulkan this is a new
ImageVulkan::UpdateRegion: the staging buffer keeps the full atlas row
stride, so bufferRowLength stays the atlas width and bufferOffset points
at the rect origin while imageOffset/imageExtent carve out the rect.
Layout transitions stay whole-image (cheap); only the copy extent
shrinks. WebGPU passes the rect straight to wgpuWriteAtlasRegion, which
already took x/y/w/h.

The freshly-zeroed atlas marks its whole extent dirty in Initialize so
the first Update clears the image once; thereafter every untouched texel
stays the zero it was uploaded as, keeping the image byte-identical to
the staging copy.

Tested: new FontAtlasDirtyRect unit test covers the accumulation /
clamp / lockstep-with-`dirty` math (no GPU needed); HelloUI renders text
correctly on both Vulkan (NVIDIA, validation-clean) and WebGPU.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:40:37 +00:00
catbot
621016f264 feat(window): multi-frame-in-flight frame pacing (#40)
The renderer was effectively single-buffered despite allocating
triple-buffered infrastructure: Render() ended with an unconditional
vkQueueWaitIdle, and a single (presentComplete, renderComplete)
semaphore pair with zero fences was shared for the Window's lifetime.

Rework the pacing model so up to numFrames frames overlap:
- Per-swapchain-image VkFence, signaled by the submit and waited+reset
  before that image's command buffer / descriptor-heap slot is
  re-recorded. Keyed by acquired image index (drawCmdBuffers, the heap
  slots, and the swapchain images are all image-indexed — see
  WriteSwapchainDescriptors). Created signaled so first use passes.
- Per-image render-finished (present) semaphore: the presentation engine
  holds it until the image is re-acquired, so per-image is the only safe
  key (per-CPU-frame trips VUID-vkQueueSubmit-pSignalSemaphores-00067).
- Per-CPU-frame acquire semaphores (image index unknown until acquire
  returns), sized numFrames+1: in-flight depth is bounded by the
  per-image fences, so numFrames+1 distinct acquire semaphores guarantee
  the reused one has no pending op (VUID-vkAcquireNextImageKHR-01779).
- Drop the steady-state vkQueueWaitIdle; keep it on resize / OUT_OF_DATE
  / teardown.

Add tests/FrameLoopSync: drives the real frame loop against a live
Wayland compositor for 60 frames (>> in-flight slots) and asserts the
CPU frame counter advanced, the swapchain rotated across multiple
images, and the validation layer stayed silent — the load-bearing check,
since the old single-pair design only avoided being an active race
because the wait-idle masked it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:37:11 +00:00
58e9e0a747 Merge pull request 'perf(shaders): drop nonuniformEXT on text font slots' (#80) from claude/issue-49 into master 2026-06-16 17:29:51 +02:00
catbot
a8ed369c71 perf(shaders): drop nonuniformEXT on text font slots
fontTextureSlot/fontSamplerSlot are push constants, hence provably
dynamically uniform. Wrapping them in nonuniformEXT forced the
per-invocation divergent-descriptor path and blocked hoisting the
uniform descriptor load out of the per-pixel glyph loop.

Removing the decoration is zero correctness risk — it is purely a
read-index hint. Not applied to ui-images.comp.glsl, whose slots come
from an SSBO load the compiler cannot prove uniform.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:29:29 +00:00
459f10afa8 Merge pull request 'perf(window): drop dead present-mode queries from swapchain recreate (#45)' (#79) from claude/issue-45 into master 2026-06-16 17:29:00 +02:00
catbot
9ef554b97e perf(window): drop dead present-mode queries from swapchain recreate (#45)
CreateSwapchain unconditionally used VK_PRESENT_MODE_FIFO_KHR, but still
ran two vkGetPhysicalDeviceSurfacePresentModesKHR calls plus a heap-allocated
std::vector to enumerate present modes that were never consulted. Delete
them and assign the constant directly.

compositeAlpha is a surface property that does not change across swapchain
recreation, so select it once at construction instead of re-deriving it
(with another heap-allocated vector) on every resize configure. The
vkQueueWaitIdle before recreation is left intact as required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:28:35 +00:00
cea57f9af8 Merge pull request 'perf(window): cache per-frame heap-bind structs across frames' (#77) from claude/issue-42 into master 2026-06-16 17:27:55 +02:00
fa35d86021 Merge pull request 'perf(window): hoist invariant per-frame Vulkan info structs to members' (#78) from claude/issue-43 into master 2026-06-16 17:27:48 +02:00
catbot
8b95b7e883 perf(window): cache per-frame heap-bind structs across frames
Render() rebuilt two VkBindHeapInfoEXT structs every frame and
recomputed reservedRangeOffset via a runtime subtract + bitmask
against Device::descriptorHeapProperties. The heap address/size per
slot are stable for a given heap; only currentBuffer varies.

Precompute numFrames resource/sampler bind structs and cache them,
keyed on the descriptorHeap pointer. The cache rebuilds whenever the
heap is (re)assigned and otherwise just indexes by currentBuffer.
Pointer-keyed invalidation catches any heap reassignment — the same
point a setter hook would fire — and is independent of onResize, as
the heaps never resize today (per the issue's correctness caveat).

Resolves #42

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:27:10 +00:00
catbot
38616d81ef perf(window): hoist invariant per-frame Vulkan info structs to members (#43)
Window::Render() rebuilt cmdBufInfo, the subresource range, both
VkImageMemoryBarrier structs, and presentInfo on the stack every frame
even though only the barriers' image (and the acquire barrier's
oldLayout) ever change. Hoist them to members initialised once in the
constructor; Render() now patches only the two varying fields.

presentInfo.pImageIndices/pSwapchains point at the currentBuffer and
swapChain members, so they track value changes (including swapchain
recreation) automatically. The render-complete wait semaphore is fixed
for the window's lifetime, so the per-frame `renderComplete != NULL`
check — set once, never cleared — was dead and is removed; the
semaphore is wired into presentInfo in the constructor instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:27:04 +00:00
626cc6f5c8 Merge pull request 'perf(window): ring buffer for CRAFTER_TIMING frame times (#44)' (#76) from claude/issue-44 into master 2026-06-16 17:25:58 +02:00
catbot
41d99890c1 perf(window): ring buffer for CRAFTER_TIMING frame times (#44)
Replace the std::vector<nanoseconds> frameTimes plus
erase(begin()) with a fixed-size std::array ring buffer (head +
count). The previous code memmoved ~99 elements left every frame once
the window filled. LogTiming computes order-independent sum/avg/min/max,
so the ring's write order is irrelevant to the reported stats.

Behind CRAFTER_TIMING only; shipping builds are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:25:22 +00:00
050a44d118 Merge pull request 'feat(rt): BuildProcedural — accept a device AABB buffer as build input (#37)' (#39) from claude/issue-37 into master 2026-06-16 16:13:09 +02:00
catbot
0d1312ac09 example(RTVolume): GPU-written device-buffer procedural AABBs (#37)
A compute shader (aabbs.comp.{glsl,wgsl}) writes the procedural box into a
device buffer that BuildProcedural consumes by device address/handle with no
host copy; the WebGPU path also refits from it each frame so the box breathes.
Verified rendering on both Vulkan (native) and WebGPU (browser).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:12:12 +00:00
catbot
8fefb2caef test(rt): headless device-buffer BuildProcedural + refit (#37)
Extend BLASBuildOptions with a device-local AABB buffer filled via a staging
copy + barrier (standing in for a GPU compute producer) fed into the new
device-address BuildProcedural/RefitProcedural. Asserts the build/refit
succeed, the in-place UPDATE keeps the AS handle + blasAddr, the host
aabbBuffer is never allocated (proof of zero-copy), and the validation layer
reports no errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:12:12 +00:00
catbot
f14441942a feat(rt): BuildProcedural/RefitProcedural from a device AABB buffer (#37)
Add zero-copy procedural BLAS overloads that take the AABB build input
straight from an existing device buffer instead of memcpy-ing a host span
through Mesh::aabbBuffer — the input-source companion to #36's refit work.
A GPU compute producer (e.g. a GPU-resident particle system) can now feed a
moving procedural BLAS that builds/refits each frame with no host round-trip.

Vulkan: new BuildProcedural(VkDeviceAddress, count, ...) and
RefitProcedural(VkDeviceAddress, count, ...) feed the device address directly
into VkAccelerationStructureGeometryAabbsDataKHR; the host-span path is
refactored to share RecordProceduralBuildFromAddress and is otherwise
unchanged.

WebGPU: device-buffer BuildProcedural/RefitProcedural copy the boxes GPU->GPU
into the mesh heap (new wgpuRegisterMeshBLASDeviceAabbs / wgpuRefitMeshBLAS-
DeviceAabbs bridge fns) and wrap them in a single root leaf bounded by a
caller-supplied worldBounds — no wasm round-trip, blasAddr stable across refit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:12:04 +00:00
15ce47f422 removed stale docs, added docs folder 2026-06-16 15:50:56 +02:00
00d14d67cb tlas build options 2026-06-16 15:49:17 +02:00
e3940d99c3 Merge pull request 'feat(vulkan-rt): BLAS build options — fast-build/fast-trace + in-place refit (#36)' (#38) from claude/issue-36 into master 2026-06-16 15:38:35 +02:00
catbot
ce0114607a test(vulkan-rt): headless BLAS build-options + refit test (#36)
Exercises the real hardware AS-build path: builds and refits triangle
and procedural BLASes with fast-build/fast-trace + allow-update flags
via one-time command buffers, asserting the requested flags land, that
an allowUpdate refit keeps the same AS handle/blasAddr (in-place
UPDATE), that the no-update / topology-change fallback still produces a
valid BLAS, and that the validation layer reports zero errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:37:46 +00:00
catbot
1a81f115c3 feat(vulkan-rt): BLAS build options — fast-build/fast-trace + in-place refit (#36)
Mesh::Build / BuildProcedural now take an RTBuildOptions { preference,
allowUpdate }. `preference` maps to PREFER_FAST_TRACE (default) or
PREFER_FAST_BUILD; `allowUpdate` sets ALLOW_UPDATE so the BLAS can be
refit later.

Adds Mesh::Refit / RefitProcedural: when the original build opted into
allowUpdate and the topology is unchanged, they record an in-place
UPDATE-mode build (src == dst) — much cheaper than a rebuild, and the
AS handle + blasAddr are preserved so TLAS instances stay valid. They
fall back to a full rebuild otherwise. The shared build tail also now
destroys a stale AS handle on re-Build (previously leaked) and guards
the in-place update with an AS read→write barrier.

The portable RTBuildPreference/RTBuildOptions types and Refit methods
also exist on the WebGPU backend for API symmetry; the software BVH has
no hardware AS, so the preference is a no-op and a "refit" rebuilds the
BVH (re-registering the handle).

Also adds Device::validationErrorCount, bumped by the debug-messenger
callback on ERROR-severity messages, so tests can assert a Vulkan
operation produced no validation errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:37:46 +00:00
27bfb36e60 Merge pull request 'feat(vulkan-rt): Mesh::BuildProcedural — AABB (procedural) BLAS build path (#33)' (#35) from claude/issue-33 into master 2026-06-13 02:18:30 +02:00
catbot
40c4184d41 test(vulkan-rt): port RTVolume example to native Vulkan (#33)
Regression test for the procedural BLAS path: the same 3x3x3 grid of
unit-box AABBs runs through a PROCEDURAL_HIT_GROUP_KHR group whose GLSL
intersection shader (reportIntersectionEXT) turns each box into a
radius-1 sphere, the any-hit shader punches the spherical-checkerboard
cut-out (visible proof non-opaque geometry runs any-hit), and the
closest-hit shades per-instance tints — the WebGPU example behavior
reproduced natively. Fixed camera in raygen.glsl; the WebGPU/DOM path is
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:17:37 +00:00