test(rt): headless device-buffer BuildProcedural + refit (#37)
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>
This commit is contained in:
parent
f14441942a
commit
8fefb2caef
1 changed files with 80 additions and 0 deletions
|
|
@ -197,6 +197,86 @@ int main() {
|
|||
Check(proc.accelerationStructure == procHandleBefore && proc.blasAddr == procAddrBefore,
|
||||
"RefitProcedural kept the same AS handle + blasAddr (in-place UPDATE)");
|
||||
|
||||
// ── Procedural (AABB) BLAS from a DEVICE buffer (issue #37). The boxes
|
||||
// live in a device-local buffer a GPU pass would write — here filled
|
||||
// via a staging copy + barrier, standing in for a compute dispatch —
|
||||
// and fed straight into BuildProcedural/RefitProcedural by device
|
||||
// address, with no host memcpy through Mesh::aabbBuffer. ────────────
|
||||
constexpr std::uint32_t kDevCount = 3;
|
||||
// Device-local AABB build input: the producer's buffer. Needs the AS
|
||||
// build-input + device-address usage bits, plus TRANSFER_DST so the
|
||||
// staging upload (our stand-in GPU writer) can fill it.
|
||||
VulkanBuffer<RTAabb, false> devAabb;
|
||||
devAabb.Resize(
|
||||
VK_BUFFER_USAGE_2_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR
|
||||
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
||||
| VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, kDevCount);
|
||||
VulkanBuffer<RTAabb, true> devStaging;
|
||||
// SHADER_DEVICE_ADDRESS because VulkanBuffer::Create always queries the
|
||||
// buffer device address (the staging buffer's address itself is unused).
|
||||
devStaging.Resize(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, kDevCount);
|
||||
|
||||
auto uploadBoxes = [&](VkCommandBuffer cmd, std::span<const RTAabb> boxes) {
|
||||
std::memcpy(devStaging.value, boxes.data(), boxes.size() * sizeof(RTAabb));
|
||||
devStaging.FlushDevice();
|
||||
VkBufferCopy region { .srcOffset = 0, .dstOffset = 0, .size = devAabb.size };
|
||||
vkCmdCopyBuffer(cmd, devStaging.buffer, devAabb.buffer, 1, ®ion);
|
||||
// Order the producing write before the AS build reads the buffer —
|
||||
// exactly the barrier a real compute producer would need.
|
||||
VkBufferMemoryBarrier barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = devAabb.buffer, .offset = 0, .size = VK_WHOLE_SIZE,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
0, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
};
|
||||
|
||||
Mesh devProc;
|
||||
{
|
||||
std::array<RTAabb, kDevCount> boxes {{
|
||||
{ .min = {-1,-1,-1}, .max = {1,1,1} },
|
||||
{ .min = { 2, 2, 2}, .max = {3,3,3} },
|
||||
{ .min = {-3,-3,-3}, .max = {-2,-2,-2} },
|
||||
}};
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
uploadBoxes(cmd, boxes);
|
||||
devProc.BuildProcedural(devAabb.address, kDevCount, /*opaque*/ false, cmd,
|
||||
RTBuildOptions{ .preference = RTBuildPreference::FastTrace, .allowUpdate = true });
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(devProc.blasAddr != 0, "device-buffer BuildProcedural produced a non-zero blasAddr");
|
||||
Check(devProc.builtPrimitiveCount == kDevCount, "device-buffer BLAS reports 3 primitives");
|
||||
// The host-side aabbBuffer must be untouched — proof there was no memcpy.
|
||||
Check(devProc.aabbBuffer.buffer == VK_NULL_HANDLE,
|
||||
"device-buffer path never allocated the host aabbBuffer (zero-copy)");
|
||||
|
||||
const VkDeviceAddress devAddrBefore = devProc.blasAddr;
|
||||
const VkAccelerationStructureKHR devHandleBefore = devProc.accelerationStructure;
|
||||
{
|
||||
// Same count, moved boxes (re-written into the same device buffer) →
|
||||
// in-place UPDATE straight from the device address.
|
||||
std::array<RTAabb, kDevCount> boxes {{
|
||||
{ .min = {-2,-2,-2}, .max = {2,2,2} },
|
||||
{ .min = { 4, 4, 4}, .max = {5,5,5} },
|
||||
{ .min = {-5,-5,-5}, .max = {-3,-3,-3} },
|
||||
}};
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
uploadBoxes(cmd, boxes);
|
||||
devProc.RefitProcedural(devAabb.address, kDevCount, cmd);
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(devProc.accelerationStructure == devHandleBefore && devProc.blasAddr == devAddrBefore,
|
||||
"device-buffer RefitProcedural kept the same AS handle + blasAddr (in-place UPDATE)");
|
||||
Check(devProc.aabbBuffer.buffer == VK_NULL_HANDLE,
|
||||
"device-buffer refit still never touched the host aabbBuffer");
|
||||
|
||||
Check(Device::validationErrorCount == 0,
|
||||
std::format("no Vulkan validation errors ({} seen)", Device::validationErrorCount));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue