251 lines
9.8 KiB
HTML
251 lines
9.8 KiB
HTML
<meta charset="utf-8" lang="en">
|
|
|
|
**Converting VK_NV_ray_tracing to VK_KHR_ray_tracing**
|
|
|
|
This document is a quick guide on what need to be changed to convert an existing application
|
|
using NV ray tracing extension to KHR.
|
|
|
|
# The Obvious
|
|
|
|
For most structures and enum, the ending with NV can be replaced with KHR.
|
|
|
|
This is true for example for:
|
|
|
|
Some examples:
|
|
|
|
NVIDIA | KHRONOS
|
|
-------------------------------|-----------------------------
|
|
VK_SHADER_STAGE_RAYGEN_BIT_NV | VK_SHADER_STAGE_RAYGEN_BIT_KHR
|
|
VK_SHADER_STAGE_CLOSEST_HIT_BIT_NV | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR
|
|
VK_GEOMETRY_INSTANCE_TRIANGLE_CULL_DISABLE_BIT_NV | VK_GEOMETRY_INSTANCE_TRIANGLE_CULL_DISABLE_BIT_KHR
|
|
VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_NV | VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR
|
|
[Types and Flags]
|
|
|
|
NVIDIA | KHRONOS
|
|
-------------------------------|-----------------------------
|
|
VkWriteDescriptorSetAccelerationStructureNV | VkWriteDescriptorSetAccelerationStructureKHR
|
|
VkRayTracingShaderGroupCreateInfoNV | VkRayTracingShaderGroupCreateInfoKHR
|
|
VkRayTracingPipelineCreateInfoNV | VkRayTracingPipelineCreateInfoKHR
|
|
[Structures]
|
|
|
|
|
|
# Handles version Device Addresses -> memory allocations
|
|
|
|
With KHR, we no longer pass the buffer or an handle, but the `vk::DeviceAddress`.
|
|
First, when allocating a buffer, it has to have the `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` flag.
|
|
Similarly, the memory associated with this buffer, needs also the `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT` flag.
|
|
|
|
For the memory allocation, this could be done like this:
|
|
|
|
~~~~ C++
|
|
vk::MemoryAllocateFlagsInfo memFlagInfo;
|
|
memFlagInfo.setFlags(vk::MemoryAllocateFlagBits::eDeviceAddress);
|
|
|
|
vk::MemoryAllocateInfo memAlloc;
|
|
memAlloc.setPNext(&memFlagInfo);
|
|
// Allocate memory
|
|
~~~~
|
|
|
|
The buffer address could then be retrieved like this:
|
|
|
|
~~~~ C++
|
|
vk::DeviceAddress vertexAddress = m_device.getBufferAddress({model.vertexBuffer.buffer});
|
|
~~~~
|
|
|
|
# Where is GeometryNV?
|
|
|
|
The structure to create BLAS was replaced by different structures.
|
|
|
|
* `vk::AccelerationStructureCreateGeometryTypeInfoKHR` : describe how the acceleration structure is created. It is an indication how large it could be.
|
|
* `vk::AccelerationStructureGeometryKHR` : the geometry to build, addresses of vertices and indices
|
|
* `vk::AccelerationStructureBuildOffsetInfoKHR` : the number of elements to build and offsets
|
|
|
|
Those three structures can be an array of each, meaning that a BLAS can be a combination fo multiple geometries.
|
|
|
|
|
|
As an example on how those are filed. It returns `nvvkpp::RaytracingBuilderKHR::Blas` which has vectors
|
|
of the above structures.
|
|
|
|
~~~~ C++
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Converting a OBJ primitive to the ray tracing geometry used for the BLAS
|
|
//
|
|
auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model)
|
|
{
|
|
// Setting up the creation info of acceleration structure
|
|
vk::AccelerationStructureCreateGeometryTypeInfoKHR asCreate;
|
|
asCreate.setGeometryType(vk::GeometryTypeKHR::eTriangles);
|
|
asCreate.setIndexType(vk::IndexType::eUint32);
|
|
asCreate.setVertexFormat(vk::Format::eR32G32B32Sfloat);
|
|
asCreate.setMaxPrimitiveCount(model.nbIndices / 3); // Nb triangles
|
|
asCreate.setMaxVertexCount(model.nbVertices);
|
|
asCreate.setAllowsTransforms(VK_FALSE); // No adding transformation matrices
|
|
|
|
// Building part
|
|
vk::DeviceAddress vertexAddress = m_device.getBufferAddress({model.vertexBuffer.buffer});
|
|
vk::DeviceAddress indexAddress = m_device.getBufferAddress({model.indexBuffer.buffer});
|
|
|
|
vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
|
|
triangles.setVertexFormat(asCreate.vertexFormat);
|
|
triangles.setVertexData(vertexAddress);
|
|
triangles.setVertexStride(sizeof(VertexObj));
|
|
triangles.setIndexType(asCreate.indexType);
|
|
triangles.setIndexData(indexAddress);
|
|
triangles.setTransformData({});
|
|
|
|
// Setting up the build info of the acceleration
|
|
vk::AccelerationStructureGeometryKHR asGeom;
|
|
asGeom.setGeometryType(asCreate.geometryType);
|
|
asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
|
|
asGeom.geometry.setTriangles(triangles);
|
|
|
|
// The primitive itself
|
|
vk::AccelerationStructureBuildOffsetInfoKHR offset;
|
|
offset.setFirstVertex(0);
|
|
offset.setPrimitiveCount(asCreate.maxPrimitiveCount);
|
|
offset.setPrimitiveOffset(0);
|
|
offset.setTransformOffset(0);
|
|
|
|
// Our blas is only one geometry, but could be made of many geometries
|
|
nvvkpp::RaytracingBuilderKHR::Blas blas;
|
|
blas.asGeometry.emplace_back(asGeom);
|
|
blas.asCreateGeometryInfo.emplace_back(asCreate);
|
|
blas.asBuildOffsetInfo.emplace_back(offset);
|
|
|
|
return blas;
|
|
}
|
|
~~~~
|
|
|
|
|
|
# Creating and building BLAS/TLAS
|
|
|
|
With the structures filled in, there are some similarities with the NVIDIA extension.
|
|
|
|
## BLAS
|
|
|
|
The construction of a BLAS AS will look like this:
|
|
|
|
~~~~ C++
|
|
vk::AccelerationStructureCreateInfoKHR asCreateInfo{{}, vk::AccelerationStructureTypeKHR::eBottomLevel};
|
|
asCreateInfo.setFlags(vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
|
|
asCreateInfo.setMaxGeometryCount((uint32_t)blas.asCreateGeometryInfo.size());
|
|
asCreateInfo.setPGeometryInfos(blas.asCreateGeometryInfo.data());
|
|
|
|
// Create an acceleration structure identifier and allocate memory to
|
|
// store the resulting structure data
|
|
blas.as = m_alloc.createAcceleration(asCreateInfo);
|
|
~~~~
|
|
|
|
To retrieve the memory requirements, there is a new flag, to be build on the host or the device.
|
|
|
|
~~~~ C++
|
|
vk::AccelerationStructureMemoryRequirementsInfoKHR memoryRequirementsInfo{
|
|
vk::AccelerationStructureMemoryRequirementsTypeKHR::eBuildScratch,
|
|
vk::AccelerationStructureBuildTypeKHR::eDevice, blas.as.accel};
|
|
~~~~
|
|
|
|
Building the acceleration structure requires to pass a pointer to the array of vk::AccelerationStructureGeometryKHR.
|
|
|
|
~~~~ C++
|
|
const vk::AccelerationStructureGeometryKHR* pGeometry = blas.asGeometry.data();
|
|
vk::AccelerationStructureBuildGeometryInfoKHR bottomASInfo{vk::AccelerationStructureTypeKHR::eBottomLevel};
|
|
bottomASInfo.setFlags(flags);
|
|
bottomASInfo.setUpdate(VK_FALSE);
|
|
bottomASInfo.setSrcAccelerationStructure({});
|
|
bottomASInfo.setDstAccelerationStructure(blas.as.accel);
|
|
bottomASInfo.setGeometryArrayOfPointers(VK_FALSE);
|
|
bottomASInfo.setGeometryCount((uint32_t)blas.asGeometry.size());
|
|
bottomASInfo.setPpGeometries(&pGeometry);
|
|
bottomASInfo.setScratchData(scratchAddress);
|
|
~~~~
|
|
|
|
|
|
It will be also necessary to create an array of pointers to the vk::AccelerationStructureBuildOffsetInfoKHR of each BLAS.
|
|
|
|
~~~~ C++
|
|
// Pointers of offset
|
|
std::vector<const vk::AccelerationStructureBuildOffsetInfoKHR*> pBuildOffset(blas.asBuildOffsetInfo.size());
|
|
for(size_t i = 0; i < blas.asBuildOffsetInfo.size(); i++)
|
|
pBuildOffset[i] = &blas.asBuildOffsetInfo[i];
|
|
~~~~
|
|
|
|
## TLAS
|
|
|
|
The same structures are now used to build the top-level, using instances as the type of geometry.
|
|
|
|
FOr example, here how can be created the AS for an array of instances
|
|
|
|
~~~~ C++
|
|
vk::AccelerationStructureCreateGeometryTypeInfoKHR geometryCreate{vk::GeometryTypeKHR::eInstances};
|
|
geometryCreate.setMaxPrimitiveCount(static_cast<uint32_t>(instances.size()));
|
|
geometryCreate.setAllowsTransforms(VK_TRUE);
|
|
|
|
vk::AccelerationStructureCreateInfoKHR asCreateInfo{{}, vk::AccelerationStructureTypeKHR::eTopLevel};
|
|
asCreateInfo.setFlags(flags);
|
|
asCreateInfo.setMaxGeometryCount(1);
|
|
asCreateInfo.setPGeometryInfos(&geometryCreate);
|
|
|
|
// Create the acceleration structure object and allocate the memory
|
|
// required to hold the TLAS data
|
|
m_tlas.as = m_alloc.createAcceleration(asCreateInfo);
|
|
~~~~
|
|
|
|
|
|
|
|
|
|
Also, there are now a structure to hold the instances `vk::AccelerationStructureInstanceKHR`
|
|
You will need to fill an array with all the information, create a buffer and use
|
|
the address to set the geometry
|
|
|
|
~~~~ C++
|
|
// Allocate the instance buffer and copy its contents from host to device
|
|
// memory
|
|
m_instBuffer = m_alloc.createBuffer(cmdBuf, geometryInstances,
|
|
vk::BufferUsageFlagBits::eRayTracingKHR | vk::BufferUsageFlagBits::eShaderDeviceAddress);
|
|
m_debug.setObjectName(m_instBuffer.buffer, "TLASInstances");
|
|
vk::DeviceAddress instanceAddress = m_device.getBufferAddress(m_instBuffer.buffer);
|
|
|
|
vk::AccelerationStructureGeometryKHR topASGeometry{vk::GeometryTypeKHR::eInstances};
|
|
topASGeometry.geometry.instances.setArrayOfPointers(VK_FALSE);
|
|
topASGeometry.geometry.instances.setData(instanceAddress);
|
|
~~~~
|
|
|
|
|
|
|
|
|
|
# Calling TraceRaysKHR
|
|
|
|
This is very close to the NVIDIA version, the difference is instead of passing buffer addresses, offsets, strides,
|
|
for each stages, we have to fill vk::StridedBufferRegionKHR structure of each stages, which have
|
|
the same parameters: buffer, offset, stride and SBT size
|
|
|
|
Example:
|
|
|
|
~~~~ C++
|
|
vk::DeviceSize sbtSize = progSize * (vk::DeviceSize)m_rtShaderGroups.size();
|
|
|
|
const vk::StridedBufferRegionKHR raygenShaderBindingTable = {m_rtSBTBuffer.buffer, rayGenOffset,
|
|
progSize, sbtSize};
|
|
const vk::StridedBufferRegionKHR missShaderBindingTable = {m_rtSBTBuffer.buffer, missOffset,
|
|
progSize, sbtSize};
|
|
const vk::StridedBufferRegionKHR hitShaderBindingTable = {m_rtSBTBuffer.buffer, hitGroupOffset,
|
|
progSize, sbtSize};
|
|
const vk::StridedBufferRegionKHR callableShaderBindingTable;
|
|
|
|
cmdBuf.traceRaysKHR(&raygenShaderBindingTable, &missShaderBindingTable, &hitShaderBindingTable,
|
|
&callableShaderBindingTable, //
|
|
m_size.width, m_size.height, 1); //
|
|
|
|
~~~~
|
|
|
|
|
|
|
|
|
|
<!-- Markdeep: -->
|
|
<link rel="stylesheet" href="vkrt_tutorial.css?">
|
|
<script> window.markdeepOptions = { tocStyle: "medium" };</script>
|
|
<script src="markdeep.min.js" charset="utf-8"></script>
|
|
<script src="https://developer.nvidia.com/sites/default/files/akamai/gameworks/whitepapers/markdeep.min.js" charset="utf-8"></script>
|
|
<script>
|
|
window.alreadyProcessedMarkdeep || (document.body.style.visibility = "visible")
|
|
</script>
|