Switch to GPU side query result replacement

This commit is contained in:
Cam Mannett
2024-10-03 15:51:51 +01:00
committed by Baldur Karlsson
parent 5be2156bbe
commit a68c076341
6 changed files with 125 additions and 167 deletions
@@ -75,7 +75,7 @@ VulkanAccelerationStructureManager::VulkanAccelerationStructureManager(WrappedVu
RDResult VulkanAccelerationStructureManager::CopyInputBuffers(
VkCommandBuffer commandBuffer, const VkAccelerationStructureBuildGeometryInfoKHR &info,
const VkAccelerationStructureBuildRangeInfoKHR *buildRange, CaptureState state)
const VkAccelerationStructureBuildRangeInfoKHR *buildRange)
{
VkResourceRecord *cmdRecord = GetRecord(commandBuffer);
RDCASSERT(cmdRecord);
@@ -87,7 +87,7 @@ RDResult VulkanAccelerationStructureManager::CopyInputBuffers(
VkAccelerationStructureInfo *metadata = asRecord->accelerationStructureInfo;
if(!metadata->geometryData.empty())
{
DeletePreviousInfo(commandBuffer, metadata, state);
DeletePreviousInfo(commandBuffer, metadata);
metadata = asRecord->accelerationStructureInfo = new VkAccelerationStructureInfo();
}
@@ -360,8 +360,8 @@ RDResult VulkanAccelerationStructureManager::CopyInputBuffers(
geometry.geometry.instances;
if(instanceInfo.arrayOfPointers)
return RDResult(ResultCode::InternalError,
"AS instance build arrayOfPointers unsupported");
RETURN_ERROR_RESULT(ResultCode::InternalError,
"AS instance build arrayOfPointers unsupported");
// Find the associated VkBuffer
BufferData data = GetDeviceAddressData(instanceInfo.data.deviceAddress);
@@ -432,7 +432,7 @@ RDResult VulkanAccelerationStructureManager::CopyInputBuffers(
}
void VulkanAccelerationStructureManager::CopyAccelerationStructure(
VkCommandBuffer commandBuffer, const VkCopyAccelerationStructureInfoKHR &pInfo, CaptureState state)
VkCommandBuffer commandBuffer, const VkCopyAccelerationStructureInfoKHR &pInfo)
{
VkResourceRecord *srcRecord = GetRecord(pInfo.src);
RDCASSERT(srcRecord->accelerationStructureInfo != NULL);
@@ -441,7 +441,7 @@ void VulkanAccelerationStructureManager::CopyAccelerationStructure(
VkResourceRecord *dstRecord = GetRecord(pInfo.dst);
VkAccelerationStructureInfo *info = dstRecord->accelerationStructureInfo;
if(!info->geometryData.empty())
DeletePreviousInfo(commandBuffer, info, state);
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.
@@ -859,8 +859,7 @@ VulkanAccelerationStructureManager::RecordAndOffset VulkanAccelerationStructureM
}
template <typename T>
void VulkanAccelerationStructureManager::DeletePreviousInfo(VkCommandBuffer commandBuffer, T *info,
CaptureState state)
void VulkanAccelerationStructureManager::DeletePreviousInfo(VkCommandBuffer commandBuffer, T *info)
{
VkResourceRecord *cmdRecord = GetRecord(commandBuffer);
cmdRecord->cmdInfo->pendingSubmissionCompleteCallbacks->callbacks.push_back(
@@ -869,8 +868,7 @@ void VulkanAccelerationStructureManager::DeletePreviousInfo(VkCommandBuffer comm
// OMM suport todo
template void VulkanAccelerationStructureManager::DeletePreviousInfo(VkCommandBuffer commandBuffer,
VkAccelerationStructureInfo *info,
CaptureState state);
VkAccelerationStructureInfo *info);
VkDeviceSize VulkanAccelerationStructureManager::SerialisedASSize(VkAccelerationStructureKHR unwrappedAs)
{
@@ -107,14 +107,13 @@ public:
// are copied.
RDResult CopyInputBuffers(VkCommandBuffer commandBuffer,
const VkAccelerationStructureBuildGeometryInfoKHR &info,
const VkAccelerationStructureBuildRangeInfoKHR *buildRange,
CaptureState state);
const VkAccelerationStructureBuildRangeInfoKHR *buildRange);
// Copies the metadata from src to dst, the input buffers are identical so don't need to be
// duplicated. Compaction is ignored but the copy is still performed so the dst handle is valid
// on replay
void CopyAccelerationStructure(VkCommandBuffer commandBuffer,
const VkCopyAccelerationStructureInfoKHR &pInfo, CaptureState state);
const VkCopyAccelerationStructureInfoKHR &pInfo);
// Called when the initial state is prepared. Any TLAS and BLAS data is copied into temporary
// buffers and the handles for that memory and the buffers is stored in the init state
@@ -135,7 +134,7 @@ private:
RecordAndOffset GetDeviceAddressData(VkDeviceAddress address) const;
template <typename T>
void DeletePreviousInfo(VkCommandBuffer commandBuffer, T *info, CaptureState state);
void DeletePreviousInfo(VkCommandBuffer commandBuffer, T *info);
VkDeviceSize SerialisedASSize(VkAccelerationStructureKHR unwrappedAs);
+7 -95
View File
@@ -3948,105 +3948,17 @@ void ImgRefs::Split(bool splitAspects, bool splitLevels, bool splitLayers)
areLayersSplit = newSplitLayerCount > 1;
}
void QueryPoolInfo::Add(uint32_t firstQuery, rdcarray<uint64_t> values)
QueryPoolInfo::QueryPoolInfo(WrappedVulkan *driver, VkDevice device,
const VkQueryPoolCreateInfo *pCreateInfo)
{
Reset(firstQuery, (uint32_t)values.size());
m_Entries.reserve(m_Entries.size() + values.size());
for(uint64_t value : values)
m_Entries.emplace_back(firstQuery++, value);
std::sort(m_Entries.begin(), m_Entries.end());
m_Buffer.Create(driver, device, pCreateInfo->queryCount * 8, 1, GPUBuffer::eGPUBufferReadback);
m_MappedMem = (byte *)m_Buffer.Map(0, m_Buffer.totalsize);
}
void QueryPoolInfo::Reset(uint32_t firstQuery, uint32_t queryCount)
QueryPoolInfo::~QueryPoolInfo()
{
m_Entries.removeIf([&](const auto &entry) {
return (entry.index >= firstQuery) && (entry.index < (firstQuery + queryCount));
});
}
void QueryPoolInfo::Replace(uint32_t firstQuery, uint32_t queryCount, void *pData,
VkDeviceSize stride, VkQueryResultFlags flags) const
{
const auto writeEntry = [&](Entry queryPoolInfoEntry) {
const size_t num_bytes = (flags & VK_QUERY_RESULT_64_BIT) ? 8 : 4;
byte *pStart = (byte *)pData + (queryPoolInfoEntry.index * stride);
memcpy(pStart, &queryPoolInfoEntry.value, num_bytes);
};
Replace(firstQuery, queryCount, writeEntry);
}
void QueryPoolInfo::Replace(uint32_t firstQuery, uint32_t queryCount,
const std::function<void(uint32_t, rdcarray<uint64_t>)> &writeEntry) const
{
rdcarray<Entry> entries;
entries.reserve(queryCount);
// Swap out any AS compaction sizes with the replacements
Replace(firstQuery, queryCount, [&](Entry entry) { entries.push_back(entry); });
std::sort(entries.begin(), entries.end());
// Now batch into contiguous ranges and dispatch
for(size_t i = 0; i < entries.size();)
{
uint32_t queryIndex = entries[i].index;
rdcarray<uint64_t> batch;
while(queryIndex == entries[++i].index)
{
batch.push_back(entries[i].value);
++queryIndex;
}
writeEntry(queryIndex, std::move(batch));
}
}
bool QueryPoolInfo::HasReplacementEntries(uint32_t firstQuery, uint32_t queryCount) const
{
uint32_t start, end;
rdctie(start, end) = GetIntersection(firstQuery, queryCount);
return start <= end;
}
rdcpair<uint32_t, uint32_t> QueryPoolInfo::GetIntersection(uint32_t firstQuery,
uint32_t queryCount) const
{
if(m_Entries.empty())
return {1, 0}; // Invalid
const uint32_t start = RDCMAX(firstQuery, m_Entries.front().index);
const uint32_t end = RDCMIN(firstQuery + queryCount - 1, (uint32_t)m_Entries.back().index);
return {start, end};
}
void QueryPoolInfo::Replace(uint32_t firstQuery, uint32_t queryCount,
const std::function<void(Entry)> &writeEntry) const
{
if(!m_Entries.empty())
{
// Find the intersection of the two query ranges
uint32_t start, end;
rdctie(start, end) = GetIntersection(firstQuery, queryCount);
if(end < start)
return;
uint32_t j = 0;
for(uint32_t i = start; i < end; ++i)
{
// The indices are sparse but ordered
while(i != m_Entries[j].index)
{
++j;
}
writeEntry(m_Entries[j]);
}
}
m_Buffer.Unmap();
m_Buffer.Destroy();
}
VkResourceRecord::~VkResourceRecord()
+5 -29
View File
@@ -2236,39 +2236,15 @@ inline FrameRefType MarkMemoryReferenced(std::unordered_map<ResourceId, MemRefs>
return MarkMemoryReferenced(memRefs, mem, offset, size, refType, ComposeFrameRefs);
}
// Used to replace QueryPool results
// Used as an alternative backing store to VkQueryPool to replace query results
class QueryPoolInfo
{
public:
void Add(uint32_t firstQuery, rdcarray<uint64_t> values);
QueryPoolInfo(WrappedVulkan *driver, VkDevice device, const VkQueryPoolCreateInfo *pCreateInfo);
~QueryPoolInfo();
void Reset(uint32_t firstQuery, uint32_t queryCount);
void Replace(uint32_t firstQuery, uint32_t queryCount, void *pData, VkDeviceSize stride,
VkQueryResultFlags flags) const;
// Calls writeEntry with matching contiguous entries, buffered into an array.
void Replace(uint32_t firstQuery, uint32_t queryCount,
const std::function<void(uint32_t, rdcarray<uint64_t>)> &writeEntry) const;
bool HasReplacementEntries(uint32_t firstQuery, uint32_t queryCount) const;
private:
struct Entry
{
Entry(uint32_t i, uint64_t v) : index(i), value(v) {}
bool operator<(Entry other) const { return index < other.index; }
uint32_t index;
uint64_t value;
};
rdcpair<uint32_t, uint32_t> GetIntersection(uint32_t firstQuery, uint32_t queryCount) const;
void Replace(uint32_t firstQuery, uint32_t queryCount,
const std::function<void(Entry)> &writeEntry) const;
rdcarray<Entry> m_Entries;
GPUBuffer m_Buffer;
byte *m_MappedMem;
};
struct DescUpdateTemplate;
@@ -4539,8 +4539,9 @@ void WrappedVulkan::vkCmdCopyQueryPoolResults(VkCommandBuffer commandBuffer, VkQ
record->MarkResourceFrameReferenced(GetResID(queryPool), eFrameRef_Read);
const bool is64bit = (flags & VK_QUERY_RESULT_64_BIT) > 0;
VkDeviceSize size = (queryCount - 1) * destStride + 4;
if(flags & VK_QUERY_RESULT_64_BIT)
if(is64bit)
{
size += 4;
}
@@ -4548,34 +4549,57 @@ void WrappedVulkan::vkCmdCopyQueryPoolResults(VkCommandBuffer commandBuffer, VkQ
eFrameRef_PartialWrite);
const QueryPoolInfo *qpInfo = GetRecord(queryPool)->queryPoolInfo;
if(qpInfo->HasReplacementEntries(firstQuery, queryCount))
if(qpInfo)
{
// We want to record these commands into the capture so they are replayed
VkMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
};
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 1, &barrier, 0, VK_NULL_HANDLE, 0,
VK_NULL_HANDLE);
ObjDisp(commandBuffer)
->CmdPipelineBarrier(Unwrap(commandBuffer), VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 1, &barrier, 0, VK_NULL_HANDLE,
0, VK_NULL_HANDLE);
qpInfo->Replace(
firstQuery, queryCount, [&](uint32_t queryIndexStart, rdcarray<uint64_t> results) {
const size_t size = (size_t)(results.size() * destStride);
RDCASSERT(size < (1 << 16));
const bool hasAvailability = (flags & VK_QUERY_RESULT_WITH_AVAILABILITY_BIT) > 0;
const VkDeviceSize resultSize = is64bit ? sizeof(uint64_t) : sizeof(uint32_t);
const size_t resultSize = (flags & VK_QUERY_RESULT_64_BIT) ? 8 : 4;
byte *tmp = new byte[size];
size_t i = 0;
for(byte *ptr = tmp; ptr < (ptr + size); ptr += destStride)
memcpy(ptr, &results[i++], resultSize);
// If the stride matches the source buffer, then we can copy everything in a single region
if(is64bit && !hasAvailability && destStride == resultSize)
{
const VkBufferCopy region = {
firstQuery * resultSize,
destOffset,
queryCount * resultSize,
};
ObjDisp(commandBuffer)
->CmdCopyBuffer(Unwrap(commandBuffer), Unwrap(qpInfo->m_Buffer.buf), Unwrap(destBuffer),
1, &region);
}
else
{
// Copy each result into the destination, converting if required
rdcarray<VkBufferCopy> regions;
regions.reserve(queryCount);
for(size_t i = 0; i < queryCount; ++i)
regions.push_back(
{(firstQuery + i) * sizeof(uint64_t), destOffset + (i * destStride), resultSize});
vkCmdUpdateBuffer(commandBuffer, destBuffer, destOffset + (queryIndexStart * destStride),
size, (const uint32_t *)tmp);
delete[] tmp;
});
ObjDisp(commandBuffer)
->CmdCopyBuffer(Unwrap(commandBuffer), Unwrap(qpInfo->m_Buffer.buf), Unwrap(destBuffer),
(uint32_t)regions.size(), regions.data());
}
if(hasAvailability)
{
const uint64_t availability = 1;
for(size_t i = 0; i < queryCount; ++i)
ObjDisp(commandBuffer)
->CmdUpdateBuffer(Unwrap(commandBuffer), Unwrap(qpInfo->m_Buffer.buf),
destOffset + (queryCount * resultSize) + resultSize, resultSize,
(uint32_t *)&availability);
}
}
}
}
@@ -7906,11 +7930,10 @@ void WrappedVulkan::vkCmdBuildAccelerationStructuresKHR(
record->cmdInfo->accelerationStructures.push_back(GetRecord(geomInfo.dstAccelerationStructure));
const RDResult copyResult = GetAccelerationStructureManager()->CopyInputBuffers(
commandBuffer, geomInfo, ppBuildRangeInfos[i], m_State);
commandBuffer, geomInfo, ppBuildRangeInfos[i]);
if(copyResult != ResultCode::Succeeded)
{
m_LastCaptureError = copyResult;
RDCERR("%s", copyResult.message.c_str());
m_CaptureFailure = true;
}
}
@@ -7964,6 +7987,8 @@ void WrappedVulkan::vkCmdCopyAccelerationStructureKHR(VkCommandBuffer commandBuf
// Add to the command buffer metadata, so we can know when it has been submitted
record->cmdInfo->accelerationStructures.push_back(GetRecord(pInfo->dst));
GetAccelerationStructureManager()->CopyAccelerationStructure(commandBuffer, *pInfo);
}
}
@@ -8051,16 +8076,24 @@ void WrappedVulkan::vkCmdWriteAccelerationStructuresPropertiesKHR(
// The compacted size can vary between capture and replay, so to ensure we always have enough
// memory we return the full AS size
if(queryType == VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR)
QueryPoolInfo *qpInfo = GetRecord(queryPool)->queryPoolInfo;
if(qpInfo)
{
auto &qpInfo = GetRecord(queryPool)->queryPoolInfo;
rdcarray<uint64_t> sizes;
sizes.reserve(accelerationStructureCount);
for(uint32_t i = 0; i < accelerationStructureCount; ++i)
sizes.push_back(GetRecord(pAccelerationStructures[i])->memSize);
qpInfo->Add(firstQuery, std::move(sizes));
constexpr size_t maxTransferrableBytes = 65536;
const size_t totalBytes = sizes.size() * sizeof(uint64_t);
for(size_t i = 0; i < totalBytes; i += maxTransferrableBytes)
{
const VkDeviceSize numBytes = RDCMIN(maxTransferrableBytes, totalBytes);
const VkDeviceSize startOffset = (firstQuery * sizeof(uint64_t)) + i;
ObjDisp(commandBuffer)
->CmdUpdateBuffer(Unwrap(commandBuffer), Unwrap(qpInfo->m_Buffer.buf), startOffset,
numBytes, (uint32_t *)((byte *)sizes.data() + i));
}
}
ObjDisp(commandBuffer)
@@ -1649,7 +1649,15 @@ VkResult WrappedVulkan::vkCreateQueryPool(VkDevice device, const VkQueryPoolCrea
}
VkResourceRecord *record = GetResourceManager()->AddResourceRecord(*pQueryPool);
record->queryPoolInfo = new QueryPoolInfo();
// We swap out the queried compacted AS size for the uncompacted size as they can differ
// between capture and replay
if(pCreateInfo->queryType == VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR)
{
record->queryPoolInfo = new QueryPoolInfo(this, device, pCreateInfo);
GetResourceManager()->SetInternalResource(GetResID(record->queryPoolInfo->m_Buffer.buf));
GetResourceManager()->SetInternalResource(GetResID(record->queryPoolInfo->m_Buffer.mem));
}
record->AddChunk(chunk);
}
@@ -1670,7 +1678,41 @@ VkResult WrappedVulkan::vkGetQueryPoolResults(VkDevice device, VkQueryPool query
VkResult result = ObjDisp(device)->GetQueryPoolResults(
Unwrap(device), Unwrap(queryPool), firstQuery, queryCount, dataSize, pData, stride, flags);
GetRecord(queryPool)->queryPoolInfo->Replace(firstQuery, queryCount, pData, stride, flags);
const QueryPoolInfo *qpInfo = GetRecord(queryPool)->queryPoolInfo;
if(qpInfo)
{
VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
NULL,
qpInfo->m_Buffer.mem,
firstQuery * sizeof(uint64_t),
queryCount * sizeof(uint64_t),
};
vkInvalidateMappedMemoryRanges(device, 1, &range);
const bool is64bit = (flags & VK_QUERY_RESULT_64_BIT) > 0;
const bool hasAvailability = (flags & VK_QUERY_RESULT_WITH_AVAILABILITY_BIT) > 0;
const size_t resultSize = is64bit ? sizeof(uint64_t) : sizeof(uint32_t);
// If the stride matches the source buffer, then we can copy everything in a single block
if(is64bit && !hasAvailability && stride == resultSize)
{
memcpy(pData, qpInfo->m_MappedMem + (firstQuery * resultSize), queryCount * resultSize);
}
else
{
for(size_t i = 0; i < queryCount; ++i)
memcpy((byte *)pData + (i * stride), qpInfo->m_MappedMem + ((firstQuery + i) * resultSize),
resultSize);
}
if(hasAvailability)
{
const uint64_t availability = 1;
for(size_t i = 0; i < queryCount; ++i)
memcpy((byte *)pData + (queryCount * stride) + resultSize, &availability, resultSize);
}
}
return result;
}
@@ -1705,8 +1747,6 @@ void WrappedVulkan::vkResetQueryPool(VkDevice device, VkQueryPool queryPool, uin
SERIALISE_TIME_CALL(
ObjDisp(device)->ResetQueryPool(Unwrap(device), Unwrap(queryPool), firstQuery, queryCount));
GetRecord(queryPool)->queryPoolInfo->Reset(firstQuery, queryCount);
if(IsActiveCapturing(m_State))
{
CACHE_THREAD_SERIALISER();