From 10637464fce0f77840f263270a887dfd0e45468e Mon Sep 17 00:00:00 2001 From: mklefrancois Date: Wed, 8 Sep 2021 15:59:03 +0200 Subject: [PATCH] Updating the creation of the SBT --- ray_tracing__simple/hello_vulkan.cpp | 86 +++++---- ray_tracing__simple/hello_vulkan.h | 7 +- ray_tracing_anyhit/hello_vulkan.cpp | 92 ++++++---- ray_tracing_anyhit/hello_vulkan.h | 10 +- ray_tracing_indirect_scissor/hello_vulkan.cpp | 92 ++++++---- ray_tracing_indirect_scissor/hello_vulkan.h | 7 +- ray_tracing_intersection/hello_vulkan.cpp | 88 +++++---- ray_tracing_intersection/hello_vulkan.h | 7 +- ray_tracing_jitter_cam/hello_vulkan.cpp | 85 +++++---- ray_tracing_jitter_cam/hello_vulkan.h | 10 +- ray_tracing_manyhits/README.md | 149 +++------------ ray_tracing_manyhits/hello_vulkan.cpp | 172 ++++++++---------- ray_tracing_manyhits/hello_vulkan.h | 19 +- ray_tracing_manyhits/images/sbt_0.png | Bin 0 -> 14639 bytes ray_tracing_manyhits/images/sbt_1.png | Bin 0 -> 15553 bytes ray_tracing_reflections/hello_vulkan.cpp | 90 +++++---- ray_tracing_reflections/hello_vulkan.h | 7 +- 17 files changed, 477 insertions(+), 444 deletions(-) create mode 100644 ray_tracing_manyhits/images/sbt_0.png create mode 100644 ray_tracing_manyhits/images/sbt_1.png diff --git a/ray_tracing__simple/hello_vulkan.cpp b/ray_tracing__simple/hello_vulkan.cpp index 4b0877f..d8e6aec 100644 --- a/ray_tracing__simple/hello_vulkan.cpp +++ b/ray_tracing__simple/hello_vulkan.cpp @@ -840,39 +840,70 @@ void HelloVulkan::createRtPipeline() // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this -// See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); // 4 shaders: raygen, 2 miss, chit - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; + uint32_t missCount{2}; + uint32_t hitCount{1}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; - // Fetch all the shader handles used in the pipeline. This is opaque data,/ so we store it in a vector of bytes. - // The order of handles follow the stage entry. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = handleSizeAligned; + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - // Allocate a buffer for storing the SBT. Give it a debug name for NSight. - m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; // Map the SBT buffer and write in the handles. - void* mapped = m_alloc.map(m_rtSBTBuffer); - auto* pData = reinterpret_cast(mapped); - for(uint32_t g = 0; g < groupCount; g++) + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); - pData += groupSizeAligned; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + for(uint32_t c = 0; c < hitCount; c++) + { + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_hitRegion.stride; + } + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -898,22 +929,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - // Size of a program identifier - uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - uint32_t groupStride = groupSize; - - VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; - VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); - - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 1}, // hit - Stride{0u, 0u, 0u}}; // callable - - - vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], - m_size.width, m_size.height, 1); + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); m_debug.endLabel(cmdBuf); diff --git a/ray_tracing__simple/hello_vulkan.h b/ray_tracing__simple/hello_vulkan.h index 65b1332..9916a59 100644 --- a/ray_tracing__simple/hello_vulkan.h +++ b/ray_tracing__simple/hello_vulkan.h @@ -144,7 +144,12 @@ public: std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - nvvk::Buffer m_rtSBTBuffer; + + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; // Push constant for ray tracer PushConstantRay m_pcRay{}; diff --git a/ray_tracing_anyhit/hello_vulkan.cpp b/ray_tracing_anyhit/hello_vulkan.cpp index 99672d5..d39c97b 100644 --- a/ray_tracing_anyhit/hello_vulkan.cpp +++ b/ray_tracing_anyhit/hello_vulkan.cpp @@ -39,7 +39,6 @@ extern std::vector defaultSearchPaths; - //-------------------------------------------------------------------------------------------------- // Keep the handle on the device // Initialize the tool to do all our allocations: buffers, images @@ -685,8 +684,7 @@ void HelloVulkan::createTopLevelAS() // void HelloVulkan::createRtDescriptorSet() { - // Top-level acceleration structure, usable by both the ray generation and the closest hit (to - // shoot shadow rays) + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays) m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, @@ -857,39 +855,70 @@ void HelloVulkan::createRtPipeline() // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this -// See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); // shaders: raygen, 2 miss, chit, 2 ahit - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; + uint32_t missCount{2}; + uint32_t hitCount{2}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; - // Fetch all the shader handles used in the pipeline. This is opaque data, - // so we store it in a vector of bytes. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = handleSizeAligned; + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - - // Allocate a buffer for storing the SBT. Give it a debug name for NSight. - m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; // Map the SBT buffer and write in the handles. - void* mapped = m_alloc.map(m_rtSBTBuffer); - auto* pData = reinterpret_cast(mapped); - for(uint32_t g = 0; g < groupCount; g++) + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); - pData += groupSizeAligned; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + for(uint32_t c = 0; c < hitCount; c++) + { + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_hitRegion.stride; + } + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -910,7 +939,6 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c m_pcRay.lightIntensity = m_pcRaster.lightIntensity; m_pcRay.lightType = m_pcRaster.lightType; - std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, @@ -920,21 +948,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - // Size of a program identifier - uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - uint32_t groupStride = groupSize; - - VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); - - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 2}, // hit - Stride{0u, 0u, 0u}}; // callable - - - vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], - m_size.width, m_size.height, 1); + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); m_debug.endLabel(cmdBuf); diff --git a/ray_tracing_anyhit/hello_vulkan.h b/ray_tracing_anyhit/hello_vulkan.h index e3a567b..34f67d7 100644 --- a/ray_tracing_anyhit/hello_vulkan.h +++ b/ray_tracing_anyhit/hello_vulkan.h @@ -145,8 +145,14 @@ public: std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - nvvk::Buffer m_rtSBTBuffer; - int m_maxFrames{10000}; + + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; + + int m_maxFrames{10000}; // Push constant for ray tracer PushConstantRay m_pcRay{}; diff --git a/ray_tracing_indirect_scissor/hello_vulkan.cpp b/ray_tracing_indirect_scissor/hello_vulkan.cpp index e91be10..58b3fa7 100644 --- a/ray_tracing_indirect_scissor/hello_vulkan.cpp +++ b/ray_tracing_indirect_scissor/hello_vulkan.cpp @@ -1116,41 +1116,71 @@ void HelloVulkan::createRtPipeline() // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this -// See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); - assert(groupCount == 8 && "Update Comment"); // 8 shaders: raygen, 3 miss, 4 chit + uint32_t missCount{3}; + uint32_t hitCount{4}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; - - // Fetch all the shader handles used in the pipeline. This is opaque data, - // so we store it in a vector of bytes. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = handleSizeAligned; + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - // Allocate a buffer for storing the SBT. Give it a debug name for NSight. - m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; // Map the SBT buffer and write in the handles. - void* mapped = m_alloc.map(m_rtSBTBuffer); - auto* pData = reinterpret_cast(mapped); - for(uint32_t g = 0; g < groupCount; g++) + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); - pData += groupSizeAligned; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + for(uint32_t c = 0; c < hitCount; c++) + { + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_hitRegion.stride; + } + + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -1323,22 +1353,8 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - // Size of a program identifier - uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - uint32_t groupStride = groupSize; + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); - VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); - - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 3}, // miss - Stride{sbtAddress + 4u * groupSize, groupStride, groupSize * 4}, // hit - Stride{0u, 0u, 0u}}; // callable - - - // First pass, illuminate scene with global light. - vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], - m_size.width, m_size.height, 1); // Lantern passes, ensure previous pass completed, then add light contribution from each lantern. for(int i = 0; i < static_cast(m_lanternCount); ++i) @@ -1372,9 +1388,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c nvvk::getBufferDeviceAddress(m_device, m_lanternIndirectBuffer.buffer) + i * sizeof(LanternIndirectEntry); // Execute lantern pass. - vkCmdTraceRaysIndirectKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], // - &strideAddresses[2], &strideAddresses[3], // - indirectDeviceAddress); + vkCmdTraceRaysIndirectKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, indirectDeviceAddress); } m_debug.endLabel(cmdBuf); diff --git a/ray_tracing_indirect_scissor/hello_vulkan.h b/ray_tracing_indirect_scissor/hello_vulkan.h index 34355e7..190b5b2 100644 --- a/ray_tracing_indirect_scissor/hello_vulkan.h +++ b/ray_tracing_indirect_scissor/hello_vulkan.h @@ -206,7 +206,12 @@ public: VkDescriptorSet m_lanternIndirectDescSet; VkPipelineLayout m_lanternIndirectCompPipelineLayout; VkPipeline m_lanternIndirectCompPipeline; - nvvk::Buffer m_rtSBTBuffer; + + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; // Buffer to source vkCmdTraceRaysIndirectKHR indirect parameters and lantern color, // position, etc. from when doing lantern lighting passes. diff --git a/ray_tracing_intersection/hello_vulkan.cpp b/ray_tracing_intersection/hello_vulkan.cpp index 9ab57f3..f95039e 100644 --- a/ray_tracing_intersection/hello_vulkan.cpp +++ b/ray_tracing_intersection/hello_vulkan.cpp @@ -423,7 +423,7 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - uint32_t nbInst = static_cast(m_instances.size() - 1); // Remove the implicit object + auto nbInst = static_cast(m_instances.size() - 1); // Remove the implicit object for(uint32_t i = 0; i < nbInst; ++i) { auto& inst = m_instances[i]; @@ -998,39 +998,70 @@ void HelloVulkan::createRtPipeline() // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this -// See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); // 4 shaders: raygen, 2 miss, chit - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; + uint32_t missCount{2}; + uint32_t hitCount{2}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; - // Fetch all the shader handles used in the pipeline. This is opaque data, - // so we store it in a vector of bytes. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = handleSizeAligned; + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - // Allocate a buffer for storing the SBT. Give it a debug name for NSight. - m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; // Map the SBT buffer and write in the handles. - void* mapped = m_alloc.map(m_rtSBTBuffer); - auto* pData = reinterpret_cast(mapped); - for(uint32_t g = 0; g < groupCount; g++) + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); - pData += groupSizeAligned; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + for(uint32_t c = 0; c < hitCount; c++) + { + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_hitRegion.stride; + } + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -1047,7 +1078,6 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c m_pcRay.lightIntensity = m_pcRaster.lightIntensity; m_pcRay.lightType = m_pcRaster.lightType; - std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, @@ -1057,21 +1087,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - // Size of a program identifier - uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - uint32_t groupStride = groupSize; - - VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); - - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 2}, // hit - Stride{0u, 0u, 0u}}; // callable - - - vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], - m_size.width, m_size.height, 1); + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); m_debug.endLabel(cmdBuf); diff --git a/ray_tracing_intersection/hello_vulkan.h b/ray_tracing_intersection/hello_vulkan.h index 971eb73..0182790 100644 --- a/ray_tracing_intersection/hello_vulkan.h +++ b/ray_tracing_intersection/hello_vulkan.h @@ -144,7 +144,12 @@ public: std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - nvvk::Buffer m_rtSBTBuffer; + + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; // Push constant for ray tracer PushConstantRay m_pcRay{}; diff --git a/ray_tracing_jitter_cam/hello_vulkan.cpp b/ray_tracing_jitter_cam/hello_vulkan.cpp index 14ad2af..a8c6e2d 100644 --- a/ray_tracing_jitter_cam/hello_vulkan.cpp +++ b/ray_tracing_jitter_cam/hello_vulkan.cpp @@ -843,39 +843,70 @@ void HelloVulkan::createRtPipeline() // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this -// See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); // 4 shaders: raygen, 2 miss, chit - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; + uint32_t missCount{2}; + uint32_t hitCount{1}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; - // Fetch all the shader handles used in the pipeline. This is opaque data,/ so we store it in a vector of bytes. - // The order of handles follow the stage entry. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = handleSizeAligned; + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - // Allocate a buffer for storing the SBT. Give it a debug name for NSight. - m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; // Map the SBT buffer and write in the handles. - void* mapped = m_alloc.map(m_rtSBTBuffer); - auto* pData = reinterpret_cast(mapped); - for(uint32_t g = 0; g < groupCount; g++) + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); - pData += groupSizeAligned; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + for(uint32_t c = 0; c < hitCount; c++) + { + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_hitRegion.stride; + } + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -906,21 +937,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - // Size of a program identifier - uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - uint32_t groupStride = groupSize; - - VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); - - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 1}, // hit - Stride{0u, 0u, 0u}}; // callable - - - vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], - m_size.width, m_size.height, 1); + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); m_debug.endLabel(cmdBuf); diff --git a/ray_tracing_jitter_cam/hello_vulkan.h b/ray_tracing_jitter_cam/hello_vulkan.h index e6c9ed6..a39a59c 100644 --- a/ray_tracing_jitter_cam/hello_vulkan.h +++ b/ray_tracing_jitter_cam/hello_vulkan.h @@ -145,8 +145,14 @@ public: std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - nvvk::Buffer m_rtSBTBuffer; - int m_maxFrames{10}; + + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; + + int m_maxFrames{10}; // Push constant for ray tracer PushConstantRay m_pcRay{}; diff --git a/ray_tracing_manyhits/README.md b/ray_tracing_manyhits/README.md index dd12205..66e0c2b 100644 --- a/ray_tracing_manyhits/README.md +++ b/ray_tracing_manyhits/README.md @@ -133,16 +133,10 @@ This information can be used to pass extra information to a shader, for each ent **:warning: Note:** Since each entry in an SBT group must have the same size, each entry of the group has to have enough space to accommodate the largest element in the entire group. -The following diagram represents our current SBT, with the addition of some data to `HitGroup1`. As mentioned in the **note**, even if -`HitGroup0` doesn't have any shader record data, it still needs to have the same size as `HitGroup1`, the largest of the hit group. +The following diagram represents our current SBT, with some data added to `HitGroup1`. As mentioned in the **note**, even though `HitGroup0` has no shader record data, it still needs to be the same size as `HitGroup1`, the largest of the hit group and aligned to the Handle alignment. + +![](images/sbt_0.png) -|Group|Handle| -| ------ | ------ | -| RayGen | Handle 0 | -| Miss0 | Handle 1 | -| Miss1 | Handle 2 | -| HitGroup0 | Handle 3
-Empty- | -| HitGroup1 | Handle 4
Data 0 | ## `hello_vulkan.h` @@ -200,16 +194,9 @@ data `m_hitShaderRecord` to the Hit group. // Find handle indices and add data m_sbtWrapper.addIndices(rayPipelineInfo); m_sbtWrapper.addData(SBTWrapper::eHit, 1, m_hitShaderRecord[0]); - m_sbtWrapper.addData(SBTWrapper::eHit, 2, m_hitShaderRecord[1]); m_sbtWrapper.create(m_rtPipeline); ```` -The buffer for Hit will have the following layout - -``` -| handle | handle,data | handle,data | -``` - The wrapper will make sure the stride covers the largest data and is aligned based on the GPU properties. @@ -217,96 +204,27 @@ based on the GPU properties. ~~Since we are no longer compacting all handles in a continuous buffer, we need to fill the SBT as described above.~~ -~~After retrieving the handles of all 5 groups (raygen, miss, miss shadow, hit0, and hit1) -using `getRayTracingShaderGroupHandlesKHR`, store the pointers to easily retrieve them.~~ - ~~~~ C++ - // Retrieve the handle pointers - std::vector handles(groupCount); - for(uint32_t i = 0; i < groupCount; i++) - { - handles[i] = &shaderHandleStorage[i * groupHandleSize]; - } -~~~~ - -~~The size of each group can be described as follows:~~ - -~~~~ C++ - // Sizes - uint32_t rayGenSize = baseAlignment; - uint32_t missSize = baseAlignment; - uint32_t hitSize = - ROUND_UP(groupHandleSize + static_cast(sizeof(HitRecordBuffer)), baseAlignment); - uint32_t newSbtSize = rayGenSize + 2 * missSize + 2 * hitSize; + m_hitRegion.stride = nvh::align_up(handleSize + sizeof(HitRecordBuffer), m_rtProperties.shaderGroupHandleAlignment); ~~~~ ~~Then write the new SBT like this, where only Hit 1 has extra data.~~ ~~~~ C++ - std::vector sbtBuffer(newSbtSize); - { - uint8_t* pBuffer = sbtBuffer.data(); + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + memcpy(pData, getHandle(handleIdx++), handleSize); - memcpy(pBuffer, handles[0], groupHandleSize); // Raygen - pBuffer += rayGenSize; - memcpy(pBuffer, handles[1], groupHandleSize); // Miss 0 - pBuffer += missSize; - memcpy(pBuffer, handles[2], groupHandleSize); // Miss 1 - pBuffer += missSize; - - uint8_t* pHitBuffer = pBuffer; - memcpy(pHitBuffer, handles[3], groupHandleSize); // Hit 0 - // No data - pBuffer += hitSize; - - pHitBuffer = pBuffer; - memcpy(pHitBuffer, handles[4], groupHandleSize); // Hit 1 - pHitBuffer += groupHandleSize; - memcpy(pHitBuffer, &m_hitShaderRecord[0], sizeof(HitRecordBuffer)); // Hit 1 data - pBuffer += hitSize; - } + // hit 1 + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size + m_hitRegion.stride; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += handleSize; + memcpy(pData, &m_hitShaderRecord[0], sizeof(HitRecordBuffer)); // Hit 1 data ~~~~ -~~Then change the call to `m_alloc.createBuffer` to create the SBT buffer from `sbtBuffer`:~~ +## Ray Trace -~~~~ C++ - m_rtSBTBuffer = m_alloc.createBuffer(cmdBuf, sbtBuffer, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR); -~~~~ - -### `raytrace` - -**:star:NEW:star:** - -The mvvk::SBTWrapper gives use the information without having to compute the `VkStridedDeviceAddressRegionKHR` - -``` C - auto& regions = m_sbtWrapper.getRegions(); - vkCmdTraceRaysKHR(cmdBuf, ®ions[0], ®ions[1], ®ions[2], ®ions[3], m_size.width, m_size.height, 1); -``` - -**OLD** - -~~Finally, since the size of the hit group is now larger than just the handle, we need to set the new value of the hit group stride in `HelloVulkan::raytrace`.~~ - -~~~~ C++ - VkDeviceSize hitGroupSize = - nvh::align_up(m_rtProperties.shaderGroupHandleSize + sizeof(HitRecordBuffer), - m_rtProperties.shaderGroupBaseAlignment); -~~~~ - -~~The stride device address will be modified like this:~~ - -~~~~ C++ - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{ - Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - Stride{sbtAddress + 3u * groupSize, hitGroupSize, hitGroupSize * 3}, // hit - Stride{0u, 0u, 0u}}; // callable -~~~~ - -~~**Note:** - The result should now show both `wuson` models with a yellow color.~~ +The result should now show both `wuson` models with a yellow color.~~ ![](images/manyhits4.png) @@ -316,14 +234,8 @@ The SBT can be larger than the number of shading models, which could then be use The following modification will add another entry to the SBT with a different color per instance. The new SBT hit group (2) will use the same CHIT handle (4) as hit group 1. -|Group|Handle| -| ------ | ------ | -| RayGen | Handle 0 | -| Miss0 | Handle 1 | -| Miss1 | Handle 2 | -| HitGroup0 | Handle 3
-Empty- | -| HitGroup1 | Handle 4
Data 0 | -| HitGroup1 | Handle 4
Data 1 | +![](images/sbt_1.png) + ### `main.cpp` @@ -342,25 +254,24 @@ In the description of the scene in `main`, we will tell the `wuson` models to us **:star:NEW:star:** -If you are using the SBT wrapper, this part is automatically handled. +If you are using the SBT wrapper, make sure to add the data to the third Hit (2). + +~~~~ C + // Find handle indices and add data + m_sbtWrapper.addIndices(rayPipelineInfo); + m_sbtWrapper.addData(nvvk::SBTWrapper::eHit, 1, m_hitShaderRecord[0]); + m_sbtWrapper.addData(nvvk::SBTWrapper::eHit, 2, m_hitShaderRecord[1]); + m_sbtWrapper.create(m_rtPipeline); +~~~~ **OLD** - -~~The size of the SBT will now account for its 3 hit groups:~~ - ~~~~ C++ - uint32_t newSbtSize = rayGenSize + 2 * missSize + 3 * hitSize; -~~~~ - -~~Finally, we need to add the new entry as well at the end of the buffer, reusing the handle of the second Hit Group and setting a different color.~~ - -~~~~ C++ - pHitBuffer = pBuffer; - memcpy(pHitBuffer, handles[4], groupHandleSize); // Hit 2 - pHitBuffer += groupHandleSize; - memcpy(pHitBuffer, &m_hitShaderRecord[1], sizeof(HitRecordBuffer)); // Hit 2 data - pBuffer += hitSize; + // hit 2 + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size + (2 * m_hitRegion.stride); + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += handleSize; + memcpy(pData, &m_hitShaderRecord[1], sizeof(HitRecordBuffer)); // Hit 2 data ~~~~ ~~**Note:** diff --git a/ray_tracing_manyhits/hello_vulkan.cpp b/ray_tracing_manyhits/hello_vulkan.cpp index 075917f..3861e22 100644 --- a/ray_tracing_manyhits/hello_vulkan.cpp +++ b/ray_tracing_manyhits/hello_vulkan.cpp @@ -384,13 +384,16 @@ void HelloVulkan::destroyResources() // #VKRay +#ifdef USE_SBT_WRAPPER m_sbtWrapper.destroy(); +#else + m_alloc.destroy(m_rtSBTBuffer); +#endif m_rtBuilder.destroy(); vkDestroyPipeline(m_device, m_rtPipeline, nullptr); vkDestroyPipelineLayout(m_device, m_rtPipelineLayout, nullptr); vkDestroyDescriptorPool(m_device, m_rtDescPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_rtDescSetLayout, nullptr); - m_alloc.destroy(m_rtSBTBuffer); m_alloc.deinit(); } @@ -595,7 +598,9 @@ void HelloVulkan::initRayTracing() m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex); +#ifdef USE_SBT_WRAPPER m_sbtWrapper.setup(m_device, m_graphicsQueueIndex, &m_alloc, m_rtProperties); +#endif } //-------------------------------------------------------------------------------------------------- @@ -848,11 +853,15 @@ void HelloVulkan::createRtPipeline() vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); +#ifdef USE_SBT_WRAPPER // Find handle indices and add data m_sbtWrapper.addIndices(rayPipelineInfo); - m_sbtWrapper.addData(SBTWrapper::eHit, 1, m_hitShaderRecord[0]); - m_sbtWrapper.addData(SBTWrapper::eHit, 2, m_hitShaderRecord[1]); + m_sbtWrapper.addData(nvvk::SBTWrapper::eHit, 1, m_hitShaderRecord[0]); + m_sbtWrapper.addData(nvvk::SBTWrapper::eHit, 2, m_hitShaderRecord[1]); m_sbtWrapper.create(m_rtPipeline); +#else + createRtShaderBindingTable(); +#endif for(auto& s : stages) vkDestroyShaderModule(m_device, s.module, nullptr); @@ -864,78 +873,84 @@ void HelloVulkan::createRtPipeline() // - Besides exception, this could be always done like this // See how the SBT buffer is used in run() // +#ifndef USE_SBT_WRAPPER void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); // 4 shaders: raygen, 2 miss, chit - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; - // Fetch all the shader handles used in the pipeline. This is opaque data,/ so we store it in a vector of bytes. - // The order of handles follow the stage entry. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + uint32_t missCount{2}; + uint32_t hitCount{3}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - // Retrieve the handle pointers - std::vector handles(groupCount); - for(uint32_t i = 0; i < groupCount; i++) + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = nvh::align_up(handleSize + sizeof(HitRecordBuffer), m_rtProperties.shaderGroupHandleAlignment); + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; + + // Map the SBT buffer and write in the handles. + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - handles[i] = &shaderHandleStorage[i * groupHandleSize]; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + memcpy(pData, getHandle(handleIdx++), handleSize); - // Sizes - uint32_t rayGenSize = groupSizeAligned; - uint32_t missSize = groupSizeAligned; - uint32_t hitSize = nvh::align_up(groupHandleSize + static_cast(sizeof(HitRecordBuffer)), groupSizeAligned); - uint32_t newSbtSize = rayGenSize + 2 * missSize + 3 * hitSize; + // hit 1 + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size + m_hitRegion.stride; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += handleSize; + memcpy(pData, &m_hitShaderRecord[0], sizeof(HitRecordBuffer)); // Hit 1 data - std::vector sbtBuffer(newSbtSize); - { - uint8_t* pBuffer = sbtBuffer.data(); - - memcpy(pBuffer, handles[0], groupHandleSize); // Raygen - pBuffer += rayGenSize; - memcpy(pBuffer, handles[1], groupHandleSize); // Miss 0 - pBuffer += missSize; - memcpy(pBuffer, handles[2], groupHandleSize); // Miss 1 - pBuffer += missSize; - - uint8_t* pHitBuffer = pBuffer; - memcpy(pHitBuffer, handles[3], groupHandleSize); // Hit 0 - // No data - pBuffer += hitSize; - - pHitBuffer = pBuffer; - memcpy(pHitBuffer, handles[4], groupHandleSize); // Hit 1 - pHitBuffer += groupHandleSize; - memcpy(pHitBuffer, &m_hitShaderRecord[0], sizeof(HitRecordBuffer)); // Hit 1 data - pBuffer += hitSize; - - pHitBuffer = pBuffer; - memcpy(pHitBuffer, handles[4], groupHandleSize); // Hit 2 - pHitBuffer += groupHandleSize; - memcpy(pHitBuffer, &m_hitShaderRecord[1], sizeof(HitRecordBuffer)); // Hit 2 data - // pBuffer += hitSize; - } - - // Write the handles in the SBT - nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); - VkCommandBuffer cmdBuf = genCmdBuf.createCommandBuffer(); - - m_rtSBTBuffer = m_alloc.createBuffer(cmdBuf, sbtBuffer, - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR); - - m_debug.setObjectName(m_rtSBTBuffer.buffer, "SBT"); + // hit 2 + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size + (2 * m_hitRegion.stride); + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += handleSize; + memcpy(pData, &m_hitShaderRecord[1], sizeof(HitRecordBuffer)); // Hit 2 data - genCmdBuf.submitAndWait(cmdBuf); - + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } +#endif //-------------------------------------------------------------------------------------------------- // Ray Tracing the scene @@ -959,39 +974,12 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - //// Size of a program identifier - //uint32_t groupSize = - // nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - //uint32_t groupStride = groupSize; - //vk::DeviceSize hitGroupSize = - // nvh::align_up(m_rtProperties.shaderGroupHandleSize + sizeof(HitRecordBuffer), - // m_rtProperties.shaderGroupBaseAlignment); - //vk::DeviceAddress sbtAddress = m_device.getBufferAddress({m_rtSBTBuffer.buffer}); - - //using Stride = vk::StridedDeviceAddressRegionKHR; - //std::array strideAddresses{ - // Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - // Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - // Stride{sbtAddress + 3u * groupSize, hitGroupSize, hitGroupSize * 2}, // hit - // Stride{0u, 0u, 0u}}; // callable - - //strideAddresses[0] = m_sbtWrapper.getRaygenRegion(); - //strideAddresses[1] = m_sbtWrapper.getMissRegion(); - //strideAddresses[2] = m_sbtWrapper.getHitRegion(); - - //cmdBuf.traceRaysKHR(&strideAddresses[0], &strideAddresses[1], &strideAddresses[2], - // &strideAddresses[3], // - // m_size.width, m_size.height, 1); // - - //std::array regions; - //regions[0] = m_sbtWrapper.getRegion(SBTWrapper::eRaygen); - //regions[1] = m_sbtWrapper.getRegion(SBTWrapper::eMiss); - //regions[2] = m_sbtWrapper.getRegion(SBTWrapper::eHit); - //regions[3] = m_sbtWrapper.getRegion(SBTWrapper::eCallable); - +#ifdef USE_SBT_WRAPPER auto& regions = m_sbtWrapper.getRegions(); vkCmdTraceRaysKHR(cmdBuf, ®ions[0], ®ions[1], ®ions[2], ®ions[3], m_size.width, m_size.height, 1); - +#else + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); +#endif m_debug.endLabel(cmdBuf); } diff --git a/ray_tracing_manyhits/hello_vulkan.h b/ray_tracing_manyhits/hello_vulkan.h index f08da79..5d3fdba 100644 --- a/ray_tracing_manyhits/hello_vulkan.h +++ b/ray_tracing_manyhits/hello_vulkan.h @@ -28,8 +28,12 @@ // #VKRay #include "nvvk/raytraceKHR_vk.hpp" + + +#define USE_SBT_WRAPPER +#ifdef USE_SBT_WRAPPER #include "nvvk/sbtwrapper_vk.hpp" -using nvvk::SBTWrapper; +#endif //-------------------------------------------------------------------------------------------------- // Simple rasterizer of OBJ objects @@ -147,7 +151,16 @@ public: std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - nvvk::Buffer m_rtSBTBuffer; + +#ifdef USE_SBT_WRAPPER + nvvk::SBTWrapper m_sbtWrapper; +#else + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; +#endif // Push constant for ray tracer PushConstantRay m_pcRay{}; @@ -157,6 +170,4 @@ public: nvmath::vec4f color; }; std::vector m_hitShaderRecord; - - nvvk::SBTWrapper m_sbtWrapper; }; diff --git a/ray_tracing_manyhits/images/sbt_0.png b/ray_tracing_manyhits/images/sbt_0.png new file mode 100644 index 0000000000000000000000000000000000000000..0216305d471d426e414584d55163b5450e0b3f72 GIT binary patch literal 14639 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 defaultSearchPaths; - //-------------------------------------------------------------------------------------------------- // Keep the handle on the device // Initialize the tool to do all our allocations: buffers, images @@ -683,8 +682,7 @@ void HelloVulkan::createTopLevelAS() // void HelloVulkan::createRtDescriptorSet() { - // Top-level acceleration structure, usable by both the ray generation and the closest hit (to - // shoot shadow rays) + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays) m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, @@ -836,39 +834,70 @@ void HelloVulkan::createRtPipeline() // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this -// See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { - auto groupCount = static_cast(m_rtShaderGroups.size()); // 4 shaders: raygen, 2 miss, chit - uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier - // Compute the actual size needed per SBT entry (round-up to alignment needed). - uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - // Bytes needed for the SBT. - uint32_t sbtSize = groupCount * groupSizeAligned; + uint32_t missCount{2}; + uint32_t hitCount{1}; + auto handleCount = 1 + missCount + hitCount; + uint32_t handleSize = m_rtProperties.shaderGroupHandleSize; - // Fetch all the shader handles used in the pipeline. This is opaque data,/ so we store it in a vector of bytes. - // The order of handles follow the stage entry. - std::vector shaderHandleStorage(sbtSize); - auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + // The SBT (buffer) need to have starting groups to be aligned and handles in the group to be aligned. + uint32_t handleSizeAligned = nvh::align_up(handleSize, m_rtProperties.shaderGroupHandleAlignment); + + m_rgenRegion.stride = nvh::align_up(handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_rgenRegion.size = m_rgenRegion.stride; // The size member of pRayGenShaderBindingTable must be equal to its stride member + m_missRegion.stride = handleSizeAligned; + m_missRegion.size = nvh::align_up(missCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + m_hitRegion.stride = handleSizeAligned; + m_hitRegion.size = nvh::align_up(hitCount * handleSizeAligned, m_rtProperties.shaderGroupBaseAlignment); + // Get the shader group handles + uint32_t dataSize = handleCount * handleSize; + std::vector handles(dataSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, handleCount, dataSize, handles.data()); assert(result == VK_SUCCESS); - // Allocate a buffer for storing the SBT. Give it a debug name for NSight. - m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + // Allocate a buffer for storing the SBT. + VkDeviceSize sbtSize = m_rgenRegion.size + m_missRegion.size + m_hitRegion.size + m_callRegion.size; + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Give it a debug name for NSight. + + // Find the SBT addresses of each group + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); + m_rgenRegion.deviceAddress = sbtAddress; + m_missRegion.deviceAddress = sbtAddress + m_rgenRegion.size; + m_hitRegion.deviceAddress = sbtAddress + m_rgenRegion.size + m_missRegion.size; + + // Helper to retrieve the handle data + auto getHandle = [&](int i) { return handles.data() + i * handleSize; }; // Map the SBT buffer and write in the handles. - void* mapped = m_alloc.map(m_rtSBTBuffer); - auto* pData = reinterpret_cast(mapped); - for(uint32_t g = 0; g < groupCount; g++) + auto* pSBTBuffer = reinterpret_cast(m_alloc.map(m_rtSBTBuffer)); + uint8_t* pData{nullptr}; + uint32_t handleIdx{0}; + // Raygen + pData = pSBTBuffer; + memcpy(pData, getHandle(handleIdx++), handleSize); + // Miss + pData = pSBTBuffer + m_rgenRegion.size; + for(uint32_t c = 0; c < missCount; c++) { - memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); - pData += groupSizeAligned; + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_missRegion.stride; } + // Hit + pData = pSBTBuffer + m_rgenRegion.size + m_missRegion.size; + for(uint32_t c = 0; c < hitCount; c++) + { + memcpy(pData, getHandle(handleIdx++), handleSize); + pData += m_hitRegion.stride; + } + m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -885,7 +914,6 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c m_pcRay.lightIntensity = m_pcRaster.lightIntensity; m_pcRay.lightType = m_pcRaster.lightType; - std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, @@ -895,21 +923,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, sizeof(PushConstantRay), &m_pcRay); - // Size of a program identifier - uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); - uint32_t groupStride = groupSize; - - VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); - - using Stride = VkStridedDeviceAddressRegionKHR; - std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen - Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss - Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 1}, // hit - Stride{0u, 0u, 0u}}; // callable - - - vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], - m_size.width, m_size.height, 1); + vkCmdTraceRaysKHR(cmdBuf, &m_rgenRegion, &m_missRegion, &m_hitRegion, &m_callRegion, m_size.width, m_size.height, 1); m_debug.endLabel(cmdBuf); diff --git a/ray_tracing_reflections/hello_vulkan.h b/ray_tracing_reflections/hello_vulkan.h index f1f70ef..d4c7f4f 100644 --- a/ray_tracing_reflections/hello_vulkan.h +++ b/ray_tracing_reflections/hello_vulkan.h @@ -144,7 +144,12 @@ public: std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - nvvk::Buffer m_rtSBTBuffer; + + nvvk::Buffer m_rtSBTBuffer; + VkStridedDeviceAddressRegionKHR m_rgenRegion{}; + VkStridedDeviceAddressRegionKHR m_missRegion{}; + VkStridedDeviceAddressRegionKHR m_hitRegion{}; + VkStridedDeviceAddressRegionKHR m_callRegion{}; // Push constant for ray tracer PushConstantRay m_pcRay{{}, {}, 0, 0, 10};