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>
This commit is contained in:
parent
00d14d67cb
commit
f14441942a
5 changed files with 306 additions and 21 deletions
|
|
@ -125,6 +125,23 @@ export namespace Crafter {
|
|||
// exactly like a triangle BLAS.
|
||||
void BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd, RTBuildOptions options = {});
|
||||
|
||||
// Zero-copy procedural build: take the AABB build input straight from
|
||||
// an existing device buffer (by device address + primitive count)
|
||||
// rather than memcpy-ing a host span through aabbBuffer. The intended
|
||||
// producer is a GPU compute pass that writes the boxes itself — e.g.
|
||||
// a GPU-resident particle system whose per-frame AABBs never touch the
|
||||
// CPU. The buffer must carry
|
||||
// VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR
|
||||
// and VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, its boxes laid out as
|
||||
// VkAabbPositionsKHR (`stride`, default sizeof(RTAabb) == 24), and the
|
||||
// caller must pipeline-barrier the producing writes (e.g. a compute
|
||||
// dispatch) before the build on `cmd` reads them. The buffer's
|
||||
// lifetime is the caller's responsibility — it is never copied, so it
|
||||
// must outlive the build submitted on `cmd`. Everything else (opaque
|
||||
// bit, hit-group contract, instance wiring) matches the host-span
|
||||
// BuildProcedural above.
|
||||
void BuildProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, bool opaque, VkCommandBuffer cmd, RTBuildOptions options = {}, std::uint32_t stride = sizeof(RTAabb));
|
||||
|
||||
// Refit the triangle BLAS against new geometry of the *same
|
||||
// topology* (same vertex count and index count as the original
|
||||
// Build). When that Build set RTBuildOptions::allowUpdate this issues
|
||||
|
|
@ -139,6 +156,16 @@ export namespace Crafter {
|
|||
void Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd);
|
||||
// Procedural analog of Refit: new object-space boxes, same count.
|
||||
void RefitProcedural(std::span<const RTAabb> aabbs, VkCommandBuffer cmd);
|
||||
// Zero-copy procedural refit: the device-buffer counterpart of
|
||||
// RefitProcedural above and the input-source pairing for the device
|
||||
// BuildProcedural. The GPU producer rewrote the boxes in `aabbAddress`
|
||||
// in place; this re-records the build straight from that address —
|
||||
// an in-place hardware UPDATE when the original build set allowUpdate
|
||||
// and `count` is unchanged (handle / blasAddr preserved), otherwise a
|
||||
// full rebuild. Same buffer-usage / barrier / lifetime contract as the
|
||||
// device BuildProcedural. Call per frame to track GPU-written boxes
|
||||
// with zero host involvement.
|
||||
void RefitProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, VkCommandBuffer cmd, std::uint32_t stride = sizeof(RTAabb));
|
||||
};
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
@ -243,6 +270,25 @@ export namespace Crafter {
|
|||
WebGPUCommandEncoderRef cmd = 0,
|
||||
RTBuildOptions options = {});
|
||||
|
||||
// Zero-copy procedural build: the boxes already live in an existing
|
||||
// device buffer (`aabbBuffer`, a GPUBuffer a compute pass wrote), fed
|
||||
// straight into the BLAS with no host copy — the WebGPU analog of the
|
||||
// device-address Vulkan BuildProcedural. The software path has no
|
||||
// hardware AS and builds its BVH on the host, but here there is no
|
||||
// host copy to build over, so the device boxes are copied GPU→GPU into
|
||||
// the mesh heap and wrapped in a single root leaf bounded by
|
||||
// `worldBounds` (object-space min/max enclosing all `count` boxes —
|
||||
// the producer supplies it since the CPU never sees the boxes). The
|
||||
// traversal then linearly intersects every box. `opaque` is the
|
||||
// geometry opaque bit (false lets any-hit run). `options` has no effect
|
||||
// (no hardware AS to tune), kept for API symmetry.
|
||||
void BuildProcedural(WebGPUBufferRef aabbBuffer,
|
||||
std::uint32_t count,
|
||||
RTAabb worldBounds,
|
||||
bool opaque = false,
|
||||
WebGPUCommandEncoderRef cmd = 0,
|
||||
RTBuildOptions options = {});
|
||||
|
||||
// Refit analogs of the native API. With no hardware AS to update,
|
||||
// these simply re-run the host BVH build over the new data, so they
|
||||
// accept the full geometry (not just moved positions). Provided so
|
||||
|
|
@ -253,6 +299,16 @@ export namespace Crafter {
|
|||
WebGPUCommandEncoderRef cmd = 0);
|
||||
void RefitProcedural(std::span<const RTAabb> aabbs,
|
||||
WebGPUCommandEncoderRef cmd = 0);
|
||||
// Zero-copy procedural refit: re-copy the boxes from the device buffer
|
||||
// into this mesh's heap region (count unchanged) and update the root
|
||||
// leaf bounds, keeping blasAddr stable — unlike the host-span
|
||||
// RefitProcedural, which rebuilds and re-publishes a NEW handle. The
|
||||
// device counterpart of the device-address Vulkan RefitProcedural;
|
||||
// call per frame to track GPU-written boxes with no host copy.
|
||||
void RefitProcedural(WebGPUBufferRef aabbBuffer,
|
||||
std::uint32_t count,
|
||||
RTAabb worldBounds,
|
||||
WebGPUCommandEncoderRef cmd = 0);
|
||||
};
|
||||
}
|
||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
|
|||
|
|
@ -195,6 +195,36 @@ namespace Crafter::WebGPU {
|
|||
const void* attribsPtr, std::int32_t attribsByteCount,
|
||||
std::int32_t geomType, std::int32_t opaqueFlag, std::int32_t primCount);
|
||||
|
||||
// Zero-copy procedural BLAS registration: the AABB build input already
|
||||
// lives in an existing device buffer (`aabbBufferHandle`, a GPUBuffer a
|
||||
// compute pass wrote) rather than a wasm host pointer. The bridge
|
||||
// copyBufferToBuffer's `count` boxes (24 bytes each, [min,max] vec3 — the
|
||||
// RTAabb / VkAabbPositionsKHR layout) straight into the vertices heap,
|
||||
// never round-tripping them through wasm memory. With no host copy of the
|
||||
// boxes there is nothing to run the SAH builder over, so the BLAS gets a
|
||||
// single root leaf spanning all `count` primitives bounded by the
|
||||
// caller-supplied object-space box (min/max) — the traversal then linearly
|
||||
// intersects every box. `opaqueFlag` is the geometry opaque bit. Returns
|
||||
// the mesh handle (0 on failure), same contract as wgpuRegisterMeshBLAS.
|
||||
__attribute__((import_module("env"), import_name("wgpuRegisterMeshBLASDeviceAabbs")))
|
||||
extern "C" std::uint32_t wgpuRegisterMeshBLASDeviceAabbs(
|
||||
WebGPUBufferRef aabbBufferHandle, std::int32_t count,
|
||||
float minX, float minY, float minZ,
|
||||
float maxX, float maxY, float maxZ,
|
||||
std::int32_t opaqueFlag);
|
||||
|
||||
// Zero-copy procedural BLAS refit: re-copy `count` boxes from the device
|
||||
// buffer into the existing mesh's vertices-heap region (count unchanged)
|
||||
// and update the root leaf bounds, preserving the mesh handle so
|
||||
// RTInstance::accelerationStructureReference stays valid. The device
|
||||
// counterpart of Mesh::RefitProcedural.
|
||||
__attribute__((import_module("env"), import_name("wgpuRefitMeshBLASDeviceAabbs")))
|
||||
extern "C" void wgpuRefitMeshBLASDeviceAabbs(
|
||||
std::uint32_t meshHandle,
|
||||
WebGPUBufferRef aabbBufferHandle, std::int32_t count,
|
||||
float minX, float minY, float minZ,
|
||||
float maxX, float maxY, float maxZ);
|
||||
|
||||
// RT pipeline build. The library composes WGSL by concatenating the
|
||||
// traversal library, generated hit-group switches, and the user-
|
||||
// supplied raygen / miss / closesthit / anyhit bodies. `bindings` is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue