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
|
|
@ -267,26 +267,18 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildO
|
|||
}
|
||||
|
||||
namespace {
|
||||
// Re-upload AABB build input and record an AABB BLAS build (fresh or
|
||||
// in-place refit). Shared by BuildProcedural and RefitProcedural; the
|
||||
// geometry's opaque bit is read from self.opaque so an UPDATE keeps the
|
||||
// exact geometry description of the original build.
|
||||
void RecordProceduralBuild(Mesh& self, std::span<const RTAabb> aabbs, VkBuildAccelerationStructureFlagsKHR flags, bool update, VkCommandBuffer cmd) {
|
||||
// 24-byte-stride VkAabbPositionsKHR-compatible build input
|
||||
// (static_assert'd in the interface). Same usage set as the triangle
|
||||
// inputs: AS-build read-only + device address. A refit reuses the
|
||||
// existing same-sized buffer (count is unchanged), so the device
|
||||
// address — and the geometry it feeds — stays stable.
|
||||
if (!update) {
|
||||
self.aabbBuffer.Resize(kVertexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, static_cast<std::uint32_t>(aabbs.size()));
|
||||
}
|
||||
std::memcpy(self.aabbBuffer.value, aabbs.data(), aabbs.size() * sizeof(RTAabb));
|
||||
self.aabbBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||
|
||||
// Record an AABB BLAS build (fresh or in-place refit) from a device
|
||||
// address that already holds the VkAabbPositionsKHR-compatible boxes.
|
||||
// Geometry-source agnostic: the host-upload path (RecordProceduralBuild)
|
||||
// points this at self.aabbBuffer, while the device-buffer overloads
|
||||
// point it straight at the producer's buffer — no copy involved either
|
||||
// way. The geometry's opaque bit is read from self.opaque so an UPDATE
|
||||
// keeps the exact geometry description of the original build.
|
||||
void RecordProceduralBuildFromAddress(Mesh& self, VkDeviceAddress aabbAddress, std::uint32_t count, std::uint32_t stride, VkBuildAccelerationStructureFlagsKHR flags, bool update, VkCommandBuffer cmd) {
|
||||
VkAccelerationStructureGeometryAabbsDataKHR aabbsData {
|
||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR,
|
||||
.data = { .deviceAddress = self.aabbBuffer.address },
|
||||
.stride = sizeof(RTAabb)
|
||||
.data = { .deviceAddress = aabbAddress },
|
||||
.stride = stride
|
||||
};
|
||||
VkAccelerationStructureGeometryDataKHR geometryData;
|
||||
geometryData.aabbs = aabbsData;
|
||||
|
|
@ -299,7 +291,25 @@ namespace {
|
|||
.flags = self.opaque ? static_cast<VkGeometryFlagsKHR>(VK_GEOMETRY_OPAQUE_BIT_KHR) : VkGeometryFlagsKHR{}
|
||||
};
|
||||
|
||||
RecordBLASBuildFromGeometry(self, blasGeometry, static_cast<std::uint32_t>(aabbs.size()), flags, update, cmd);
|
||||
RecordBLASBuildFromGeometry(self, blasGeometry, count, flags, update, cmd);
|
||||
}
|
||||
|
||||
// Re-upload AABB build input from host memory and record an AABB BLAS
|
||||
// build (fresh or in-place refit). Shared by the host-span BuildProcedural
|
||||
// and RefitProcedural.
|
||||
void RecordProceduralBuild(Mesh& self, std::span<const RTAabb> aabbs, VkBuildAccelerationStructureFlagsKHR flags, bool update, VkCommandBuffer cmd) {
|
||||
// 24-byte-stride VkAabbPositionsKHR-compatible build input
|
||||
// (static_assert'd in the interface). Same usage set as the triangle
|
||||
// inputs: AS-build read-only + device address. A refit reuses the
|
||||
// existing same-sized buffer (count is unchanged), so the device
|
||||
// address — and the geometry it feeds — stays stable.
|
||||
if (!update) {
|
||||
self.aabbBuffer.Resize(kVertexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, static_cast<std::uint32_t>(aabbs.size()));
|
||||
}
|
||||
std::memcpy(self.aabbBuffer.value, aabbs.data(), aabbs.size() * sizeof(RTAabb));
|
||||
self.aabbBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||
|
||||
RecordProceduralBuildFromAddress(self, self.aabbBuffer.address, static_cast<std::uint32_t>(aabbs.size()), sizeof(RTAabb), flags, update, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,6 +321,20 @@ void Mesh::BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommand
|
|||
RecordProceduralBuild(*this, aabbs, BlasFlags(options), /*update*/ false, cmd);
|
||||
}
|
||||
|
||||
void Mesh::BuildProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, bool opaque, VkCommandBuffer cmd, RTBuildOptions options, std::uint32_t stride) {
|
||||
// Zero-copy procedural build: the AABBs already live in `aabbAddress`
|
||||
// (a device buffer a GPU compute pass wrote). Nothing touches
|
||||
// self.aabbBuffer — the build input is the producer's buffer directly.
|
||||
// The caller owns that buffer's lifetime + usage flags
|
||||
// (ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY + SHADER_DEVICE_ADDRESS)
|
||||
// and must barrier the producing writes before this build reads them.
|
||||
this->opaque = opaque;
|
||||
allowUpdate = options.allowUpdate;
|
||||
builtInputCount = count;
|
||||
|
||||
RecordProceduralBuildFromAddress(*this, aabbAddress, count, stride, BlasFlags(options), /*update*/ false, cmd);
|
||||
}
|
||||
|
||||
void Mesh::Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd) {
|
||||
// A hardware in-place UPDATE is only valid when the original build asked
|
||||
// for it, an AS already exists, and the topology is unchanged. Otherwise
|
||||
|
|
@ -356,3 +380,20 @@ void Mesh::RefitProcedural(std::span<const RTAabb> aabbs, VkCommandBuffer cmd) {
|
|||
RecordProceduralBuild(*this, aabbs, buildFlags, update, cmd);
|
||||
}
|
||||
|
||||
void Mesh::RefitProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, VkCommandBuffer cmd, std::uint32_t stride) {
|
||||
// Zero-copy refit: the GPU producer rewrote the same device buffer in
|
||||
// place; re-record the build (UPDATE when allowed + count unchanged,
|
||||
// else a full rebuild) reading straight from `aabbAddress`. As with the
|
||||
// device-buffer BuildProcedural, nothing touches self.aabbBuffer and the
|
||||
// caller is responsible for barriering the producing writes.
|
||||
const bool sameTopology =
|
||||
accelerationStructure != VK_NULL_HANDLE
|
||||
&& count == builtInputCount;
|
||||
const bool update = allowUpdate && sameTopology;
|
||||
|
||||
if (!update && count != builtInputCount) {
|
||||
builtInputCount = count;
|
||||
}
|
||||
RecordProceduralBuildFromAddress(*this, aabbAddress, count, stride, buildFlags, update, cmd);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue