Merge pull request 'feat(vulkan-rt): BLAS build options — fast-build/fast-trace + in-place refit (#36)' (#38) from claude/issue-36 into master
This commit is contained in:
commit
e3940d99c3
7 changed files with 554 additions and 82 deletions
|
|
@ -167,6 +167,7 @@ VkBool32 onError(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMe
|
||||||
break;
|
break;
|
||||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT :
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT :
|
||||||
printf("(error): ");
|
printf("(error): ");
|
||||||
|
Device::validationErrorCount++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,12 +271,17 @@ namespace {
|
||||||
|
|
||||||
void Mesh::Build(std::span<Vector<float, 3, 3>> vertices,
|
void Mesh::Build(std::span<Vector<float, 3, 3>> vertices,
|
||||||
std::span<std::uint32_t> indices,
|
std::span<std::uint32_t> indices,
|
||||||
WebGPUCommandEncoderRef /*cmd*/) {
|
WebGPUCommandEncoderRef /*cmd*/,
|
||||||
|
RTBuildOptions /*options*/) {
|
||||||
|
// The build preference (FastTrace/FastBuild) and allowUpdate have no
|
||||||
|
// effect on the software-RT path — there is no hardware AS to tune or
|
||||||
|
// refit. The SAH BVH2 is always built the same way.
|
||||||
BuildBVHAndRegister(*this, vertices, indices, {});
|
BuildBVHAndRegister(*this, vertices, indices, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::Build(const CompressedMeshAsset& asset,
|
void Mesh::Build(const CompressedMeshAsset& asset,
|
||||||
WebGPUCommandEncoderRef /*cmd*/) {
|
WebGPUCommandEncoderRef /*cmd*/,
|
||||||
|
RTBuildOptions /*options*/) {
|
||||||
std::vector<Vector<float, 3, 3>> vertices(asset.vertexCount);
|
std::vector<Vector<float, 3, 3>> vertices(asset.vertexCount);
|
||||||
std::vector<std::uint32_t> indices(asset.indexCount);
|
std::vector<std::uint32_t> indices(asset.indexCount);
|
||||||
std::vector<std::byte> dataBytes(
|
std::vector<std::byte> dataBytes(
|
||||||
|
|
@ -298,7 +303,8 @@ void Mesh::Build(const CompressedMeshAsset& asset,
|
||||||
|
|
||||||
void Mesh::BuildProcedural(std::span<const RTAabb> aabbs,
|
void Mesh::BuildProcedural(std::span<const RTAabb> aabbs,
|
||||||
bool opaque_,
|
bool opaque_,
|
||||||
WebGPUCommandEncoderRef /*cmd*/) {
|
WebGPUCommandEncoderRef /*cmd*/,
|
||||||
|
RTBuildOptions /*options*/) {
|
||||||
const std::uint32_t count = static_cast<std::uint32_t>(aabbs.size());
|
const std::uint32_t count = static_cast<std::uint32_t>(aabbs.size());
|
||||||
opaque = opaque_;
|
opaque = opaque_;
|
||||||
triangleCount = 0; // not a triangle mesh
|
triangleCount = 0; // not a triangle mesh
|
||||||
|
|
@ -335,3 +341,21 @@ void Mesh::BuildProcedural(std::span<const RTAabb> aabbs,
|
||||||
/*opaqueFlag*/ opaque ? 1 : 0,
|
/*opaqueFlag*/ opaque ? 1 : 0,
|
||||||
/*primCount*/ static_cast<std::int32_t>(count));
|
/*primCount*/ static_cast<std::int32_t>(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mesh::Refit(std::span<Vector<float, 3, 3>> vertices,
|
||||||
|
std::span<std::uint32_t> indices,
|
||||||
|
WebGPUCommandEncoderRef cmd) {
|
||||||
|
// No hardware AS to update in place — the software path rebuilds the
|
||||||
|
// host BVH and registers it afresh. Unlike the Vulkan UPDATE path, this
|
||||||
|
// assigns a NEW blasAddr (the JS heap append is not in-place), so any
|
||||||
|
// RTInstance::accelerationStructureReference pointing at this mesh must
|
||||||
|
// be re-pointed at the updated mesh.blasAddr afterwards. Refit is far
|
||||||
|
// better suited to the hardware backend; on WebGPU prefer rebuilding
|
||||||
|
// and re-publishing the handle.
|
||||||
|
Build(vertices, indices, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::RefitProcedural(std::span<const RTAabb> aabbs,
|
||||||
|
WebGPUCommandEncoderRef cmd) {
|
||||||
|
BuildProcedural(aabbs, opaque, cmd);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,51 +39,107 @@ namespace {
|
||||||
constexpr VkBufferUsageFlags2 kIndexUsageBase =
|
constexpr VkBufferUsageFlags2 kIndexUsageBase =
|
||||||
kVertexUsageBase | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
kVertexUsageBase | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||||
|
|
||||||
|
// Translate the portable RTBuildOptions to Vulkan build flags. The two
|
||||||
|
// preferences are mutually exclusive; ALLOW_UPDATE is layered on top so
|
||||||
|
// a later in-place refit (UPDATE mode) is permitted.
|
||||||
|
VkBuildAccelerationStructureFlagsKHR BlasFlags(const RTBuildOptions& options) {
|
||||||
|
VkBuildAccelerationStructureFlagsKHR flags =
|
||||||
|
options.preference == RTBuildPreference::FastBuild
|
||||||
|
? VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR
|
||||||
|
: VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
|
||||||
|
if (options.allowUpdate)
|
||||||
|
flags |= VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
// Shared BLAS sizing / creation / build-record tail — geometry-type
|
// Shared BLAS sizing / creation / build-record tail — geometry-type
|
||||||
// agnostic; both the triangle and the AABB (procedural) paths feed
|
// agnostic; both the triangle and the AABB (procedural) paths feed
|
||||||
// their single geometry through here.
|
// their single geometry through here. `update` selects an in-place
|
||||||
void RecordBLASBuildFromGeometry(Mesh& self, const VkAccelerationStructureGeometryKHR& blasGeometry, std::uint32_t primitiveCount, VkCommandBuffer cmd) {
|
// refit (VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR, src == dst)
|
||||||
|
// over a fresh build; the caller guarantees the geometry topology and
|
||||||
|
// primitiveCount match the existing AS and that `flags` carried
|
||||||
|
// ALLOW_UPDATE on the original build.
|
||||||
|
void RecordBLASBuildFromGeometry(Mesh& self, const VkAccelerationStructureGeometryKHR& blasGeometry, std::uint32_t primitiveCount, VkBuildAccelerationStructureFlagsKHR flags, bool update, VkCommandBuffer cmd) {
|
||||||
VkAccelerationStructureBuildGeometryInfoKHR blasBuildGeometryInfo{
|
VkAccelerationStructureBuildGeometryInfoKHR blasBuildGeometryInfo{
|
||||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
|
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
|
||||||
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,
|
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,
|
||||||
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,
|
.flags = flags,
|
||||||
|
.mode = update
|
||||||
|
? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR
|
||||||
|
: VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,
|
||||||
.geometryCount = 1,
|
.geometryCount = 1,
|
||||||
.pGeometries = &blasGeometry,
|
.pGeometries = &blasGeometry,
|
||||||
};
|
};
|
||||||
|
|
||||||
VkAccelerationStructureBuildSizesInfoKHR blasBuildSizes = {
|
if (!update) {
|
||||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR
|
// Fresh build: query sizes, (re)allocate scratch + AS storage,
|
||||||
};
|
// create a new AS handle. The scratch buffer is sized for the
|
||||||
Device::vkGetAccelerationStructureBuildSizesKHR(
|
// build (always >= the update scratch requirement), so it also
|
||||||
Device::device,
|
// covers later in-place refits without resizing.
|
||||||
VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
|
VkAccelerationStructureBuildSizesInfoKHR blasBuildSizes = {
|
||||||
&blasBuildGeometryInfo,
|
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR
|
||||||
&primitiveCount,
|
};
|
||||||
&blasBuildSizes
|
Device::vkGetAccelerationStructureBuildSizesKHR(
|
||||||
);
|
Device::device,
|
||||||
|
VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
|
||||||
|
&blasBuildGeometryInfo,
|
||||||
|
&primitiveCount,
|
||||||
|
&blasBuildSizes
|
||||||
|
);
|
||||||
|
|
||||||
|
self.scratchBuffer.Resize(
|
||||||
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||||
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||||
|
blasBuildSizes.buildScratchSize);
|
||||||
|
|
||||||
|
self.blasBuffer.Resize(
|
||||||
|
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR
|
||||||
|
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
||||||
|
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
|
||||||
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||||
|
blasBuildSizes.accelerationStructureSize);
|
||||||
|
|
||||||
|
// Re-Build of an existing Mesh: destroy the stale handle first
|
||||||
|
// so the previous AS storage isn't leaked.
|
||||||
|
if (self.accelerationStructure != VK_NULL_HANDLE) {
|
||||||
|
Device::vkDestroyAccelerationStructureKHR(Device::device, self.accelerationStructure, nullptr);
|
||||||
|
self.accelerationStructure = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkAccelerationStructureCreateInfoKHR blasCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
|
||||||
|
.buffer = self.blasBuffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = blasBuildSizes.accelerationStructureSize,
|
||||||
|
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,
|
||||||
|
};
|
||||||
|
Device::CheckVkResult(Device::vkCreateAccelerationStructureKHR(Device::device, &blasCreateInfo, nullptr, &self.accelerationStructure));
|
||||||
|
|
||||||
|
VkAccelerationStructureDeviceAddressInfoKHR addrInfo {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR,
|
||||||
|
.accelerationStructure = self.accelerationStructure
|
||||||
|
};
|
||||||
|
self.blasAddr = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
|
||||||
|
} else {
|
||||||
|
// In-place refit: the AS may have been read by a prior TLAS
|
||||||
|
// build / trace, so order that read before the build overwrites
|
||||||
|
// it. Scratch reuse needs the same write-after-read guard.
|
||||||
|
VkMemoryBarrier asBarrier {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
|
.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
||||||
|
.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR
|
||||||
|
};
|
||||||
|
vkCmdPipelineBarrier(cmd,
|
||||||
|
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
|
||||||
|
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||||
|
0, 1, &asBarrier, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
self.scratchBuffer.Resize(
|
|
||||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
||||||
blasBuildSizes.buildScratchSize);
|
|
||||||
blasBuildGeometryInfo.scratchData.deviceAddress = self.scratchBuffer.address;
|
blasBuildGeometryInfo.scratchData.deviceAddress = self.scratchBuffer.address;
|
||||||
|
blasBuildGeometryInfo.dstAccelerationStructure = self.accelerationStructure;
|
||||||
self.blasBuffer.Resize(
|
// UPDATE refits in place (src == dst); a fresh build has no source.
|
||||||
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR
|
blasBuildGeometryInfo.srcAccelerationStructure =
|
||||||
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
update ? self.accelerationStructure : VK_NULL_HANDLE;
|
||||||
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
|
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
||||||
blasBuildSizes.accelerationStructureSize);
|
|
||||||
|
|
||||||
VkAccelerationStructureCreateInfoKHR blasCreateInfo{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
|
|
||||||
.buffer = self.blasBuffer.buffer,
|
|
||||||
.offset = 0,
|
|
||||||
.size = blasBuildSizes.accelerationStructureSize,
|
|
||||||
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,
|
|
||||||
};
|
|
||||||
Device::CheckVkResult(Device::vkCreateAccelerationStructureKHR(Device::device, &blasCreateInfo, nullptr, &self.accelerationStructure));
|
|
||||||
blasBuildGeometryInfo.dstAccelerationStructure = self.accelerationStructure;
|
|
||||||
|
|
||||||
VkAccelerationStructureBuildRangeInfoKHR blasRangeInfo {
|
VkAccelerationStructureBuildRangeInfoKHR blasRangeInfo {
|
||||||
.primitiveCount = primitiveCount,
|
.primitiveCount = primitiveCount,
|
||||||
|
|
@ -94,14 +150,11 @@ namespace {
|
||||||
VkAccelerationStructureBuildRangeInfoKHR* blasRangeInfoPP = &blasRangeInfo;
|
VkAccelerationStructureBuildRangeInfoKHR* blasRangeInfoPP = &blasRangeInfo;
|
||||||
Device::vkCmdBuildAccelerationStructuresKHR(cmd, 1, &blasBuildGeometryInfo, &blasRangeInfoPP);
|
Device::vkCmdBuildAccelerationStructuresKHR(cmd, 1, &blasBuildGeometryInfo, &blasRangeInfoPP);
|
||||||
|
|
||||||
VkAccelerationStructureDeviceAddressInfoKHR addrInfo {
|
self.buildFlags = flags;
|
||||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR,
|
self.builtPrimitiveCount = primitiveCount;
|
||||||
.accelerationStructure = self.accelerationStructure
|
|
||||||
};
|
|
||||||
self.blasAddr = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordBLASBuild(Mesh& self, std::uint32_t vertexCount, std::uint32_t indexCount, VkCommandBuffer cmd) {
|
void RecordBLASBuild(Mesh& self, std::uint32_t vertexCount, std::uint32_t indexCount, VkBuildAccelerationStructureFlagsKHR flags, bool update, VkCommandBuffer cmd) {
|
||||||
VkDeviceOrHostAddressConstKHR vertexAddr;
|
VkDeviceOrHostAddressConstKHR vertexAddr;
|
||||||
vertexAddr.deviceAddress = self.vertexBuffer.address;
|
vertexAddr.deviceAddress = self.vertexBuffer.address;
|
||||||
|
|
||||||
|
|
@ -126,11 +179,11 @@ namespace {
|
||||||
.flags = VK_GEOMETRY_OPAQUE_BIT_KHR
|
.flags = VK_GEOMETRY_OPAQUE_BIT_KHR
|
||||||
};
|
};
|
||||||
|
|
||||||
RecordBLASBuildFromGeometry(self, blasGeometry, indexCount / 3, cmd);
|
RecordBLASBuildFromGeometry(self, blasGeometry, indexCount / 3, flags, update, cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd) {
|
void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd, RTBuildOptions options) {
|
||||||
vertexBuffer.Resize(kVertexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, verticies.size());
|
vertexBuffer.Resize(kVertexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, verticies.size());
|
||||||
indexBuffer.Resize(kIndexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, indicies.size());
|
indexBuffer.Resize(kIndexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, indicies.size());
|
||||||
|
|
||||||
|
|
@ -140,10 +193,12 @@ void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32
|
||||||
vertexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
vertexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||||
indexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
indexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||||
|
|
||||||
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), cmd);
|
allowUpdate = options.allowUpdate;
|
||||||
|
builtInputCount = static_cast<std::uint32_t>(verticies.size());
|
||||||
|
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), BlasFlags(options), /*update*/ false, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd) {
|
void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildOptions options) {
|
||||||
if (!Device::memoryDecompressionSupported) {
|
if (!Device::memoryDecompressionSupported) {
|
||||||
// CPU fallback: decompress into temporary host vectors, then take
|
// CPU fallback: decompress into temporary host vectors, then take
|
||||||
// the existing uncompressed path. The data region is decompressed
|
// the existing uncompressed path. The data region is decompressed
|
||||||
|
|
@ -159,7 +214,7 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd) {
|
||||||
std::span<std::byte>(dataDiscard),
|
std::span<std::byte>(dataDiscard),
|
||||||
};
|
};
|
||||||
Compression::DecompressCPU(asset.blob, std::span(outputs).first(asset.blob.regions.size()));
|
Compression::DecompressCPU(asset.blob, std::span(outputs).first(asset.blob.regions.size()));
|
||||||
Build(vertices, indices, cmd);
|
Build(vertices, indices, cmd, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,35 +261,98 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd) {
|
||||||
VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||||
VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR);
|
VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR);
|
||||||
|
|
||||||
RecordBLASBuild(*this, asset.vertexCount, asset.indexCount, cmd);
|
allowUpdate = options.allowUpdate;
|
||||||
|
builtInputCount = asset.vertexCount;
|
||||||
|
RecordBLASBuild(*this, asset.vertexCount, asset.indexCount, BlasFlags(options), /*update*/ false, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd) {
|
namespace {
|
||||||
this->opaque = opaque;
|
// 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);
|
||||||
|
|
||||||
// 24-byte-stride VkAabbPositionsKHR-compatible build input
|
VkAccelerationStructureGeometryAabbsDataKHR aabbsData {
|
||||||
// (static_assert'd in the interface). Same usage set as the triangle
|
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR,
|
||||||
// inputs: AS-build read-only + device address.
|
.data = { .deviceAddress = self.aabbBuffer.address },
|
||||||
aabbBuffer.Resize(kVertexUsageBase, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, static_cast<std::uint32_t>(aabbs.size()));
|
.stride = sizeof(RTAabb)
|
||||||
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);
|
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 = self.opaque ? static_cast<VkGeometryFlagsKHR>(VK_GEOMETRY_OPAQUE_BIT_KHR) : VkGeometryFlagsKHR{}
|
||||||
|
};
|
||||||
|
|
||||||
VkAccelerationStructureGeometryAabbsDataKHR aabbsData {
|
RecordBLASBuildFromGeometry(self, blasGeometry, static_cast<std::uint32_t>(aabbs.size()), flags, update, cmd);
|
||||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR,
|
}
|
||||||
.data = { .deviceAddress = aabbBuffer.address },
|
}
|
||||||
.stride = sizeof(RTAabb)
|
|
||||||
};
|
void Mesh::BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd, RTBuildOptions options) {
|
||||||
VkAccelerationStructureGeometryDataKHR geometryData;
|
this->opaque = opaque;
|
||||||
geometryData.aabbs = aabbsData;
|
allowUpdate = options.allowUpdate;
|
||||||
VkAccelerationStructureGeometryKHR blasGeometry {
|
builtInputCount = static_cast<std::uint32_t>(aabbs.size());
|
||||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
|
|
||||||
.geometryType = VK_GEOMETRY_TYPE_AABBS_KHR,
|
RecordProceduralBuild(*this, aabbs, BlasFlags(options), /*update*/ false, cmd);
|
||||||
.geometry = geometryData,
|
}
|
||||||
// Non-opaque by default (mirrors the WebGPU path) so any-hit
|
|
||||||
// shaders run for procedural geometry unless the caller opts out.
|
void Mesh::Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd) {
|
||||||
.flags = opaque ? static_cast<VkGeometryFlagsKHR>(VK_GEOMETRY_OPAQUE_BIT_KHR) : VkGeometryFlagsKHR{}
|
// 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
|
||||||
|
// fall back to a full rebuild (mode BUILD) of the same buffers — still
|
||||||
RecordBLASBuildFromGeometry(*this, blasGeometry, static_cast<std::uint32_t>(aabbs.size()), cmd);
|
// correct, just not the cheap path.
|
||||||
|
const bool sameTopology =
|
||||||
|
accelerationStructure != VK_NULL_HANDLE
|
||||||
|
&& verticies.size() == builtInputCount
|
||||||
|
&& indicies.size() == static_cast<std::size_t>(builtPrimitiveCount) * 3;
|
||||||
|
const bool update = allowUpdate && sameTopology;
|
||||||
|
|
||||||
|
if (!update) {
|
||||||
|
// Counts may have changed (or update wasn't permitted): take the
|
||||||
|
// resizing build path, preserving the caller's original flags.
|
||||||
|
Build(verticies, indicies, cmd, RTBuildOptions{
|
||||||
|
.preference = (buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR)
|
||||||
|
? RTBuildPreference::FastBuild : RTBuildPreference::FastTrace,
|
||||||
|
.allowUpdate = allowUpdate,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same-sized buffers: overwrite in place so the device addresses (and
|
||||||
|
// thus the geometry the UPDATE reads) stay stable.
|
||||||
|
std::memcpy(vertexBuffer.value, verticies.data(), verticies.size() * sizeof(Vector<float, 3, 3>));
|
||||||
|
std::memcpy(indexBuffer.value, indicies.data(), indicies.size() * sizeof(std::uint32_t));
|
||||||
|
vertexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||||
|
indexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||||
|
|
||||||
|
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), buildFlags, /*update*/ true, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::RefitProcedural(std::span<const RTAabb> aabbs, VkCommandBuffer cmd) {
|
||||||
|
const bool sameTopology =
|
||||||
|
accelerationStructure != VK_NULL_HANDLE
|
||||||
|
&& aabbs.size() == builtInputCount;
|
||||||
|
const bool update = allowUpdate && sameTopology;
|
||||||
|
|
||||||
|
if (!update && aabbs.size() != builtInputCount) {
|
||||||
|
// Topology changed — a full rebuild needs the new count.
|
||||||
|
builtInputCount = static_cast<std::uint32_t>(aabbs.size());
|
||||||
|
}
|
||||||
|
RecordProceduralBuild(*this, aabbs, buildFlags, update, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,13 @@ export namespace Crafter {
|
||||||
// path and RTPass pushes the active TLAS address as push data. Delete
|
// path and RTPass pushes the active TLAS address as push data. Delete
|
||||||
// this flag and everything keyed on it once a fixed driver ships.
|
// this flag and everything keyed on it once a fixed driver ships.
|
||||||
inline static bool workaroundDescriptorHeapAS = false;
|
inline static bool workaroundDescriptorHeapAS = false;
|
||||||
|
|
||||||
|
// Count of ERROR-severity validation messages seen by the debug
|
||||||
|
// messenger callback since instance creation. The callback only
|
||||||
|
// prints (it never aborts), so tests that want to fail on a
|
||||||
|
// validation error — e.g. a malformed acceleration-structure build —
|
||||||
|
// assert this stays zero across the operation under test.
|
||||||
|
inline static std::uint32_t validationErrorCount = 0;
|
||||||
// The byte offset of the TLAS-address member inside a patched shader's
|
// The byte offset of the TLAS-address member inside a patched shader's
|
||||||
// push-constant block is tracked per-shader (VulkanShader::tlasPushOffset),
|
// push-constant block is tracked per-shader (VulkanShader::tlasPushOffset),
|
||||||
// not here: a single global is clobbered by whichever shader was patched
|
// not here: a single global is clobbered by whichever shader was patched
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,31 @@ export namespace Crafter {
|
||||||
};
|
};
|
||||||
static_assert(sizeof(RTAabb) == sizeof(VkAabbPositionsKHR));
|
static_assert(sizeof(RTAabb) == sizeof(VkAabbPositionsKHR));
|
||||||
|
|
||||||
|
// Build-time tuning for a BLAS, mapped onto the preference bits of
|
||||||
|
// VkBuildAccelerationStructureFlagBitsKHR.
|
||||||
|
enum class RTBuildPreference {
|
||||||
|
// VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR — spend
|
||||||
|
// more time building for faster traversal. The right default for
|
||||||
|
// static geometry that is traced many times.
|
||||||
|
FastTrace,
|
||||||
|
// VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR — build
|
||||||
|
// quickly at the cost of traversal speed. For geometry rebuilt
|
||||||
|
// (or first-frame streamed) often enough that build time dominates.
|
||||||
|
FastBuild,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per-build options for Mesh::Build / BuildProcedural.
|
||||||
|
struct RTBuildOptions {
|
||||||
|
RTBuildPreference preference = RTBuildPreference::FastTrace;
|
||||||
|
// Set VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR so the
|
||||||
|
// BLAS can later be refit in place via Refit()/RefitProcedural().
|
||||||
|
// Required for any subsequent refit; refitting a BLAS built without
|
||||||
|
// it falls back to a full rebuild. PREFER_FAST_TRACE and
|
||||||
|
// PREFER_FAST_BUILD are mutually exclusive, so `preference` selects
|
||||||
|
// exactly one — allowUpdate is layered on top of whichever is set.
|
||||||
|
bool allowUpdate = false;
|
||||||
|
};
|
||||||
|
|
||||||
class Mesh {
|
class Mesh {
|
||||||
public:
|
public:
|
||||||
VulkanBuffer<char, false> scratchBuffer;
|
VulkanBuffer<char, false> scratchBuffer;
|
||||||
|
|
@ -61,17 +86,32 @@ export namespace Crafter {
|
||||||
VulkanBuffer<std::byte, true> compressedStaging;
|
VulkanBuffer<std::byte, true> compressedStaging;
|
||||||
VkAccelerationStructureGeometryTrianglesDataKHR blasData;
|
VkAccelerationStructureGeometryTrianglesDataKHR blasData;
|
||||||
VkAccelerationStructureGeometryKHR blas;
|
VkAccelerationStructureGeometryKHR blas;
|
||||||
VkAccelerationStructureKHR accelerationStructure;
|
VkAccelerationStructureKHR accelerationStructure = VK_NULL_HANDLE;
|
||||||
VkDeviceAddress blasAddr;
|
VkDeviceAddress blasAddr;
|
||||||
bool opaque;
|
bool opaque;
|
||||||
void Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd);
|
// ─── Build-option / refit state ──────────────────────────────────
|
||||||
|
// Flags the BLAS was last (re)built with — carried so Refit can
|
||||||
|
// re-issue an identical-flags UPDATE build (the FAST_TRACE/FAST_BUILD
|
||||||
|
// preference plus, when opted in, ALLOW_UPDATE).
|
||||||
|
VkBuildAccelerationStructureFlagsKHR buildFlags = 0;
|
||||||
|
// Primitive count of the current build (triangles = indexCount/3,
|
||||||
|
// procedural = aabb count). An in-place UPDATE must preserve it.
|
||||||
|
std::uint32_t builtPrimitiveCount = 0;
|
||||||
|
// Element count the vertex / aabb input buffer was sized for. Refit
|
||||||
|
// re-uploads in place only when the new data matches this; a change
|
||||||
|
// forces a full rebuild (topology changed).
|
||||||
|
std::uint32_t builtInputCount = 0;
|
||||||
|
// Whether ALLOW_UPDATE was set on the last build, i.e. whether an
|
||||||
|
// in-place refit is possible at all.
|
||||||
|
bool allowUpdate = false;
|
||||||
|
void Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd, RTBuildOptions options = {});
|
||||||
// GPU path: decompresses vertex (region 0) and index (region 1) streams
|
// GPU path: decompresses vertex (region 0) and index (region 1) streams
|
||||||
// from asset.blob into vertexBuffer / indexBuffer using
|
// from asset.blob into vertexBuffer / indexBuffer using
|
||||||
// VK_EXT_memory_decompression. Falls back to CPU decode + the
|
// VK_EXT_memory_decompression. Falls back to CPU decode + the
|
||||||
// uncompressed Build if Device::memoryDecompressionSupported is false.
|
// uncompressed Build if Device::memoryDecompressionSupported is false.
|
||||||
// Region 2 (data) is not consumed here — the caller decompresses it
|
// Region 2 (data) is not consumed here — the caller decompresses it
|
||||||
// into their own buffer if needed (or uses Compression::DecompressCPU).
|
// into their own buffer if needed (or uses Compression::DecompressCPU).
|
||||||
void Build(const ::Crafter::CompressedMeshAsset& asset, VkCommandBuffer cmd);
|
void Build(const ::Crafter::CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildOptions options = {});
|
||||||
// Build an AABB (procedural) BLAS from a list of object-space
|
// Build an AABB (procedural) BLAS from a list of object-space
|
||||||
// boxes (VK_GEOMETRY_TYPE_AABBS_KHR). The hit group bound to
|
// boxes (VK_GEOMETRY_TYPE_AABBS_KHR). The hit group bound to
|
||||||
// instances of this mesh must be a
|
// instances of this mesh must be a
|
||||||
|
|
@ -83,7 +123,22 @@ export namespace Crafter {
|
||||||
// default) to let any-hit shaders run. Same scratch/lifetime
|
// default) to let any-hit shaders run. Same scratch/lifetime
|
||||||
// handling as the triangle Build; instances reference blasAddr
|
// handling as the triangle Build; instances reference blasAddr
|
||||||
// exactly like a triangle BLAS.
|
// exactly like a triangle BLAS.
|
||||||
void BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd);
|
void BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd, RTBuildOptions options = {});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// a hardware UPDATE-mode build — much cheaper than a rebuild, and the
|
||||||
|
// BLAS handle / blasAddr are preserved, so TLAS instances referencing
|
||||||
|
// it stay valid. A hardware update may only move vertex positions,
|
||||||
|
// not change connectivity; the indices are re-uploaded for symmetry
|
||||||
|
// with the rebuild path but must describe the same topology. Without
|
||||||
|
// allowUpdate (or if the counts changed) it falls back to a full
|
||||||
|
// rebuild. Lifetime contract matches Build: the spans need only
|
||||||
|
// outlive this call. Call this per frame to track a deforming mesh.
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||||
|
|
@ -125,6 +180,20 @@ export namespace Crafter {
|
||||||
};
|
};
|
||||||
static_assert(sizeof(RTAabb) == 24);
|
static_assert(sizeof(RTAabb) == 24);
|
||||||
|
|
||||||
|
// Mirror of the native build-option types so portable code compiles
|
||||||
|
// unchanged. The software-RT path has no hardware acceleration
|
||||||
|
// structure, so the FastTrace/FastBuild preference has no effect (the
|
||||||
|
// SAH BVH2 is always built the same way) and a "refit" simply rebuilds
|
||||||
|
// the BVH on the host. The types exist purely for API symmetry.
|
||||||
|
enum class RTBuildPreference {
|
||||||
|
FastTrace,
|
||||||
|
FastBuild,
|
||||||
|
};
|
||||||
|
struct RTBuildOptions {
|
||||||
|
RTBuildPreference preference = RTBuildPreference::FastTrace;
|
||||||
|
bool allowUpdate = false;
|
||||||
|
};
|
||||||
|
|
||||||
class Mesh {
|
class Mesh {
|
||||||
public:
|
public:
|
||||||
// BLAS "handle": opaque identity that goes into
|
// BLAS "handle": opaque identity that goes into
|
||||||
|
|
@ -147,7 +216,8 @@ export namespace Crafter {
|
||||||
// kept for API symmetry with the Vulkan signature.
|
// kept for API symmetry with the Vulkan signature.
|
||||||
void Build(std::span<Crafter::Vector<float, 3, 3>> vertices,
|
void Build(std::span<Crafter::Vector<float, 3, 3>> vertices,
|
||||||
std::span<std::uint32_t> indices,
|
std::span<std::uint32_t> indices,
|
||||||
WebGPUCommandEncoderRef cmd = 0);
|
WebGPUCommandEncoderRef cmd = 0,
|
||||||
|
RTBuildOptions options = {});
|
||||||
|
|
||||||
// CPU-decompress the .cmesh blob (no VK_EXT_memory_decompression
|
// CPU-decompress the .cmesh blob (no VK_EXT_memory_decompression
|
||||||
// equivalent in WebGPU) and forward to the positions+indices path,
|
// equivalent in WebGPU) and forward to the positions+indices path,
|
||||||
|
|
@ -156,7 +226,8 @@ export namespace Crafter {
|
||||||
// The data layout is example-defined — the heap is exposed in WGSL
|
// The data layout is example-defined — the heap is exposed in WGSL
|
||||||
// as `vertexAttribs : array<u32>` with a per-mesh u32-word offset.
|
// as `vertexAttribs : array<u32>` with a per-mesh u32-word offset.
|
||||||
void Build(const ::Crafter::CompressedMeshAsset& asset,
|
void Build(const ::Crafter::CompressedMeshAsset& asset,
|
||||||
WebGPUCommandEncoderRef cmd = 0);
|
WebGPUCommandEncoderRef cmd = 0,
|
||||||
|
RTBuildOptions options = {});
|
||||||
|
|
||||||
// Build an AABB (procedural) BLAS from a list of object-space boxes
|
// Build an AABB (procedural) BLAS from a list of object-space boxes
|
||||||
// — the WebGPU analog of a VK_GEOMETRY_TYPE_AABBS_KHR geometry. The
|
// — the WebGPU analog of a VK_GEOMETRY_TYPE_AABBS_KHR geometry. The
|
||||||
|
|
@ -168,8 +239,20 @@ export namespace Crafter {
|
||||||
// transparent / volumetric). The `cmd` parameter is unused on
|
// transparent / volumetric). The `cmd` parameter is unused on
|
||||||
// WebGPU — kept for API symmetry with the triangle path.
|
// WebGPU — kept for API symmetry with the triangle path.
|
||||||
void BuildProcedural(std::span<const RTAabb> aabbs,
|
void BuildProcedural(std::span<const RTAabb> aabbs,
|
||||||
bool opaque = false,
|
bool opaque = false,
|
||||||
WebGPUCommandEncoderRef cmd = 0);
|
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
|
||||||
|
// portable code that calls Refit/RefitProcedural compiles and
|
||||||
|
// behaves correctly on the WebGPU backend.
|
||||||
|
void Refit(std::span<Crafter::Vector<float, 3, 3>> vertices,
|
||||||
|
std::span<std::uint32_t> indices,
|
||||||
|
WebGPUCommandEncoderRef cmd = 0);
|
||||||
|
void RefitProcedural(std::span<const RTAabb> aabbs,
|
||||||
|
WebGPUCommandEncoderRef cmd = 0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||||
|
|
|
||||||
30
project.cpp
30
project.cpp
|
|
@ -265,6 +265,36 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
||||||
sc.GetInterfacesAndImplementations(ifaces, scrollImpls);
|
sc.GetInterfacesAndImplementations(ifaces, scrollImpls);
|
||||||
cfg.tests.push_back(std::move(scrollTest));
|
cfg.tests.push_back(std::move(scrollTest));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue #36: BLAS build options. Drives the real hardware AS-build
|
||||||
|
// path — records Mesh::Build / Refit / BuildProcedural /
|
||||||
|
// RefitProcedural with fast-build/fast-trace + allow-update flags
|
||||||
|
// into one-time command buffers and submits them, asserting the
|
||||||
|
// requested flags land, that an allowUpdate refit keeps the AS
|
||||||
|
// handle (in-place UPDATE), and that the validation layer reports no
|
||||||
|
// errors. Needs a Vulkan RT device at runtime (same as the RT
|
||||||
|
// examples), so it shares the native build settings.
|
||||||
|
Test blasTest;
|
||||||
|
Configuration& bc = blasTest.config;
|
||||||
|
bc.path = cfg.path;
|
||||||
|
bc.name = "BLASBuildOptions";
|
||||||
|
bc.outputName = "BLASBuildOptions";
|
||||||
|
bc.type = ConfigurationType::Executable;
|
||||||
|
bc.target = cfg.target;
|
||||||
|
bc.march = cfg.march;
|
||||||
|
bc.mtune = cfg.mtune;
|
||||||
|
bc.debug = cfg.debug;
|
||||||
|
bc.sysroot = cfg.sysroot;
|
||||||
|
bc.dependencies = cfg.dependencies;
|
||||||
|
bc.externalDependencies = cfg.externalDependencies;
|
||||||
|
bc.compileFlags = cfg.compileFlags;
|
||||||
|
bc.linkFlags = cfg.linkFlags;
|
||||||
|
bc.defines = cfg.defines;
|
||||||
|
bc.cFiles = cfg.cFiles;
|
||||||
|
std::vector<fs::path> blasImpls(impls.begin(), impls.end());
|
||||||
|
blasImpls.emplace_back("tests/BLASBuildOptions/main");
|
||||||
|
bc.GetInterfacesAndImplementations(ifaces, blasImpls);
|
||||||
|
cfg.tests.push_back(std::move(blasTest));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg;
|
return cfg;
|
||||||
|
|
|
||||||
209
tests/BLASBuildOptions/main.cpp
Normal file
209
tests/BLASBuildOptions/main.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
Crafter®.Graphics
|
||||||
|
Copyright (C) 2026 Catcrafts®
|
||||||
|
catcrafts.net
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License version 3.0 as published by the Free Software Foundation;
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Issue #36: BLAS build options — fast-build / fast-trace preference and
|
||||||
|
// in-place refit (UPDATE-mode rebuild). This exercises the real hardware
|
||||||
|
// path: it spins up a headless Vulkan device (no swapchain / window needed
|
||||||
|
// — a BLAS build only touches the queue + command pool), records BLAS
|
||||||
|
// builds and refits into one-time command buffers, and submits them.
|
||||||
|
//
|
||||||
|
// What is asserted:
|
||||||
|
// - Build() with RTBuildOptions records the requested preference + the
|
||||||
|
// ALLOW_UPDATE bit, and produces a non-zero device address.
|
||||||
|
// - Refit() on an allowUpdate BLAS keeps the SAME acceleration-structure
|
||||||
|
// handle and blasAddr (proof it took the in-place UPDATE path, so TLAS
|
||||||
|
// instances referencing it stay valid).
|
||||||
|
// - Refit() without allowUpdate, or after a topology change, falls back to
|
||||||
|
// a fresh build (new handle / address are fine; it must still succeed).
|
||||||
|
// - The procedural (AABB) path supports the same options + refit.
|
||||||
|
// - The Vulkan validation layer reports ZERO errors across all of the
|
||||||
|
// above (Device::validationErrorCount) — the strongest check that the
|
||||||
|
// UPDATE builds are spec-correct (scratch sizing, ALLOW_UPDATE present,
|
||||||
|
// src==dst, matching topology, …).
|
||||||
|
//
|
||||||
|
// Validation layers are required for the last check to be meaningful; the
|
||||||
|
// build marks this test as needing the SDK layers.
|
||||||
|
|
||||||
|
#include "vulkan/vulkan.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
import Crafter.Graphics;
|
||||||
|
import Crafter.Math;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using namespace Crafter;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int failures = 0;
|
||||||
|
|
||||||
|
void Check(bool ok, std::string_view what) {
|
||||||
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||||
|
if (!ok) ++failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One-time command buffer helpers — record a BLAS build, submit, block.
|
||||||
|
VkCommandBuffer BeginCmd() {
|
||||||
|
VkCommandBufferAllocateInfo allocInfo {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||||
|
.commandPool = Device::commandPool,
|
||||||
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||||
|
.commandBufferCount = 1,
|
||||||
|
};
|
||||||
|
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||||
|
Device::CheckVkResult(vkAllocateCommandBuffers(Device::device, &allocInfo, &cmd));
|
||||||
|
VkCommandBufferBeginInfo beginInfo {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||||
|
};
|
||||||
|
Device::CheckVkResult(vkBeginCommandBuffer(cmd, &beginInfo));
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubmitWait(VkCommandBuffer cmd) {
|
||||||
|
Device::CheckVkResult(vkEndCommandBuffer(cmd));
|
||||||
|
VkSubmitInfo submitInfo {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
|
.commandBufferCount = 1,
|
||||||
|
.pCommandBuffers = &cmd,
|
||||||
|
};
|
||||||
|
Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||||
|
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||||||
|
vkFreeCommandBuffers(Device::device, Device::commandPool, 1, &cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A unit cube (8 verts, 12 triangles) — enough topology for a real BLAS.
|
||||||
|
std::vector<Vector<float, 3, 3>> CubeVerts(float s) {
|
||||||
|
return {
|
||||||
|
{-s,-s,-s}, { s,-s,-s}, { s, s,-s}, {-s, s,-s},
|
||||||
|
{-s,-s, s}, { s,-s, s}, { s, s, s}, {-s, s, s},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
std::vector<std::uint32_t> CubeIndices() {
|
||||||
|
return {
|
||||||
|
0,1,2, 0,2,3, 4,6,5, 4,7,6,
|
||||||
|
0,4,5, 0,5,1, 3,2,6, 3,6,7,
|
||||||
|
1,5,6, 1,6,2, 0,3,7, 0,7,4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Device::Initialize();
|
||||||
|
Device::validationErrorCount = 0;
|
||||||
|
|
||||||
|
// ── Triangle BLAS: fast-trace + allow-update, then in-place refit. ──
|
||||||
|
Mesh tri;
|
||||||
|
{
|
||||||
|
auto verts = CubeVerts(1.0f);
|
||||||
|
auto idx = CubeIndices();
|
||||||
|
VkCommandBuffer cmd = BeginCmd();
|
||||||
|
tri.Build(verts, idx, cmd, RTBuildOptions{
|
||||||
|
.preference = RTBuildPreference::FastTrace,
|
||||||
|
.allowUpdate = true,
|
||||||
|
});
|
||||||
|
SubmitWait(cmd);
|
||||||
|
}
|
||||||
|
Check(tri.blasAddr != 0, "triangle Build produced a non-zero blasAddr");
|
||||||
|
Check(tri.accelerationStructure != VK_NULL_HANDLE, "triangle Build created an AS handle");
|
||||||
|
Check((tri.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR) != 0,
|
||||||
|
"FastTrace preference → PREFER_FAST_TRACE bit set");
|
||||||
|
Check((tri.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR) != 0,
|
||||||
|
"allowUpdate=true → ALLOW_UPDATE bit set");
|
||||||
|
Check(tri.builtPrimitiveCount == 12, "triangle BLAS reports 12 primitives");
|
||||||
|
|
||||||
|
const VkDeviceAddress triAddrBefore = tri.blasAddr;
|
||||||
|
const VkAccelerationStructureKHR triHandleBefore = tri.accelerationStructure;
|
||||||
|
{
|
||||||
|
// Same topology, deformed positions → must take the UPDATE path.
|
||||||
|
auto verts = CubeVerts(1.5f);
|
||||||
|
auto idx = CubeIndices();
|
||||||
|
VkCommandBuffer cmd = BeginCmd();
|
||||||
|
tri.Refit(verts, idx, cmd);
|
||||||
|
SubmitWait(cmd);
|
||||||
|
}
|
||||||
|
Check(tri.accelerationStructure == triHandleBefore,
|
||||||
|
"Refit kept the same AS handle (in-place UPDATE)");
|
||||||
|
Check(tri.blasAddr == triAddrBefore,
|
||||||
|
"Refit kept the same blasAddr (instances stay valid)");
|
||||||
|
|
||||||
|
// ── Triangle BLAS: fast-build, no update → flags reflect the choice. ─
|
||||||
|
Mesh triFast;
|
||||||
|
{
|
||||||
|
auto verts = CubeVerts(1.0f);
|
||||||
|
auto idx = CubeIndices();
|
||||||
|
VkCommandBuffer cmd = BeginCmd();
|
||||||
|
triFast.Build(verts, idx, cmd, RTBuildOptions{ .preference = RTBuildPreference::FastBuild });
|
||||||
|
SubmitWait(cmd);
|
||||||
|
}
|
||||||
|
Check((triFast.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR) != 0,
|
||||||
|
"FastBuild preference → PREFER_FAST_BUILD bit set");
|
||||||
|
Check((triFast.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR) == 0,
|
||||||
|
"allowUpdate=false → ALLOW_UPDATE bit clear");
|
||||||
|
|
||||||
|
// Refit without allowUpdate must still succeed via the rebuild fallback.
|
||||||
|
{
|
||||||
|
auto verts = CubeVerts(0.5f);
|
||||||
|
auto idx = CubeIndices();
|
||||||
|
VkCommandBuffer cmd = BeginCmd();
|
||||||
|
triFast.Refit(verts, idx, cmd);
|
||||||
|
SubmitWait(cmd);
|
||||||
|
}
|
||||||
|
Check(triFast.blasAddr != 0, "Refit fallback rebuild still produced a valid BLAS");
|
||||||
|
|
||||||
|
// ── Procedural (AABB) BLAS: build with options, then refit. ─────────
|
||||||
|
Mesh proc;
|
||||||
|
{
|
||||||
|
std::array<RTAabb, 2> boxes {{
|
||||||
|
{ .min = {-1,-1,-1}, .max = {1,1,1} },
|
||||||
|
{ .min = { 2, 2, 2}, .max = {3,3,3} },
|
||||||
|
}};
|
||||||
|
VkCommandBuffer cmd = BeginCmd();
|
||||||
|
proc.BuildProcedural(boxes, /*opaque*/ false, cmd, RTBuildOptions{
|
||||||
|
.preference = RTBuildPreference::FastTrace, .allowUpdate = true });
|
||||||
|
SubmitWait(cmd);
|
||||||
|
}
|
||||||
|
Check(proc.blasAddr != 0, "procedural Build produced a non-zero blasAddr");
|
||||||
|
Check(proc.builtPrimitiveCount == 2, "procedural BLAS reports 2 primitives");
|
||||||
|
|
||||||
|
const VkDeviceAddress procAddrBefore = proc.blasAddr;
|
||||||
|
const VkAccelerationStructureKHR procHandleBefore = proc.accelerationStructure;
|
||||||
|
{
|
||||||
|
std::array<RTAabb, 2> boxes {{
|
||||||
|
{ .min = {-2,-2,-2}, .max = {2,2,2} },
|
||||||
|
{ .min = { 4, 4, 4}, .max = {5,5,5} },
|
||||||
|
}};
|
||||||
|
VkCommandBuffer cmd = BeginCmd();
|
||||||
|
proc.RefitProcedural(boxes, cmd);
|
||||||
|
SubmitWait(cmd);
|
||||||
|
}
|
||||||
|
Check(proc.accelerationStructure == procHandleBefore && proc.blasAddr == procAddrBefore,
|
||||||
|
"RefitProcedural kept the same AS handle + blasAddr (in-place UPDATE)");
|
||||||
|
|
||||||
|
Check(Device::validationErrorCount == 0,
|
||||||
|
std::format("no Vulkan validation errors ({} seen)", Device::validationErrorCount));
|
||||||
|
|
||||||
|
if (failures != 0) {
|
||||||
|
std::println("{} check(s) failed", failures);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
std::println("all checks passed");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue