feat(rt): BuildProcedural — accept a device AABB buffer as build input (#37) #39

Merged
catbot merged 3 commits from claude/issue-37 into master 2026-06-16 16:13:10 +02:00
Member

Summary

Mesh::BuildProcedural (and the refit path from #36) can now take the AABB build input straight from an existing device buffer instead of memcpy-ing a host std::span through Mesh::aabbBuffer. This is the input-source companion to #36's per-frame-cost work: together a GPU compute producer (e.g. the 3DForts GPU-resident particle system) can feed a moving procedural BLAS that builds/refits every frame with zero host involvement.

Resolves #37.

API

Vulkan (interfaces/Crafter.Graphics-Mesh.cppm):

  • BuildProcedural(VkDeviceAddress aabbAddress, uint32 count, bool opaque, VkCommandBuffer cmd, RTBuildOptions = {}, uint32 stride = sizeof(RTAabb))
  • RefitProcedural(VkDeviceAddress aabbAddress, uint32 count, VkCommandBuffer cmd, uint32 stride = sizeof(RTAabb))

These feed the device address directly into VkAccelerationStructureGeometryAabbsDataKHR — no copy, aabbBuffer never allocated. The caller owns the buffer's usage flags (ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY + SHADER_DEVICE_ADDRESS) and barriers the producing writes. The host-span path is refactored to share RecordProceduralBuildFromAddress and is otherwise unchanged.

WebGPU (software-RT): device-buffer BuildProcedural/RefitProcedural take a WebGPUBufferRef + count + worldBounds. The boxes are copied GPU→GPU into the mesh heap (new wgpuRegisterMeshBLASDeviceAabbs / wgpuRefitMeshBLASDeviceAabbs bridge fns) and wrapped in a single root leaf bounded by worldBounds — no wasm round-trip, and blasAddr stays stable across a refit so TLAS instances remain valid.

Testing

  • Headless BLASBuildOptions test (gates crafter-build test): a device-local AABB buffer filled via a staging copy + barrier (standing in for a GPU compute producer) is fed into the new device-address build/refit. 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 Vulkan validation layer reports zero errors. 3 passed.
  • examples/RTVolume extended on both backends: 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 refits from it each frame (breathing box). Verified rendering on Vulkan (native) and WebGPU (browser).

Screenshots

Vulkan (native)

RTVolume Vulkan

WebGPU (browser)

RTVolume WebGPU

## Summary `Mesh::BuildProcedural` (and the refit path from #36) can now take the AABB build input straight from an **existing device buffer** instead of `memcpy`-ing a host `std::span` through `Mesh::aabbBuffer`. This is the input-source companion to #36's per-frame-cost work: together a GPU compute producer (e.g. the 3DForts GPU-resident particle system) can feed a moving procedural BLAS that builds/refits every frame with **zero host involvement**. Resolves #37. ## API **Vulkan** (`interfaces/Crafter.Graphics-Mesh.cppm`): - `BuildProcedural(VkDeviceAddress aabbAddress, uint32 count, bool opaque, VkCommandBuffer cmd, RTBuildOptions = {}, uint32 stride = sizeof(RTAabb))` - `RefitProcedural(VkDeviceAddress aabbAddress, uint32 count, VkCommandBuffer cmd, uint32 stride = sizeof(RTAabb))` These feed the device address directly into `VkAccelerationStructureGeometryAabbsDataKHR` — no copy, `aabbBuffer` never allocated. The caller owns the buffer's usage flags (`ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY` + `SHADER_DEVICE_ADDRESS`) and barriers the producing writes. The host-span path is refactored to share `RecordProceduralBuildFromAddress` and is otherwise unchanged. **WebGPU** (software-RT): device-buffer `BuildProcedural`/`RefitProcedural` take a `WebGPUBufferRef` + count + `worldBounds`. The boxes are copied GPU→GPU into the mesh heap (new `wgpuRegisterMeshBLASDeviceAabbs` / `wgpuRefitMeshBLASDeviceAabbs` bridge fns) and wrapped in a single root leaf bounded by `worldBounds` — no wasm round-trip, and `blasAddr` stays stable across a refit so TLAS instances remain valid. ## Testing - **Headless `BLASBuildOptions` test** (gates `crafter-build test`): a device-local AABB buffer filled via a staging copy + barrier (standing in for a GPU compute producer) is fed into the new device-address build/refit. 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 Vulkan validation layer reports **zero errors**. `3 passed`. - **`examples/RTVolume`** extended on both backends: 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 refits from it each frame (breathing box). Verified rendering on Vulkan (native) and WebGPU (browser). ## Screenshots ### Vulkan (native) ![RTVolume Vulkan](https://forgejo.catcrafts.net/attachments/5171a96a-d0a1-4aa3-b711-f94160376f71) ### WebGPU (browser) ![RTVolume WebGPU](https://forgejo.catcrafts.net/attachments/e63c271e-aa0b-48af-8fe4-c70bfbf03719)
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>
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>
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>
catbot merged commit 050a44d118 into master 2026-06-16 16:13:10 +02:00
catbot deleted branch claude/issue-37 2026-06-16 16:13:10 +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!39
No description provided.