Files
renderdoc/renderdoc/driver/vulkan/vk_acceleration_structure.cpp
T

1000 lines
35 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2024 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "vk_acceleration_structure.h"
#include "core/settings.h"
#include "vk_core.h"
#include "vk_manager.h"
RDOC_EXTERN_CONFIG(bool, Vulkan_Debug_SingleSubmitFlushing);
RDOC_CONFIG(uint32_t, Vulkan_Debug_RT_MaxVertexIncrement, 1000,
"Amount to add to the API-provided max vertex when building a BLAS with an index "
"buffer, to account for incorrectly set values by application.");
RDOC_CONFIG(
uint32_t, Vulkan_Debug_RT_MaxVertexPercentIncrease, 10,
"Percentage increase for the API-provided max vertex when building a BLAS with an index "
"buffer, to account for incorrectly set values by application.");
namespace
{
// Although the serialised data is implementation-defined in general, the header is defined:
// https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap37.html#vkCmdCopyAccelerationStructureToMemoryKHR
constexpr std::size_t handleCountOffset = VK_UUID_SIZE + VK_UUID_SIZE + 8 + 8;
constexpr VkDeviceSize handleCountSize = 8;
// Spec says VkCopyAccelerationStructureToMemoryInfoKHR::dst::deviceAddress must be 256 bytes aligned
constexpr VkDeviceSize asBufferAlignment = 256;
VkDeviceSize IndexTypeSize(VkIndexType type)
{
switch(type)
{
case VK_INDEX_TYPE_UINT32: return 4;
case VK_INDEX_TYPE_UINT16: return 2;
case VK_INDEX_TYPE_UINT8_KHR: return 1;
default: return 0;
}
}
}
VkAccelerationStructureInfo::~VkAccelerationStructureInfo()
{
readbackMem.Destroy();
}
void VkAccelerationStructureInfo::Release()
{
const int32_t ref = Atomic::Dec32(&refCount);
RDCASSERT(ref >= 0);
if(ref <= 0)
delete this;
}
uint64_t VkAccelerationStructureInfo::GetSerialisedSize() const
{
const uint64_t geomDataSize = geometryData.byteSize();
const uint64_t size = sizeof(VkAccelerationStructureTypeKHR) + // type
sizeof(VkBuildAccelerationStructureFlagsKHR) + // flags
sizeof(uint64_t) + geomDataSize; // geometryData;
// Add the readbackmem buffer sizes
const uint64_t bufferSize = sizeof(uint64_t) + memSize + WriteSerialiser::GetChunkAlignment();
return size + bufferSize;
}
void VkAccelerationStructureInfo::convertGeometryData(
rdcarray<VkAccelerationStructureGeometryKHR> &geometry) const
{
geometry.clear();
for(const VkAccelerationStructureInfo::GeometryData &g : geometryData)
{
VkAccelerationStructureGeometryDataKHR geoUnion = {};
switch(g.geometryType)
{
case VK_GEOMETRY_TYPE_TRIANGLES_KHR:
{
// We'll write the offset into buffer address so when FixUpReplayBDAs is called, the real
// base address is just added on
VkDeviceOrHostAddressConstKHR vData;
vData.deviceAddress = g.memOffset;
VkDeviceOrHostAddressConstKHR iData;
iData.deviceAddress = g.memOffset;
// vkGetAccelerationStructureBuildSizesKHR just checks if the transform BDA is non-null,
// so fudge that here
VkDeviceOrHostAddressConstKHR tData;
tData.deviceAddress = g.buildRangeInfo.transformOffset ? g.memOffset : ~0ULL;
geoUnion.triangles = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR,
NULL,
g.tris.vertexFormat,
vData,
g.tris.vertexStride,
g.tris.maxVertex,
g.tris.indexType,
iData,
tData,
};
break;
}
case VK_GEOMETRY_TYPE_AABBS_KHR:
{
VkDeviceOrHostAddressConstKHR aData;
aData.deviceAddress = g.memOffset;
geoUnion.aabbs = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR,
NULL,
aData,
g.aabbs.stride,
};
break;
}
case VK_GEOMETRY_TYPE_INSTANCES_KHR:
{
VkDeviceOrHostAddressConstKHR iData;
iData.deviceAddress = g.memOffset;
geoUnion.instances = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR,
NULL,
false,
iData,
};
break;
}
default: RDCERR("Unhandled geometry type: %d", g.geometryType); return;
}
geometry.push_back({VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR, NULL, g.geometryType,
geoUnion, g.flags});
}
}
rdcarray<VkAccelerationStructureBuildRangeInfoKHR> VkAccelerationStructureInfo::getBuildRanges() const
{
rdcarray<VkAccelerationStructureBuildRangeInfoKHR> result;
result.reserve(geometryData.size());
for(const GeometryData &geom : geometryData)
result.push_back(geom.buildRangeInfo);
return result;
}
VulkanAccelerationStructureManager::VulkanAccelerationStructureManager(WrappedVulkan *driver)
: m_pDriver(driver)
{
}
RDResult VulkanAccelerationStructureManager::CopyInputBuffers(
VkCommandBuffer commandBuffer, const VkAccelerationStructureBuildGeometryInfoKHR &info,
const VkAccelerationStructureBuildRangeInfoKHR *buildRange)
{
VkResourceRecord *cmdRecord = GetRecord(commandBuffer);
RDCASSERT(cmdRecord);
VkResourceRecord *asRecord = GetRecord(info.dstAccelerationStructure);
RDCASSERT(asRecord);
// If this is an update then replace the existing and safely delete it
VkAccelerationStructureInfo *metadata = asRecord->accelerationStructureInfo;
if(!metadata->geometryData.empty())
{
DeletePreviousInfo(commandBuffer, metadata);
metadata = asRecord->accelerationStructureInfo = new VkAccelerationStructureInfo();
}
VkDevice device = cmdRecord->cmdInfo->device;
metadata->type = info.type;
metadata->flags = info.flags;
metadata->geometryData.reserve(info.geometryCount);
struct BufferData
{
BufferData() = default;
BufferData(RecordAndOffset r) : rao(r)
{
if(rao.record != NULL)
buf = ToUnwrappedHandle<VkBuffer>(rao.record->Resource);
}
operator bool() const { return buf != VK_NULL_HANDLE; }
bool operator!() const { return buf == VK_NULL_HANDLE; }
void SetReadPosition(VkDeviceSize startFrom) { start = startFrom; }
VkDeviceSize GetReadPosition() const { return rao.offset + start; }
RecordAndOffset rao;
VkBuffer buf = VK_NULL_HANDLE;
VkDeviceSize alignment = 0;
VkDeviceSize size = 0;
VkBufferCopy region;
private:
VkDeviceSize start = 0;
};
VkDeviceSize currentDstOffset = 0;
rdcarray<BufferData> inputBuffersData;
for(uint32_t i = 0; i < info.geometryCount; ++i)
{
// Work out the buffer size needed for each geometry type
const VkAccelerationStructureGeometryKHR &geometry =
info.pGeometries != NULL ? info.pGeometries[i] : *(info.ppGeometries[i]);
const VkAccelerationStructureBuildRangeInfoKHR &rangeInfo = buildRange[i];
switch(geometry.geometryType)
{
case VK_GEOMETRY_TYPE_TRIANGLES_KHR:
{
const VkAccelerationStructureGeometryTrianglesDataKHR &triInfo = geometry.geometry.triangles;
// Find the associated VkBuffers
BufferData vertexData = GetDeviceAddressData(triInfo.vertexData.deviceAddress);
if(!vertexData)
{
RDCERR("Unable to find VkBuffer for vertex data at 0x%llx",
triInfo.vertexData.deviceAddress);
continue;
}
BufferData indexData;
if(triInfo.indexType != VK_INDEX_TYPE_NONE_KHR)
{
indexData = GetDeviceAddressData(triInfo.indexData.deviceAddress);
if(!indexData)
{
RDCERR("Unable to find VkBuffer for index data at 0x%llx",
triInfo.indexData.deviceAddress);
continue;
}
}
BufferData transformData;
if(triInfo.transformData.deviceAddress != 0x0)
{
transformData = GetDeviceAddressData(triInfo.transformData.deviceAddress);
if(!transformData)
{
RDCERR("Unable to find VkBuffer for transform data at 0x%llx",
triInfo.transformData.deviceAddress);
continue;
}
}
// Gather the buffer requirements for each type
VkMemoryRequirements mrq = {};
// Vertex buffer. The complexity here is that the rangeInfo members are interpreted
// differently depending on whether or not index buffers are used
ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), vertexData.buf, &mrq);
vertexData.alignment = mrq.alignment;
if(indexData)
{
// don't take maxVertex as perfect, applications have no reason to set it correctly for
// everything to work with drivers, and likely no validation. Add an overestimate factor
uint32_t untrustedVertexCount = triInfo.maxVertex;
uint32_t estimatedVertexCount =
untrustedVertexCount +
(untrustedVertexCount / 100) * Vulkan_Debug_RT_MaxVertexPercentIncrease() +
Vulkan_Debug_RT_MaxVertexIncrement();
// don't read more than what is left in the buffer
vertexData.size = RDCMIN(triInfo.vertexStride * estimatedVertexCount,
vertexData.rao.record->memSize - vertexData.rao.offset);
vertexData.SetReadPosition(0);
}
else
{
vertexData.size = rangeInfo.primitiveCount * 3 * triInfo.vertexStride;
vertexData.SetReadPosition(rangeInfo.primitiveOffset +
(triInfo.vertexStride * rangeInfo.firstVertex));
}
// Index buffer
if(indexData)
{
ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), indexData.buf, &mrq);
indexData.alignment = mrq.alignment;
indexData.size = rangeInfo.primitiveCount * 3 * IndexTypeSize(triInfo.indexType);
indexData.SetReadPosition(rangeInfo.primitiveOffset);
}
// Transform buffer
if(transformData)
{
ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), transformData.buf, &mrq);
transformData.alignment = mrq.alignment;
transformData.size = sizeof(VkTransformMatrixKHR);
transformData.SetReadPosition(rangeInfo.transformOffset);
}
// Store the metadata
VkAccelerationStructureInfo::GeometryData geoData;
geoData.geometryType = geometry.geometryType;
geoData.flags = geometry.flags;
geoData.memOffset = currentDstOffset;
geoData.tris.vertexFormat = geometry.geometry.triangles.vertexFormat;
geoData.tris.vertexStride = geometry.geometry.triangles.vertexStride;
geoData.tris.maxVertex = geometry.geometry.triangles.maxVertex;
geoData.tris.indexType = geometry.geometry.triangles.indexType;
// Frustratingly rangeInfo.primitiveOffset represents either the offset into the index or
// vertex buffer depending if indices are in use or not
VkAccelerationStructureBuildRangeInfoKHR &buildData = geoData.buildRangeInfo;
buildData.primitiveCount = rangeInfo.primitiveCount;
buildData.primitiveOffset = 0;
buildData.firstVertex = 0;
buildData.transformOffset = 0;
// Store the data and update the current destinaton offset
vertexData.region = {
vertexData.GetReadPosition(),
currentDstOffset,
vertexData.size,
};
inputBuffersData.push_back(vertexData);
currentDstOffset += AlignUp(vertexData.size, vertexData.alignment);
if(indexData)
{
// The index primitiveOffset has its own alignment requirements
buildData.primitiveOffset = (uint32_t)(currentDstOffset - geoData.memOffset);
const uint32_t primOffsetAlign =
AlignUp(buildData.primitiveOffset, (uint32_t)IndexTypeSize(triInfo.indexType)) -
buildData.primitiveOffset;
buildData.primitiveOffset += primOffsetAlign;
currentDstOffset += primOffsetAlign;
buildData.firstVertex = rangeInfo.firstVertex;
indexData.region = {
indexData.GetReadPosition(),
currentDstOffset,
indexData.size,
};
inputBuffersData.push_back(indexData);
currentDstOffset += AlignUp(indexData.size, indexData.alignment);
}
if(transformData)
{
// The transform primitiveOffset has its own alignment requirements
buildData.transformOffset = (uint32_t)(currentDstOffset - geoData.memOffset);
const uint32_t primOffsetAlign =
AlignUp(buildData.transformOffset, (uint32_t)16) - buildData.transformOffset;
buildData.transformOffset += primOffsetAlign;
currentDstOffset += primOffsetAlign;
transformData.region = {
transformData.GetReadPosition(),
currentDstOffset,
transformData.size,
};
inputBuffersData.push_back(transformData);
currentDstOffset += AlignUp(transformData.size, transformData.alignment);
}
metadata->geometryData.push_back(geoData);
break;
}
case VK_GEOMETRY_TYPE_AABBS_KHR:
{
const VkAccelerationStructureGeometryAabbsDataKHR &aabbInfo = geometry.geometry.aabbs;
// Find the associated VkBuffer
BufferData data = GetDeviceAddressData(aabbInfo.data.deviceAddress);
if(!data)
{
RDCERR("Unable to find VkBuffer for AABB data at 0x%llx", aabbInfo.data.deviceAddress);
continue;
}
data.size = rangeInfo.primitiveCount * sizeof(VkAabbPositionsKHR);
data.SetReadPosition(rangeInfo.primitiveOffset);
// Get the alignment
VkMemoryRequirements mrq = {};
ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), data.buf, &mrq);
// Insert copy commands
data.region = {
data.GetReadPosition(),
currentDstOffset,
data.size,
};
// Store the metadata
VkAccelerationStructureInfo::GeometryData geoData;
geoData.geometryType = geometry.geometryType;
geoData.flags = geometry.flags;
geoData.memOffset = currentDstOffset;
geoData.aabbs.stride = aabbInfo.stride;
geoData.buildRangeInfo = rangeInfo;
geoData.buildRangeInfo.primitiveOffset = 0;
metadata->geometryData.push_back(geoData);
currentDstOffset += AlignUp(data.size, mrq.alignment);
inputBuffersData.push_back(data);
break;
}
case VK_GEOMETRY_TYPE_INSTANCES_KHR:
{
const VkAccelerationStructureGeometryInstancesDataKHR &instanceInfo =
geometry.geometry.instances;
if(instanceInfo.arrayOfPointers)
RETURN_ERROR_RESULT(ResultCode::InternalError,
"AS instance build arrayOfPointers unsupported");
// Find the associated VkBuffer
BufferData data = GetDeviceAddressData(instanceInfo.data.deviceAddress);
if(!data)
{
RDCERR("Unable to find VkBuffer for instance data at 0x%llx",
instanceInfo.data.deviceAddress);
continue;
}
data.size = rangeInfo.primitiveCount * sizeof(VkAccelerationStructureInstanceKHR);
data.SetReadPosition(rangeInfo.primitiveOffset);
// Get the alignment
VkMemoryRequirements mrq = {};
ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), data.buf, &mrq);
// Insert copy commands
data.region = {
data.GetReadPosition(),
currentDstOffset,
data.size,
};
// Store the metadata
VkAccelerationStructureInfo::GeometryData geoData;
geoData.geometryType = geometry.geometryType;
geoData.flags = geometry.flags;
geoData.memOffset = currentDstOffset;
geoData.buildRangeInfo = rangeInfo;
geoData.buildRangeInfo.primitiveOffset = 0;
metadata->geometryData.push_back(geoData);
currentDstOffset += AlignUp(data.size, mrq.alignment);
inputBuffersData.push_back(data);
break;
}
default: RDCERR("Unhandled geometry type: %d", geometry.geometryType); continue;
}
}
bool skipBarrier = false;
if(currentDstOffset == 0)
{
// Rather than deal with empty buffers, for empty ASes just create a min-sized one
const VkDeviceSize nonCoherentAtomSize = m_pDriver->GetDeviceProps().limits.nonCoherentAtomSize;
currentDstOffset = nonCoherentAtomSize;
skipBarrier = true;
}
// Allocate the required memory block
metadata->readbackMem = CreateTempReadBackBuffer(device, currentDstOffset);
if(metadata->readbackMem.UnwrappedMemory() == VK_NULL_HANDLE)
{
RDCERR("Unable to allocate AS input buffer readback memory (size: %u bytes)", currentDstOffset);
return {};
}
metadata->memSize = currentDstOffset;
// Make sure nothing writes to our source buffers before we finish copying them
if(!skipBarrier)
{
// Queue the copying
for(const BufferData &bufData : inputBuffersData)
ObjDisp(device)->CmdCopyBuffer(Unwrap(commandBuffer), bufData.buf,
metadata->readbackMem.UnwrappedBuffer(), 1, &bufData.region);
VkMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_NONE,
};
ObjDisp(device)->CmdPipelineBarrier(Unwrap(commandBuffer), VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 1, &barrier, 0, VK_NULL_HANDLE, 0, VK_NULL_HANDLE);
}
return {};
}
void VulkanAccelerationStructureManager::CopyAccelerationStructure(
VkCommandBuffer commandBuffer, const VkCopyAccelerationStructureInfoKHR &pInfo)
{
VkResourceRecord *srcRecord = GetRecord(pInfo.src);
RDCASSERT(srcRecord->accelerationStructureInfo != NULL);
// Delete any previous data associated with AS
VkResourceRecord *dstRecord = GetRecord(pInfo.dst);
VkAccelerationStructureInfo *info = dstRecord->accelerationStructureInfo;
if(!info->geometryData.empty())
DeletePreviousInfo(commandBuffer, info);
// Rather than copy the backing mem, we can just increase the ref count. If there is an update
// build to the AS then the ref will be replaced in the record, so there's no risk of aliasing.
// The copy mode is irrelevant as it doesn't affect the rebuild
dstRecord->accelerationStructureInfo = srcRecord->accelerationStructureInfo;
dstRecord->accelerationStructureInfo->AddRef();
}
uint64_t VulkanAccelerationStructureManager::GetSize_InitialState(ResourceId id,
const VkInitialContents &initial)
{
const uint64_t infoSize = initial.accelerationStructureInfo->GetSerialisedSize();
const uint64_t serialisedASSize =
(sizeof(uint64_t) * 2) + initial.mem.size + WriteSerialiser::GetChunkAlignment();
return 128ULL + infoSize + serialisedASSize;
}
template <typename SerialiserType>
bool VulkanAccelerationStructureManager::Serialise(SerialiserType &ser, ResourceId id,
const VkInitialContents *initial,
CaptureState state)
{
VkDevice d = !IsStructuredExporting(state) ? m_pDriver->GetDev() : VK_NULL_HANDLE;
VkResult vkr = VK_SUCCESS;
VkAccelerationStructureInfo *asInfo =
initial ? initial->accelerationStructureInfo : new VkAccelerationStructureInfo();
RDCASSERT(asInfo);
byte *contents = NULL;
const VkDeviceSize nonCoherentAtomSize = m_pDriver->GetDeviceProps().limits.nonCoherentAtomSize;
Allocation uploadMemory;
SERIALISE_ELEMENT(*asInfo).Hidden();
if(ser.IsWriting())
{
// The input buffers have already been copied into readable memory, so they just need
// mapping and serialising
contents = (byte *)asInfo->readbackMem.Map();
}
else if(IsReplayMode(state) && !ser.IsErrored())
{
uploadMemory = CreateTempReplayBuffer(MemoryType::Upload, asInfo->memSize, 0);
if(uploadMemory.memAlloc.mem == VK_NULL_HANDLE)
{
RDCERR("Failed to allocate AS build data upload buffer");
return false;
}
vkr = ObjDisp(d)->MapMemory(
Unwrap(d), Unwrap(uploadMemory.memAlloc.mem), uploadMemory.memAlloc.offs,
AlignUp(asInfo->memSize, nonCoherentAtomSize), 0, (void **)&contents);
CHECK_VKR(m_pDriver, vkr);
if(!contents)
{
RDCERR("Manually reporting failed memory map");
CHECK_VKR(m_pDriver, VK_ERROR_MEMORY_MAP_FAILED);
return false;
}
if(vkr != VK_SUCCESS)
return false;
}
ser.Serialise("AS Input"_lit, contents, asInfo->memSize, SerialiserFlags::NoFlags).Hidden();
if(ser.IsWriting())
{
asInfo->readbackMem.Unmap();
}
else
{
if(!IsStructuredExporting(state) && uploadMemory.memAlloc.mem != VK_NULL_HANDLE)
{
// first ensure we flush the writes from the cpu to gpu memory
const VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, //
NULL,
Unwrap(uploadMemory.memAlloc.mem),
uploadMemory.memAlloc.offs,
AlignUp(asInfo->memSize, nonCoherentAtomSize),
};
vkr = ObjDisp(d)->FlushMappedMemoryRanges(Unwrap(d), 1, &range);
CHECK_VKR(m_pDriver, vkr);
ObjDisp(d)->UnmapMemory(Unwrap(d), Unwrap(uploadMemory.memAlloc.mem));
asInfo->uploadAlloc = uploadMemory.memAlloc;
asInfo->uploadBuf = uploadMemory.buf;
}
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayMode(state))
{
VkInitialContents initialContents;
initialContents.type = eResAccelerationStructureKHR;
initialContents.accelerationStructureInfo = asInfo;
m_pDriver->GetResourceManager()->SetInitialContents(id, initialContents);
}
else
{
asInfo->Release();
}
}
return true;
}
template bool VulkanAccelerationStructureManager::Serialise(WriteSerialiser &ser, ResourceId id,
const VkInitialContents *initial,
CaptureState state);
template bool VulkanAccelerationStructureManager::Serialise(ReadSerialiser &ser, ResourceId id,
const VkInitialContents *initial,
CaptureState state);
void VulkanAccelerationStructureManager::Apply(ResourceId id, VkInitialContents &initial)
{
const VkAccelerationStructureKHR wrappedAS =
m_pDriver->GetResourceManager()->GetCurrentHandle<VkAccelerationStructureKHR>(id);
VkAccelerationStructureInfo *asInfo = initial.accelerationStructureInfo;
RDCASSERT(asInfo);
const VkDevice d = m_pDriver->GetDev();
VkCommandBuffer cmd = m_pDriver->GetInitStateCmd();
if(cmd == VK_NULL_HANDLE)
{
RDCERR("Couldn't acquire command buffer");
return;
}
// If our 'base' AS has not been created yet, build it now
if(asInfo->replayAS == VK_NULL_HANDLE)
{
rdcarray<VkAccelerationStructureBuildRangeInfoKHR> buildRangeInfos = asInfo->getBuildRanges();
rdcarray<VkAccelerationStructureGeometryKHR> geometry;
asInfo->convertGeometryData(geometry);
RDCASSERT(!geometry.empty());
RDCASSERT(asInfo->geometryData.size() == geometry.size());
// Copy over the input data from the upload mem to GPU local to increase build speed
Allocation inputGpuMemory =
CreateTempReplayBuffer(MemoryType::GPULocal, asInfo->memSize, 0,
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
VkBufferCopy toGpuCopy = {0, 0, asInfo->memSize};
ObjDisp(d)->CmdCopyBuffer(Unwrap(cmd), asInfo->uploadBuf, inputGpuMemory.buf, 1, &toGpuCopy);
const VkMemoryBarrier copyBarrier = {
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
};
ObjDisp(d)->CmdPipelineBarrier(Unwrap(cmd), VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, 0, 1,
&copyBarrier, 0, NULL, 0, NULL);
// We can clean up the buffers now, the backing mem will be freed after the first Apply()
m_pDriver->AddPendingObjectCleanup(
[d, gpuBuf = inputGpuMemory.buf, uploadBuf = asInfo->uploadBuf]() {
ObjDisp(d)->DestroyBuffer(Unwrap(d), uploadBuf, NULL);
ObjDisp(d)->DestroyBuffer(Unwrap(d), gpuBuf, NULL);
});
if(!FixUpReplayBDAs(asInfo, inputGpuMemory.buf, geometry))
return;
// Allocate the scratch buffer which involves working out how big it should be
VkAccelerationStructureBuildSizesInfoKHR sizeResult = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR,
};
{
const VkAccelerationStructureBuildGeometryInfoKHR sizeInfo = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
NULL,
asInfo->type,
asInfo->flags,
VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,
VK_NULL_HANDLE,
VK_NULL_HANDLE,
(uint32_t)geometry.size(),
geometry.data(),
VK_NULL_HANDLE,
};
rdcarray<uint32_t> counts;
counts.reserve(geometry.size());
for(VkAccelerationStructureBuildRangeInfoKHR numPrims : buildRangeInfos)
counts.push_back(numPrims.primitiveCount);
ObjDisp(d)->GetAccelerationStructureBuildSizesKHR(
Unwrap(d), VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &sizeInfo, counts.data(),
&sizeResult);
}
UpdateScratch(sizeResult.buildScratchSize);
// Create the base AS
const VkBufferCreateInfo gpuBufInfo = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
NULL,
0,
sizeResult.accelerationStructureSize,
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR,
};
VkBuffer asBuf = VK_NULL_HANDLE;
VkResult vkr = m_pDriver->vkCreateBuffer(d, &gpuBufInfo, NULL, &asBuf);
CHECK_VKR(m_pDriver, vkr);
VkMemoryRequirements mrq = {};
ObjDisp(d)->GetBufferMemoryRequirements(Unwrap(d), Unwrap(asBuf), &mrq);
mrq.alignment = AlignUp(mrq.alignment, asBufferAlignment);
const MemoryAllocation asMemory = m_pDriver->AllocateMemoryForResource(
true, mrq, MemoryScope::InitialContents, MemoryType::GPULocal);
vkr = m_pDriver->vkBindBufferMemory(d, asBuf, asMemory.mem, asMemory.offs);
CHECK_VKR(m_pDriver, vkr);
const VkAccelerationStructureCreateInfoKHR asCreateInfo = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
NULL,
0,
asBuf,
0,
sizeResult.accelerationStructureSize,
asInfo->type,
0x0,
};
m_pDriver->vkCreateAccelerationStructureKHR(d, &asCreateInfo, NULL, &asInfo->replayAS);
// Build the AS
const VkAccelerationStructureBuildGeometryInfoKHR asGeomInfo = {
VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
NULL,
asInfo->type,
asInfo->flags,
VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,
VK_NULL_HANDLE,
Unwrap(asInfo->replayAS),
(uint32_t)geometry.size(),
geometry.data(),
NULL,
scratchAddressUnion,
};
const VkAccelerationStructureBuildRangeInfoKHR *pBuildInfo = buildRangeInfos.data();
ObjDisp(d)->CmdBuildAccelerationStructuresKHR(Unwrap(cmd), 1, &asGeomInfo, &pBuildInfo);
// Make sure the AS builds are serialised as the scratch mem is shared
const VkMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
NULL,
VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
};
ObjDisp(d)->CmdPipelineBarrier(
Unwrap(cmd), VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, 0, 1, &barrier, 0, NULL, 0, NULL);
}
// Copy the base AS to the captured one to reset it
const VkCopyAccelerationStructureInfoKHR asCopyInfo = {
VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_INFO_KHR,
NULL,
Unwrap(asInfo->replayAS),
Unwrap(wrappedAS),
VK_COPY_ACCELERATION_STRUCTURE_MODE_CLONE_KHR,
};
ObjDisp(d)->CmdCopyAccelerationStructureKHR(Unwrap(cmd), &asCopyInfo);
if(Vulkan_Debug_SingleSubmitFlushing())
{
m_pDriver->CloseInitStateCmd();
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
}
}
GPUBuffer VulkanAccelerationStructureManager::CreateTempReadBackBuffer(VkDevice device,
VkDeviceSize size)
{
GPUBuffer result;
result.Create(m_pDriver, device, size, 1,
GPUBuffer::eGPUBufferReadback | GPUBuffer::eGPUBufferAddressable);
return result;
}
VulkanAccelerationStructureManager::Allocation VulkanAccelerationStructureManager::CreateTempReplayBuffer(
MemoryType memType, VkDeviceSize size, VkDeviceSize alignment, VkBufferUsageFlags extraUsageFlags)
{
const VkDevice d = m_pDriver->GetDev();
const VkBufferCreateInfo bufInfo = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
NULL,
0,
size,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | extraUsageFlags,
};
Allocation result;
VkResult vkr = ObjDisp(d)->CreateBuffer(Unwrap(d), &bufInfo, NULL, &result.buf);
CHECK_VKR(m_pDriver, vkr);
VkMemoryRequirements mrq = {};
ObjDisp(d)->GetBufferMemoryRequirements(Unwrap(d), result.buf, &mrq);
mrq.alignment = RDCMAX(mrq.alignment, alignment);
result.memAlloc = m_pDriver->AllocateMemoryForResource(
true, mrq, MemoryScope::InitialContentsFirstApplyOnly, memType);
if(result.memAlloc.mem == VK_NULL_HANDLE)
return {};
vkr = ObjDisp(d)->BindBufferMemory(Unwrap(d), result.buf, Unwrap(result.memAlloc.mem),
result.memAlloc.offs);
CHECK_VKR(m_pDriver, vkr);
return result;
}
bool VulkanAccelerationStructureManager::FixUpReplayBDAs(
VkAccelerationStructureInfo *asInfo, VkBuffer buf,
rdcarray<VkAccelerationStructureGeometryKHR> &geoms)
{
RDCASSERT(asInfo);
RDCASSERT(asInfo->geometryData.size() == geoms.size());
const VkDevice d = m_pDriver->GetDev();
const VkBufferDeviceAddressInfo addrInfo = {VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, NULL,
buf};
const VkDeviceAddress bufAddr = ObjDisp(d)->GetBufferDeviceAddressKHR(Unwrap(d), &addrInfo);
for(size_t i = 0; i < geoms.size(); ++i)
{
VkAccelerationStructureGeometryKHR &geom = geoms[i];
switch(geom.geometryType)
{
case VK_GEOMETRY_TYPE_TRIANGLES_KHR:
{
VkAccelerationStructureGeometryTrianglesDataKHR &tri = geom.geometry.triangles;
tri.vertexData.deviceAddress += bufAddr;
if(tri.indexType != VK_INDEX_TYPE_NONE_KHR)
tri.indexData.deviceAddress += bufAddr;
if(tri.transformData.deviceAddress != ~0ULL)
tri.transformData.deviceAddress += bufAddr;
else
tri.transformData.deviceAddress = 0x0;
break;
}
case VK_GEOMETRY_TYPE_AABBS_KHR:
{
geom.geometry.aabbs.data.deviceAddress += bufAddr;
break;
}
case VK_GEOMETRY_TYPE_INSTANCES_KHR:
{
geom.geometry.instances.data.deviceAddress += bufAddr;
break;
}
default: RDCERR("Unhandled geometry type: %d", geom.geometryType); return false;
}
}
return true;
}
void VulkanAccelerationStructureManager::UpdateScratch(VkDeviceSize requiredSize)
{
// We serialise the AS and OMM builds, so reuse the existing scratch
if(requiredSize > scratch.memAlloc.size || scratch.memAlloc.mem == VK_NULL_HANDLE)
{
const VkDevice d = m_pDriver->GetDev();
const VkPhysicalDevice physDev = m_pDriver->GetPhysDev();
VkPhysicalDeviceAccelerationStructurePropertiesKHR asProps = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR,
};
VkPhysicalDeviceProperties2 asPropsBase = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
&asProps,
};
ObjDisp(physDev)->GetPhysicalDeviceProperties2(Unwrap(physDev), &asPropsBase);
scratch = CreateTempReplayBuffer(MemoryType::GPULocal, requiredSize,
asProps.minAccelerationStructureScratchOffsetAlignment,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
if(scratch.memAlloc.mem == VK_NULL_HANDLE)
{
RDCERR("Failed to allocate AS build scratch buffer");
return;
}
const VkBufferDeviceAddressInfo scratchAddressInfo = {
VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
NULL,
scratch.buf,
};
scratchAddressUnion.deviceAddress =
ObjDisp(d)->GetBufferDeviceAddressKHR(Unwrap(d), &scratchAddressInfo);
// We do not need the buffer object, only the mem address
m_pDriver->AddPendingObjectCleanup(
[d, buf = scratch.buf]() { ObjDisp(d)->DestroyBuffer(Unwrap(d), buf, NULL); });
}
}
VulkanAccelerationStructureManager::RecordAndOffset VulkanAccelerationStructureManager::GetDeviceAddressData(
VkDeviceAddress address) const
{
RecordAndOffset result;
ResourceId id;
m_pDriver->GetResIDFromAddr(address, id, result.offset);
// No match
if(id == ResourceId())
return {};
// Convert the ID to a resource record
result.record = m_pDriver->GetResourceManager()->GetResourceRecord(id);
RDCASSERTMSG("Unable to find record", result.record, id);
if(!result.record)
return {};
result.address = address - result.offset;
return result;
}
template <typename T>
void VulkanAccelerationStructureManager::DeletePreviousInfo(VkCommandBuffer commandBuffer, T *info)
{
VkResourceRecord *cmdRecord = GetRecord(commandBuffer);
cmdRecord->cmdInfo->pendingSubmissionCompleteCallbacks->callbacks.push_back(
[info]() { info->Release(); });
}
// OMM suport todo
template void VulkanAccelerationStructureManager::DeletePreviousInfo(VkCommandBuffer commandBuffer,
VkAccelerationStructureInfo *info);