feat(vulkan-rt): Mesh::BuildProcedural — AABB (procedural) BLAS build path (#33) #35

Merged
catbot merged 2 commits from claude/issue-33 into master 2026-06-13 02:18:31 +02:00
2 changed files with 88 additions and 26 deletions
Showing only changes of commit a9e42f9c90 - Show all commits

feat(vulkan-rt): Mesh::BuildProcedural — AABB (procedural) BLAS build path (#33)

Adds the native counterpart of the WebGPU Mesh::BuildProcedural: uploads
the boxes as a 24-byte-stride VkAabbPositionsKHR-compatible device buffer
(AS-build-input read-only + device address) and builds the BLAS from one
VK_GEOMETRY_TYPE_AABBS_KHR geometry. opaque maps to
VK_GEOMETRY_OPAQUE_BIT_KHR, default-false semantics matching the WebGPU
path so any-hit shaders run. The BLAS sizing/create/build tail is factored
into a geometry-type-agnostic helper shared with the triangle path; no
TLAS/instance changes needed.

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

View file

@ -39,31 +39,10 @@ namespace {
constexpr VkBufferUsageFlags2 kIndexUsageBase =
kVertexUsageBase | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
void RecordBLASBuild(Mesh& self, std::uint32_t vertexCount, std::uint32_t indexCount, VkCommandBuffer cmd) {
VkDeviceOrHostAddressConstKHR vertexAddr;
vertexAddr.deviceAddress = self.vertexBuffer.address;
VkDeviceOrHostAddressConstKHR indexAddr;
indexAddr.deviceAddress = self.indexBuffer.address;
auto trianglesData = VkAccelerationStructureGeometryTrianglesDataKHR {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR,
.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT,
.vertexData = vertexAddr,
.vertexStride = sizeof(Vector<float, 3, 3>),
.maxVertex = vertexCount - 1,
.indexType = VK_INDEX_TYPE_UINT32,
.indexData = indexAddr,
.transformData = {.deviceAddress = 0}
};
VkAccelerationStructureGeometryDataKHR geometryData(trianglesData);
VkAccelerationStructureGeometryKHR blasGeometry {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR,
.geometry = geometryData,
.flags = VK_GEOMETRY_OPAQUE_BIT_KHR
};
// Shared BLAS sizing / creation / build-record tail — geometry-type
// agnostic; both the triangle and the AABB (procedural) paths feed
// their single geometry through here.
void RecordBLASBuildFromGeometry(Mesh& self, const VkAccelerationStructureGeometryKHR& blasGeometry, std::uint32_t primitiveCount, VkCommandBuffer cmd) {
VkAccelerationStructureBuildGeometryInfoKHR blasBuildGeometryInfo{
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,
@ -72,7 +51,6 @@ namespace {
.pGeometries = &blasGeometry,
};
auto primitiveCount = indexCount / 3;
VkAccelerationStructureBuildSizesInfoKHR blasBuildSizes = {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR
};
@ -122,6 +100,34 @@ namespace {
};
self.blasAddr = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
}
void RecordBLASBuild(Mesh& self, std::uint32_t vertexCount, std::uint32_t indexCount, VkCommandBuffer cmd) {
VkDeviceOrHostAddressConstKHR vertexAddr;
vertexAddr.deviceAddress = self.vertexBuffer.address;
VkDeviceOrHostAddressConstKHR indexAddr;
indexAddr.deviceAddress = self.indexBuffer.address;
auto trianglesData = VkAccelerationStructureGeometryTrianglesDataKHR {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR,
.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT,
.vertexData = vertexAddr,
.vertexStride = sizeof(Vector<float, 3, 3>),
.maxVertex = vertexCount - 1,
.indexType = VK_INDEX_TYPE_UINT32,
.indexData = indexAddr,
.transformData = {.deviceAddress = 0}
};
VkAccelerationStructureGeometryDataKHR geometryData(trianglesData);
VkAccelerationStructureGeometryKHR blasGeometry {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR,
.geometry = geometryData,
.flags = VK_GEOMETRY_OPAQUE_BIT_KHR
};
RecordBLASBuildFromGeometry(self, blasGeometry, indexCount / 3, cmd);
}
}
void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd) {
@ -203,3 +209,32 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd) {
RecordBLASBuild(*this, asset.vertexCount, asset.indexCount, cmd);
}
void Mesh::BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd) {
this->opaque = opaque;
// 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.
aabbBuffer.Resize(kVertexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, static_cast<std::uint32_t>(aabbs.size()));
std::memcpy(aabbBuffer.value, aabbs.data(), aabbs.size() * sizeof(RTAabb));
aabbBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
VkAccelerationStructureGeometryAabbsDataKHR aabbsData {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR,
.data = { .deviceAddress = aabbBuffer.address },
.stride = sizeof(RTAabb)
};
VkAccelerationStructureGeometryDataKHR geometryData;
geometryData.aabbs = aabbsData;
VkAccelerationStructureGeometryKHR blasGeometry {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_AABBS_KHR,
.geometry = geometryData,
// Non-opaque by default (mirrors the WebGPU path) so any-hit
// shaders run for procedural geometry unless the caller opts out.
.flags = opaque ? static_cast<VkGeometryFlagsKHR>(VK_GEOMETRY_OPAQUE_BIT_KHR) : VkGeometryFlagsKHR{}
};
RecordBLASBuildFromGeometry(*this, blasGeometry, static_cast<std::uint32_t>(aabbs.size()), cmd);
}

View file

@ -31,12 +31,27 @@ import Crafter.Asset;
import :VulkanBuffer;
export namespace Crafter {
// One procedural primitive's axis-aligned bounding box, in object
// space. Layout-compatible with VkAabbPositionsKHR (24-byte stride),
// so a span of these uploads directly as a
// VK_GEOMETRY_TYPE_AABBS_KHR build input. Mirrors the DOM-side
// definition below so portable user code compiles unchanged.
struct RTAabb {
float min[3];
float max[3];
};
static_assert(sizeof(RTAabb) == sizeof(VkAabbPositionsKHR));
class Mesh {
public:
VulkanBuffer<char, false> scratchBuffer;
VulkanBuffer<char, false> blasBuffer;
VulkanBuffer<Vector<float, 3, 3>, true> vertexBuffer;
VulkanBuffer<std::uint32_t, true> indexBuffer;
// AABB build input for the procedural path (BuildProcedural).
// Lifetime contract matches vertexBuffer/indexBuffer: must stay
// alive until the build submitted on `cmd` completes.
VulkanBuffer<RTAabb, true> aabbBuffer;
// Lives until the cmd buffer issued by the compressed Build path
// completes execution. Kept as a member so the recorded
// vkCmdDecompressMemoryEXT references valid memory until the queue
@ -57,6 +72,18 @@ export namespace Crafter {
// Region 2 (data) is not consumed here — the caller decompresses it
// into their own buffer if needed (or uses Compression::DecompressCPU).
void Build(const ::Crafter::CompressedMeshAsset& asset, VkCommandBuffer cmd);
// Build an AABB (procedural) BLAS from a list of object-space
// boxes (VK_GEOMETRY_TYPE_AABBS_KHR). The hit group bound to
// instances of this mesh must be a
// VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR group
// carrying an intersection shader; that shader is invoked for
// each box the ray enters and reports the surface hit via
// reportIntersectionEXT. `opaque` maps to
// VK_GEOMETRY_OPAQUE_BIT_KHR: pass false (the WebGPU-path
// default) to let any-hit shaders run. Same scratch/lifetime
// handling as the triangle Build; instances reference blasAddr
// exactly like a triangle BLAS.
void BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd);
};
}
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM