diff --git a/tests/BLASBuildOptions/main.cpp b/tests/BLASBuildOptions/main.cpp index f8e842e..f4fed5f 100644 --- a/tests/BLASBuildOptions/main.cpp +++ b/tests/BLASBuildOptions/main.cpp @@ -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 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 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 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 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 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));