Snapshot sparse tables into resource initial states, and apply on replay

This commit is contained in:
baldurk
2021-03-08 16:38:52 +00:00
parent da4706d423
commit 8ff59fea79
2 changed files with 487 additions and 19 deletions
+445 -14
View File
@@ -36,6 +36,13 @@
// command buffer that stalls the GPU).
// See INITSTATEBATCH
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, AspectSparseTable &el)
{
SERIALISE_MEMBER(aspectMask);
SERIALISE_MEMBER(table);
}
bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res)
{
ResourceId id = GetResourceManager()->GetID(res);
@@ -72,6 +79,11 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res)
// buffers are only dirty if they are sparse
RDCASSERT(buffer->record->resInfo && buffer->record->resInfo->IsSparse());
VkInitialContents initialContents(type, VkInitialContents::SparseTableOnly);
initialContents.SnapshotPageTable(*buffer->record->resInfo);
GetResourceManager()->SetInitialContents(id, initialContents);
return true;
}
else if(type == eResImage)
@@ -82,11 +94,6 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res)
const ResourceInfo &resInfo = *im->record->resInfo;
const ImageInfo &imageInfo = resInfo.imageInfo;
if(resInfo.IsSparse())
{
// if the image is sparse we have to snapshot the page table
}
LockedImageStateRef state = FindImageState(im->id);
// if the image has no memory bound, nothing is to be fetched
@@ -415,7 +422,15 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res)
GetResourceManager()->ReleaseWrappedResource(arrayIm);
}
GetResourceManager()->SetInitialContents(id, VkInitialContents(type, readbackmem));
VkInitialContents initialContents(type, readbackmem);
// include the sparse page table if it exists
if(resInfo.IsSparse())
{
initialContents.SnapshotPageTable(resInfo);
}
GetResourceManager()->SetInitialContents(id, initialContents);
return true;
}
@@ -526,19 +541,35 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res)
uint64_t WrappedVulkan::GetSize_InitialState(ResourceId id, const VkInitialContents &initial)
{
uint64_t ret = 0;
// account for sparse page tables when present
if(initial.sparseTables)
{
// array count overheads
ret += 128;
for(size_t i = 0; i < initial.sparseTables->size(); i++)
{
ret += sizeof(VkImageAspectFlagBits);
ret += initial.sparseTables->at(i).table.GetSerialiseSize();
}
}
if(initial.type == eResDescriptorSet)
{
// shouldn't have a sparse table here!
RDCASSERTEQUAL(ret, 0);
return 128 + initial.numDescriptors * sizeof(DescriptorSetSlot) + initial.inlineByteSize;
}
else if(initial.type == eResBuffer)
{
// buffers only have initial states when they're sparse
return 0;
return ret;
}
else if(initial.type == eResImage || initial.type == eResDeviceMemory)
{
// the size primarily comes from the buffer, the size of which we conveniently have stored.
return uint64_t(128 + initial.mem.size + WriteSerialiser::GetChunkAlignment());
return ret + uint64_t(128 + initial.mem.size + WriteSerialiser::GetChunkAlignment());
}
RDCERR("Unhandled resource type %s", ToStr(initial.type).c_str());
@@ -558,6 +589,341 @@ static rdcliteral NameOfType(VkResourceType type)
return "VkResource"_lit;
}
SparseBinding::SparseBinding(WrappedVulkan *vk, VkBuffer unwrappedBuffer,
const rdcarray<AspectSparseTable> &tables)
{
sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO;
pNext = NULL;
waitSemaphoreCount = 0;
pWaitSemaphores = NULL;
imageOpaqueBindCount = 0;
pImageOpaqueBinds = NULL;
imageBindCount = 0;
pImageBinds = NULL;
signalSemaphoreCount = 0;
pSignalSemaphores = NULL;
if(tables.empty())
{
RDCERR("Expected a page table initialising buffer sparse bindings");
invalid = true;
return;
}
VkDevice device = vk->GetDev();
VkMemoryRequirements mrq = {0};
ObjDisp(device)->GetBufferMemoryRequirements(Unwrap(device), unwrappedBuffer, &mrq);
const Sparse::PageTable &table = tables[0].table;
if(mrq.alignment != table.getPageByteSize())
{
RDCERR("Captured page table uses page size %llu, but on replay page size is %llu",
table.getPageByteSize(), mrq.alignment);
invalid = true;
return;
}
bufBind.buffer = unwrappedBuffer;
RDCASSERTEQUAL(table.getMipTail().mappings.size(), 1);
const Sparse::PageRangeMapping &mapping = table.getMipTail().mappings[0];
if(mapping.hasSingleMapping())
{
opaqueBinds.resize(1);
opaqueBinds[0].flags = 0;
opaqueBinds[0].resourceOffset = 0;
opaqueBinds[0].memory =
Unwrap(vk->GetResourceManager()->GetLiveHandle<VkDeviceMemory>(mapping.singleMapping.memory));
opaqueBinds[0].memoryOffset = mapping.singleMapping.offset;
opaqueBinds[0].size = table.getMipTail().totalPackedByteSize;
}
else
{
opaqueBinds.resize(mapping.pages.size());
for(size_t i = 0; i < mapping.pages.size(); i++)
{
opaqueBinds[i].flags = 0;
opaqueBinds[i].resourceOffset = i * mrq.alignment;
opaqueBinds[i].memory =
Unwrap(vk->GetResourceManager()->GetLiveHandle<VkDeviceMemory>(mapping.pages[i].memory));
opaqueBinds[i].memoryOffset = mapping.pages[i].offset;
opaqueBinds[i].size = mrq.alignment;
}
}
bufBind.bindCount = (uint32_t)opaqueBinds.size();
bufBind.pBinds = opaqueBinds.data();
bufferBindCount = 1;
pBufferBinds = &bufBind;
}
SparseBinding::SparseBinding(WrappedVulkan *vk, VkImage unwrappedImage,
const rdcarray<AspectSparseTable> &tables)
{
sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO;
pNext = NULL;
waitSemaphoreCount = 0;
pWaitSemaphores = NULL;
bufferBindCount = 0;
pBufferBinds = NULL;
signalSemaphoreCount = 0;
pSignalSemaphores = NULL;
VkDevice device = vk->GetDev();
VkMemoryRequirements mrq = {0};
ObjDisp(device)->GetImageMemoryRequirements(Unwrap(device), unwrappedImage, &mrq);
uint32_t numreqs = 8;
VkSparseImageMemoryRequirements reqs[8];
ObjDisp(device)->GetImageSparseMemoryRequirements(Unwrap(device), unwrappedImage, &numreqs, reqs);
// if we have a different number of aspwects, we can't apply this page table
if(numreqs != tables.size())
{
rdcstr tablesStr, replayStr;
for(size_t i = 0; i < tables.size(); i++)
tablesStr += ToStr((VkImageAspectFlagBits)tables[i].aspectMask) + ", ";
if(tablesStr.size() > 2)
tablesStr.resize(tablesStr.size() - 2);
for(uint32_t i = 0; i < numreqs; i++)
replayStr += ToStr((VkImageAspectFlagBits)reqs[i].formatProperties.aspectMask) + ", ";
if(replayStr.size() > 2)
replayStr.resize(replayStr.size() - 2);
RDCERR("Captured page table has %zu aspects (%s), but on replay we need %u aspects (%s)",
tables.size(), tablesStr.c_str(), numreqs, replayStr.c_str());
invalid = true;
return;
}
for(uint32_t a = 0; a < numreqs; a++)
{
// can't apply if the aspects mismatch
if(tables[a].aspectMask != reqs[a].formatProperties.aspectMask)
{
RDCERR("Captured page table aspect %u is %s, but on replay it is %s", a,
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(),
ToStr((VkImageAspectFlagBits)reqs[a].formatProperties.aspectMask).c_str());
invalid = true;
return;
}
VkImageAspectFlags aspect = reqs[a].formatProperties.aspectMask;
const Sparse::PageTable &table = tables[a].table;
// can't apply if page size changed
if(mrq.alignment != table.getPageByteSize())
{
RDCERR("Captured page table for %s uses page size %llu, but on replay page size is %llu",
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(), table.getPageByteSize(),
mrq.alignment);
invalid = true;
return;
}
Sparse::Coord blockSize = table.getPageTexelSize();
VkExtent3D gran = reqs[a].formatProperties.imageGranularity;
// can't apply if the page texel dimension has changed
if(blockSize.x != RDCMAX(1U, gran.width) || blockSize.y != RDCMAX(1U, gran.height) ||
blockSize.z != RDCMAX(1U, gran.depth))
{
RDCERR("Captured page table for %s uses %ux%ux%u pages, but on replay pages are %ux%ux%u",
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(), blockSize.x, blockSize.y,
blockSize.z, gran.width, gran.height, gran.depth);
invalid = true;
return;
}
const Sparse::MipTail &mipTail = table.getMipTail();
// can't apply if the mip tail is differently shaped/sized
if(mipTail.byteOffset != reqs[a].imageMipTailOffset)
{
RDCERR("Captured mip tail for %s begins at offset %llu, on replay it begins at %llu",
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(), mipTail.byteOffset,
reqs[a].imageMipTailOffset);
invalid = true;
return;
}
if(mipTail.firstMip != RDCMIN(table.getMipCount(), reqs[a].imageMipTailFirstLod))
{
RDCERR("Captured mip tail for %s begins at mip %u, on replay it begins at %u",
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(), mipTail.firstMip,
reqs[a].imageMipTailFirstLod);
invalid = true;
return;
}
if(reqs[a].formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT)
{
if(mipTail.totalPackedByteSize != reqs[a].imageMipTailSize)
{
RDCERR("Captured single mip tail for %s is %llu bytes, on replay it is %llu",
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(),
mipTail.totalPackedByteSize, reqs[a].imageMipTailSize);
invalid = true;
return;
}
}
else
{
if(mipTail.totalPackedByteSize / table.getArraySize() != reqs[a].imageMipTailSize ||
mipTail.byteStride != reqs[a].imageMipTailStride)
{
RDCERR(
"Captured mip tail per slice for %s is %llu bytes with stride %llu, "
"on replay it is %llu bytes with stride %llu",
ToStr((VkImageAspectFlagBits)tables[a].aspectMask).c_str(),
mipTail.totalPackedByteSize / table.getArraySize(), mipTail.byteStride,
reqs[a].imageMipTailSize, reqs[a].imageMipTailStride);
invalid = true;
return;
}
}
{
VkSparseImageMemoryBind bind = {};
bind.subresource.aspectMask = aspect;
if(aspect & VK_IMAGE_ASPECT_METADATA_BIT)
bind.flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT;
for(uint32_t slice = 0; slice < table.getArraySize(); slice++)
{
bind.subresource.arrayLayer = slice;
for(uint32_t mip = 0; mip < table.getMipCount(); mip++)
{
const uint32_t sub = table.calcSubresource(slice, mip);
if(table.isSubresourceInMipTail(sub))
continue;
bind.subresource.mipLevel = mip;
const Sparse::PageRangeMapping &mapping = table.getSubresource(sub);
Sparse::Coord dim = table.calcSubresourcePageDim(sub);
if(mapping.hasSingleMapping())
{
// vulkan allows us to bind more than the subresource size as long as it's a case where
// we
// have less than a page used on the edges.
bind.offset = {};
bind.extent.width = dim.x * blockSize.x;
bind.extent.height = dim.y * blockSize.y;
bind.extent.depth = dim.z * blockSize.z;
bind.memory = Unwrap(vk->GetResourceManager()->GetLiveHandle<VkDeviceMemory>(
mapping.singleMapping.memory));
bind.memoryOffset = mapping.singleMapping.offset;
imgBinds.push_back(bind);
}
else
{
uint32_t page = 0;
// bind each block individually. Slow path :(
bind.extent = {blockSize.x, blockSize.y, blockSize.z};
for(uint32_t z = 0; z < dim.z; z++)
{
bind.offset.z = z * blockSize.z;
for(uint32_t y = 0; y < dim.y; y++)
{
bind.offset.y = y * blockSize.y;
for(uint32_t x = 0; x < dim.x; x++)
{
bind.offset.x = x * blockSize.x;
bind.memory = Unwrap(vk->GetResourceManager()->GetLiveHandle<VkDeviceMemory>(
mapping.pages[page].memory));
bind.memoryOffset = mapping.pages[page].offset;
page++;
imgBinds.push_back(bind);
}
}
}
}
}
}
}
if(mipTail.totalPackedByteSize > 0)
{
VkSparseMemoryBind bind = {};
for(uint32_t slice = 0; slice < mipTail.mappings.size(); slice++)
{
const Sparse::PageRangeMapping &mapping = mipTail.mappings[slice];
// if all slices are in a combined mip tail, byteStride will be 0 and there will only be one
// mapping. Otherwise we look at each mapping individually and remap the resource offset as
// appropriate
bind.resourceOffset = mipTail.byteOffset + mipTail.byteStride * slice;
if(mapping.hasSingleMapping())
{
bind.memory = Unwrap(
vk->GetResourceManager()->GetLiveHandle<VkDeviceMemory>(mapping.singleMapping.memory));
bind.memoryOffset = mapping.singleMapping.offset;
// if stride is 0, we bind the whole mip tail at once. Otherwise only bind the section of
// memory backing it
if(mipTail.byteStride == 0)
bind.size = mipTail.totalPackedByteSize;
else
bind.size = mipTail.totalPackedByteSize / mipTail.mappings.size();
opaqueBinds.push_back(bind);
}
else
{
// bind a block at a time
bind.size = mrq.alignment;
for(size_t i = 0; i < mapping.pages.size(); i++)
{
bind.memory = Unwrap(
vk->GetResourceManager()->GetLiveHandle<VkDeviceMemory>(mapping.pages[i].memory));
bind.memoryOffset = mapping.pages[i].offset;
opaqueBinds.push_back(bind);
bind.resourceOffset += bind.size;
}
}
}
}
}
imgOpaqueBind.image = unwrappedImage;
imgOpaqueBind.bindCount = (uint32_t)opaqueBinds.size();
imgOpaqueBind.pBinds = opaqueBinds.data();
imgBind.image = unwrappedImage;
imgBind.bindCount = (uint32_t)imgBinds.size();
imgBind.pBinds = imgBinds.data();
imageOpaqueBindCount = 1;
pImageOpaqueBinds = &imgOpaqueBind;
imageBindCount = 1;
pImageBinds = &imgBind;
}
template <typename SerialiserType>
bool WrappedVulkan::Serialise_InitialState(SerialiserType &ser, ResourceId id, VkResourceRecord *,
const VkInitialContents *initial)
@@ -892,6 +1258,7 @@ bool WrappedVulkan::Serialise_InitialState(SerialiserType &ser, ResourceId id, V
}
else if(type == eResBuffer)
{
// check for legacy captures
if(ser.IsReading() && ser.VersionLess(0x13))
{
RDCWARN(
@@ -922,19 +1289,51 @@ bool WrappedVulkan::Serialise_InitialState(SerialiserType &ser, ResourceId id, V
return true;
}
rdcarray<AspectSparseTable> sparseTables;
// while writing, fetch sparse table from prepared initial contents
if(ser.IsWriting())
{
sparseTables = *initial->sparseTables;
}
SERIALISE_ELEMENT(sparseTables);
SERIALISE_CHECK_READ_ERRORS();
// while reading, store the bindings in initial contents
if(IsReplayingAndReading())
{
VkInitialContents initialContents(type, VkInitialContents::SparseTableOnly);
WrappedVkRes *res = GetResourceManager()->GetLiveResource(id);
initialContents.sparseBind =
new SparseBinding(this, ToUnwrappedHandle<VkBuffer>(res), sparseTables);
// if something went wrong the sparse binding information is not valid, have to abort
if(initialContents.sparseBind->invalid)
{
delete initialContents.sparseBind;
return false;
}
GetResourceManager()->SetInitialContents(id, initialContents);
}
}
else if(type == eResDeviceMemory || type == eResImage)
{
VkDevice d = !IsStructuredExporting(m_State) ? GetDev() : VK_NULL_HANDLE;
// if we have a blob of data, this contains sparse mapping so re-direct to the sparse
// implementation of this function
SERIALISE_ELEMENT_LOCAL(IsSparse, false);
SERIALISE_ELEMENT_LOCAL(IsSparse, initial->sparseTables != NULL);
rdcarray<AspectSparseTable> sparseTables;
if(IsSparse)
{
if(type == eResImage)
{
// check for legacy captures
if(ser.IsReading() && ser.VersionLess(0x13))
{
RDCWARN(
@@ -981,6 +1380,12 @@ bool WrappedVulkan::Serialise_InitialState(SerialiserType &ser, ResourceId id, V
return true;
}
// while writing, fetch sparse table from prepared initial contents
if(ser.IsWriting())
sparseTables = *initial->sparseTables;
SERIALISE_ELEMENT(sparseTables);
}
else
{
@@ -1097,6 +1502,21 @@ bool WrappedVulkan::Serialise_InitialState(SerialiserType &ser, ResourceId id, V
{
VkInitialContents initialContents(type, uploadMemory);
// if we have sparse page tables, store them here now
if(!sparseTables.empty())
{
WrappedVkRes *res = GetResourceManager()->GetLiveResource(id);
initialContents.sparseBind =
new SparseBinding(this, ToUnwrappedHandle<VkImage>(res), sparseTables);
// if something went wrong the sparse binding information is not valid, have to abort
if(initialContents.sparseBind->invalid)
{
delete initialContents.sparseBind;
return false;
}
}
VulkanCreationInfo::Image &c = m_CreationInfo.m_Image[liveid];
// for non-MSAA images, we're done - we'll do buffer-to-image copies with appropriate
@@ -1335,10 +1755,10 @@ void WrappedVulkan::Create_InitialState(ResourceId id, WrappedVkRes *live, bool)
}
}
std::map<uint32_t, rdcarray<VkImageMemoryBarrier> > GetExtQBarriers(
std::map<uint32_t, rdcarray<VkImageMemoryBarrier>> GetExtQBarriers(
const rdcarray<VkImageMemoryBarrier> &barriers)
{
std::map<uint32_t, rdcarray<VkImageMemoryBarrier> > extQBarriers;
std::map<uint32_t, rdcarray<VkImageMemoryBarrier>> extQBarriers;
for(auto barrierIt = barriers.begin(); barrierIt != barriers.end(); ++barrierIt)
{
@@ -1416,6 +1836,11 @@ void WrappedVulkan::Apply_InitialState(WrappedVkRes *live, const VkInitialConten
}
else if(type == eResBuffer)
{
// we should only get here if we have a sparse page table to apply
RDCASSERT(initial.tag == VkInitialContents::SparseTableOnly, (uint32_t)initial.tag);
if(initial.sparseBind)
ObjDisp(m_Queue)->QueueBindSparse(Unwrap(m_Queue), 1, initial.sparseBind, VK_NULL_HANDLE);
}
else if(type == eResImage)
{
@@ -1441,7 +1866,13 @@ void WrappedVulkan::Apply_InitialState(WrappedVkRes *live, const VkInitialConten
const ImageInfo &imageInfo = state->GetImageInfo();
initialized = IsActiveReplaying(m_State);
if(initialized && boundMemory != ResourceId())
// apply sparse page table mappings and skip memory-bound optimisations
if(initial.sparseBind)
{
ObjDisp(m_Queue)->QueueBindSparse(Unwrap(m_Queue), 1, initial.sparseBind, VK_NULL_HANDLE);
initialized = false;
}
else if(initialized && boundMemory != ResourceId())
{
ResourceId origMem = GetResourceManager()->GetOriginalID(boundMemory);
if(origMem != ResourceId())
+42 -5
View File
@@ -29,6 +29,23 @@
class WrappedVulkan;
struct SparseBinding : public VkBindSparseInfo
{
SparseBinding(WrappedVulkan *vk, VkBuffer unwrappedBuffer,
const rdcarray<AspectSparseTable> &tables);
SparseBinding(WrappedVulkan *vk, VkImage unwrappedImage, const rdcarray<AspectSparseTable> &tables);
SparseBinding(const SparseBinding &) = delete;
bool invalid = false;
VkSparseBufferMemoryBindInfo bufBind;
VkSparseImageMemoryBindInfo imgBind;
VkSparseImageOpaqueMemoryBindInfo imgOpaqueBind;
rdcarray<VkSparseMemoryBind> opaqueBinds;
rdcarray<VkSparseImageMemoryBind> imgBinds;
};
// this struct is copied around and for that reason we explicitly keep it simple and POD. The
// lifetime of the memory allocated is controlled by the resource manager - when preparing or
// serialising, we explicitly set the initial contents, then when the whole system is done with them
@@ -41,6 +58,7 @@ struct VkInitialContents
ClearColorImage = 1,
ClearDepthStencilImage,
DescriptorSet,
SparseTableOnly,
};
VkInitialContents()
@@ -64,6 +82,20 @@ struct VkInitialContents
mem = m;
}
void SnapshotPageTable(const ResourceInfo &resInfo)
{
SAFE_DELETE(sparseTables);
sparseTables = new rdcarray<AspectSparseTable>;
sparseTables->resize(resInfo.altSparseAspects.size() + 1);
sparseTables->at(0).aspectMask = resInfo.sparseAspect;
sparseTables->at(0).table = resInfo.sparseTable;
for(size_t a = 0; a < resInfo.altSparseAspects.size(); a++)
sparseTables->at(a + 1) = resInfo.altSparseAspects[a];
}
template <typename Configuration>
void Free(ResourceManager<Configuration> *rm)
{
@@ -77,7 +109,10 @@ struct VkInitialContents
rm->ResourceTypeRelease(GetWrapped(buf));
rm->ResourceTypeRelease(GetWrapped(img));
// memory is not free'd here
SAFE_DELETE(sparseTables);
SAFE_DELETE(sparseBind);
// MemoryAllocation is not free'd here
}
// for descriptor heaps, when capturing we save the slots, when replaying we store direct writes
@@ -95,6 +130,11 @@ struct VkInitialContents
VkImage img;
MemoryAllocation mem;
Tag tag;
// for sparse resources. The tables pointer is only valid on capture, it is converted to the queue
// sparse bind. Similar to the descriptors above
rdcarray<AspectSparseTable> *sparseTables;
SparseBinding *sparseBind;
};
struct VulkanResourceManagerConfiguration
@@ -165,10 +205,7 @@ public:
ResourceId GetFirstIDForHandle(uint64_t handle);
// easy path for getting the unwrapped handle cast to the
// write type. Saves a lot of work casting to either WrappedVkNonDispRes
// or WrappedVkDispRes depending on the type, then ->real, then casting
// when this is all we want to do in most cases
// easy path for getting the wrapped handle cast to the correct type
template <typename realtype>
realtype GetLiveHandle(ResourceId origid)
{