Files
flower/source/engine/rhi/accelerate_struct.cpp

519 lines
22 KiB
C++

#include "accelerate_struct.h"
#include "rhi.h"
#include <numeric>
namespace engine
{
static std::string getRuntimeUniqueGPUASName(const std::string& in)
{
static size_t GRuntimeId = 0;
GRuntimeId++;
return std::format("AS: {}. {}.", GRuntimeId, in);
}
void AccelKHR::release()
{
if (accel != VK_NULL_HANDLE)
{
destroyAccelerationStructure(accel, nullptr);
accel = VK_NULL_HANDLE;
}
buffer = nullptr;
}
void AccelKHR::create(VkAccelerationStructureCreateInfoKHR& accelInfo)
{
buffer = std::make_shared<VulkanBuffer>(
getContext(),
getRuntimeUniqueGPUASName("AccelBuffer"),
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
0,
accelInfo.size
);
// Setting the buffer
accelInfo.buffer = buffer->getVkBuffer();
// Create the acceleration structure
createAccelerationStructure(&accelInfo, nullptr, &accel);
// Cache create info.
createInfo = accelInfo;
}
void TLASBuilder::destroy()
{
if (m_bInit)
{
getContext()->waitDeviceIdle();
m_bInit = false;
m_tlas.release();
m_scratchBuffer = nullptr;
}
}
void TLASBuilder::buildTlas(
VkCommandBuffer cmdBuf,
const std::vector<VkAccelerationStructureInstanceKHR>& instances,
bool update,
VkBuildAccelerationStructureFlagsKHR flags)
{
bool bUpdate = update && m_bInit;
// Copy instance matrix to buffer.
auto instanceGPU = getContext()->getBufferParameters().getParameter("TLAS_Instances",
sizeof(instances[0]) * instances.size(),
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
VK_DESCRIPTOR_TYPE_MAX_ENUM, VulkanBuffer::getStageCopyForUploadBufferFlags());
instanceGPU->updateDataPtr((void*)instances.data());
VkDeviceAddress instBufferAddr = instanceGPU->getBuffer()->getDeviceAddress();
// Cannot call buildTlas twice except to update.
uint32_t countInstance = static_cast<uint32_t>(instances.size());
// Creating the TLAS
cmdCreateTlas(cmdBuf, countInstance, instBufferAddr, flags, bUpdate);
m_bInit = true;
}
void TLASBuilder::cmdCreateTlas(
VkCommandBuffer cmdBuf,
uint32_t countInstance,
VkDeviceAddress instBufferAddr,
VkBuildAccelerationStructureFlagsKHR flags,
bool update)
{
{
VkMemoryBarrier barrier{ VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
}
// Wraps a device pointer to the above uploaded instances.
VkAccelerationStructureGeometryInstancesDataKHR instancesVk{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR };
instancesVk.data.deviceAddress = instBufferAddr;
// Put the above into a VkAccelerationStructureGeometryKHR. We need to put the instances struct in a union and label it as instance data.
VkAccelerationStructureGeometryKHR topASGeometry{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR };
topASGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
topASGeometry.geometry.instances = instancesVk;
// Find sizes
VkAccelerationStructureBuildGeometryInfoKHR buildInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR };
VkAccelerationStructureBuildSizesInfoKHR sizeInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR };
buildInfo.flags = flags | VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
buildInfo.geometryCount = 1;
buildInfo.pGeometries = &topASGeometry;
buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
buildInfo.srcAccelerationStructure = VK_NULL_HANDLE;
{
buildInfo.mode = update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
getAccelerationStructureBuildSizesKHR(VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildInfo, &countInstance, &sizeInfo);
}
auto validMaxSize = math::max(sizeInfo.buildScratchSize, sizeInfo.updateScratchSize);
if (update)
{
CHECK(m_tlas.accel);
CHECK(m_scratchBuffer);
// Size not match, rebuild.
if (m_scratchBuffer->getSize() < validMaxSize ||
m_tlas.createInfo.size != sizeInfo.accelerationStructureSize)
{
update = false;
destroy();
}
// Need rebuild, re-compute size info and build info.
if (!update)
{
buildInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildInfo.flags = flags;
getAccelerationStructureBuildSizesKHR(VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildInfo, &countInstance, &sizeInfo);
}
}
// Create TLAS
if (m_tlas.accel == VK_NULL_HANDLE)
{
VkAccelerationStructureCreateInfoKHR createInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR };
createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
createInfo.size = sizeInfo.accelerationStructureSize;
m_tlas.create(createInfo);
}
// Prepare tlas scratch buffer if no exist.
if (m_scratchBuffer == nullptr)
{
// Allocate the scratch memory
m_scratchBuffer = std::make_unique<VulkanBuffer>(
getContext(),
getRuntimeUniqueGPUASName("tlas_scratch"),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
0,
math::max(4U * 1024U * 1024U, getNextPOT((uint32_t)validMaxSize)) // At least 4 MB for TLAS scratch.
);
}
VkDeviceAddress scratchAddress = m_scratchBuffer->getDeviceAddress();
// Update build information
buildInfo.srcAccelerationStructure = update ? m_tlas.accel : VK_NULL_HANDLE;
buildInfo.dstAccelerationStructure = m_tlas.accel;
buildInfo.scratchData.deviceAddress = scratchAddress;
// Build Offsets info: n instances
VkAccelerationStructureBuildRangeInfoKHR buildOffsetInfo{ countInstance, 0, 0, 0 };
const VkAccelerationStructureBuildRangeInfoKHR* pBuildOffsetInfo = &buildOffsetInfo;
// Build the TLAS
cmdBuildAccelerationStructures(cmdBuf, 1, &buildInfo, &pBuildOffsetInfo);
{
// Barrier.
VkMemoryBarrier barrier{ VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
}
if (!update)
{
vkQueueWaitIdle(getContext()->getMajorGraphicsQueue());
}
}
void BLASBuilder::destroy()
{
getContext()->waitDeviceIdle();
m_bInit = false;
for (auto& b : m_blas)
{
b.release();
}
m_blas.clear();
}
VkDeviceAddress BLASBuilder::getBlasDeviceAddress(uint32_t inBlasId)
{
CHECK(m_bInit);
CHECK(size_t(inBlasId) < m_blas.size());
VkAccelerationStructureDeviceAddressInfoKHR addressInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR };
addressInfo.accelerationStructure = m_blas[inBlasId].accel;
return getAccelerationStructureDeviceAddress(&addressInfo);
}
void BLASBuilder::build(const std::vector<BlasInput>& input, VkBuildAccelerationStructureFlagsKHR flags)
{
m_bInit = true;
auto nbBlas = static_cast<uint32_t>(input.size());
VkDeviceSize asTotalSize{ 0 }; // Memory size of all allocated BLAS
uint32_t nbCompactions{ 0 }; // Nb of BLAS requesting compaction
VkDeviceSize maxScratchSize{ 0 }; // Largest scratch size
// Preparing the information for the acceleration build commands.
std::vector<BuildAccelerationStructure> buildAs(nbBlas);
for (uint32_t idx = 0; idx < nbBlas; idx++)
{
// Filling partially the VkAccelerationStructureBuildGeometryInfoKHR for querying the build sizes.
// Other information will be filled in the createBlas (see #2)
buildAs[idx].buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
buildAs[idx].buildInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildAs[idx].buildInfo.flags = input[idx].flags | flags;
buildAs[idx].buildInfo.geometryCount = static_cast<uint32_t>(input[idx].asGeometry.size());
buildAs[idx].buildInfo.pGeometries = input[idx].asGeometry.data();
// Build range information
buildAs[idx].rangeInfo = input[idx].asBuildOffsetInfo.data();
// Finding sizes to create acceleration structures and scratch
std::vector<uint32_t> maxPrimCount(input[idx].asBuildOffsetInfo.size());
for (auto tt = 0; tt < input[idx].asBuildOffsetInfo.size(); tt++)
maxPrimCount[tt] = input[idx].asBuildOffsetInfo[tt].primitiveCount; // Number of primitives/triangles
getAccelerationStructureBuildSizesKHR(VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
&buildAs[idx].buildInfo, maxPrimCount.data(), &buildAs[idx].sizeInfo);
// Extra info
asTotalSize += buildAs[idx].sizeInfo.accelerationStructureSize;
maxScratchSize = std::max(maxScratchSize, buildAs[idx].sizeInfo.buildScratchSize);
nbCompactions += hasFlag(buildAs[idx].buildInfo.flags, VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR);
}
// Allocate the scratch buffers holding the temporary data of the acceleration structure builder
std::unique_ptr<VulkanBuffer> scratchBuffer = std::make_unique<VulkanBuffer>(
getContext(),
getRuntimeUniqueGPUASName("scratch"),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
0,
maxScratchSize
);
VkDeviceAddress scratchAddress = scratchBuffer->getDeviceAddress();
// Allocate a query pool for storing the needed size for every BLAS compaction.
VkQueryPool queryPool{ VK_NULL_HANDLE };
if (nbCompactions > 0) // Is compaction requested?
{
CHECK(nbCompactions == nbBlas); // Don't allow mix of on/off compaction
VkQueryPoolCreateInfo qpci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO };
qpci.queryCount = nbBlas;
qpci.queryType = VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR;
vkCreateQueryPool(getContext()->getDevice(), &qpci, nullptr, &queryPool);
}
// Batching creation/compaction of BLAS to allow staying in restricted amount of memory
std::vector<uint32_t> indices; // Indices of the BLAS to create
VkDeviceSize batchSize{ 0 };
VkDeviceSize batchLimit{ 256'000'000 }; // 256 MB
for (uint32_t idx = 0; idx < nbBlas; idx++)
{
indices.push_back(idx);
batchSize += buildAs[idx].sizeInfo.accelerationStructureSize;
// Over the limit or last BLAS element
if (batchSize >= batchLimit || idx == nbBlas - 1)
{
getContext()->executeImmediately(
getContext()->getMajorComputeCommandPool(),
getContext()->getMajorComputeQueue(), [&](VkCommandBuffer cmd)
{
cmdCreateBlas(cmd, indices, buildAs, scratchAddress, queryPool);
});
if (queryPool)
{
getContext()->executeImmediately(
getContext()->getMajorComputeCommandPool(),
getContext()->getMajorComputeQueue(), [&](VkCommandBuffer cmd)
{
cmdCompactBlas(cmd, indices, buildAs, queryPool);
});
// Destroy the non-compacted version
destroyNonCompacted(indices, buildAs);
}
// Reset
batchSize = 0;
indices.clear();
}
}
// Logging reduction
if (queryPool)
{
VkDeviceSize compactSize = std::accumulate(buildAs.begin(), buildAs.end(), 0ULL, [](const auto& a, const auto& b) {
return a + b.sizeInfo.accelerationStructureSize;
});
LOG_TRACE(" RT BLAS: reducing from: {0} KB to: {1}KB, Save {2}KB({3}% smaller).",
asTotalSize / 1024.0f,
compactSize / 1024.0f,
(asTotalSize - compactSize) / 1024.0f,
(asTotalSize - compactSize) / float(asTotalSize) * 100.f);
}
// Keeping all the created acceleration structures
for (auto& b : buildAs)
{
m_blas.emplace_back(b.as);
}
// Clean up
vkDestroyQueryPool(getContext()->getDevice(), queryPool, nullptr);
}
void BLASBuilder::update(VkCommandBuffer cmd, const std::vector<BlasInput>& input, VkBuildAccelerationStructureFlagsKHR flags)
{
CHECK(m_bInit);
std::vector<VkAccelerationStructureBuildGeometryInfoKHR> buildInfosArray(input.size());
{
VkMemoryBarrier barrier{ VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
}
VkDeviceSize maxSize = 0;
for (size_t i = 0; i < input.size(); i++)
{
auto& blas = input[i];
auto& buildInfos = buildInfosArray[i];
// Preparing all build information, acceleration is filled later
buildInfos.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
buildInfos.flags = flags;
buildInfos.geometryCount = (uint32_t)blas.asGeometry.size();
buildInfos.pGeometries = blas.asGeometry.data();
buildInfos.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR; // UPDATE
buildInfos.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
buildInfos.srcAccelerationStructure = m_blas[i].accel; // UPDATE
buildInfos.dstAccelerationStructure = m_blas[i].accel;
// Find size to build on the device
std::vector<uint32_t> maxPrimCount(blas.asBuildOffsetInfo.size());
for (auto tt = 0; tt < blas.asBuildOffsetInfo.size(); tt++)
{
maxPrimCount[tt] = blas.asBuildOffsetInfo[tt].primitiveCount; // Number of primitives/triangles
}
VkAccelerationStructureBuildSizesInfoKHR sizeInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR };
getAccelerationStructureBuildSizesKHR(VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildInfos, maxPrimCount.data(), &sizeInfo);
maxSize = math::max(maxSize, math::max(sizeInfo.updateScratchSize, sizeInfo.buildScratchSize));
}
if (m_updateScratchBuffer)
{
if (maxSize > m_updateScratchBuffer->getSize())
{
m_updateScratchBuffer = nullptr;
getContext()->waitDeviceIdle();
}
}
// Allocate the scratch buffer and setting the scratch info
if (m_updateScratchBuffer == nullptr)
{
m_updateScratchBuffer = std::make_unique<VulkanBuffer>(
getContext(),
getRuntimeUniqueGPUASName("scratch_update"),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
0,
getNextPOT((uint32_t)maxSize)
);
}
for (size_t i = 0; i < input.size(); i++)
{
auto& blas = input[i];
auto& buildInfos = buildInfosArray[i];
buildInfos.scratchData.deviceAddress = m_updateScratchBuffer->getDeviceAddress();
std::vector<const VkAccelerationStructureBuildRangeInfoKHR*> pBuildOffset(blas.asBuildOffsetInfo.size());
for (size_t i = 0; i < blas.asBuildOffsetInfo.size(); i++)
pBuildOffset[i] = &blas.asBuildOffsetInfo[i];
cmdBuildAccelerationStructures(cmd, 1, &buildInfos, pBuildOffset.data());
{
VkMemoryBarrier barrier{ VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
}
}
}
void BLASBuilder::cmdCreateBlas(
VkCommandBuffer cmdBuf,
std::vector<uint32_t> indices,
std::vector<BuildAccelerationStructure>& buildAs,
VkDeviceAddress scratchAddress,
VkQueryPool queryPool)
{
if (queryPool)
{
vkResetQueryPool(getContext()->getDevice(), queryPool, 0, static_cast<uint32_t>(indices.size()));
}
uint32_t queryCnt{ 0 };
for (const auto& idx : indices)
{
// Actual allocation of buffer and acceleration structure.
VkAccelerationStructureCreateInfoKHR createInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR };
createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
createInfo.size = buildAs[idx].sizeInfo.accelerationStructureSize; // Will be used to allocate memory.
buildAs[idx].as.create(createInfo);
// BuildInfo #2 part
buildAs[idx].buildInfo.dstAccelerationStructure = buildAs[idx].as.accel; // Setting where the build lands
buildAs[idx].buildInfo.scratchData.deviceAddress = scratchAddress; // All build are using the same scratch buffer
// Building the bottom-level-acceleration-structure
cmdBuildAccelerationStructures(cmdBuf, 1, &buildAs[idx].buildInfo, &buildAs[idx].rangeInfo);
// Since the scratch buffer is reused across builds, we need a barrier to ensure one build
// is finished before starting the next one.
VkMemoryBarrier barrier{ VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, 0, 1, &barrier, 0, nullptr, 0, nullptr);
if (queryPool)
{
// Add a query to find the 'real' amount of memory needed, use for compaction
cmdWriteAccelerationStructuresProperties(cmdBuf, 1, &buildAs[idx].buildInfo.dstAccelerationStructure,
VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR, queryPool, queryCnt++);
}
}
}
void BLASBuilder::cmdCompactBlas(VkCommandBuffer cmdBuf, std::vector<uint32_t> indices, std::vector<BuildAccelerationStructure>& buildAs, VkQueryPool queryPool)
{
uint32_t queryCtn{ 0 };
// Get the compacted size result back
std::vector<VkDeviceSize> compactSizes(static_cast<uint32_t>(indices.size()));
vkGetQueryPoolResults(getContext()->getDevice(), queryPool, 0, (uint32_t)compactSizes.size(), compactSizes.size() * sizeof(VkDeviceSize),
compactSizes.data(), sizeof(VkDeviceSize), VK_QUERY_RESULT_WAIT_BIT);
for (auto idx : indices)
{
buildAs[idx].cleanupAS = buildAs[idx].as; // previous AS to destroy
buildAs[idx].sizeInfo.accelerationStructureSize = compactSizes[queryCtn++]; // new reduced size
// Creating a compact version of the AS
VkAccelerationStructureCreateInfoKHR asCreateInfo{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR };
asCreateInfo.size = buildAs[idx].sizeInfo.accelerationStructureSize;
asCreateInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
buildAs[idx].as.create(asCreateInfo);
// Copy the original BLAS to a compact version
VkCopyAccelerationStructureInfoKHR copyInfo{ VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_INFO_KHR };
copyInfo.src = buildAs[idx].buildInfo.dstAccelerationStructure;
copyInfo.dst = buildAs[idx].as.accel;
copyInfo.mode = VK_COPY_ACCELERATION_STRUCTURE_MODE_COMPACT_KHR;
cmdCopyAccelerationStructure(cmdBuf, &copyInfo);
}
}
void BLASBuilder::destroyNonCompacted(std::vector<uint32_t> indices, std::vector<BuildAccelerationStructure>& buildAs)
{
for (auto& i : indices)
{
buildAs[i].cleanupAS.release();
}
}
}