Optimize Apply_InitialState for VkDeviceMemory resources

This change reduces the amount of data copied into VkDeviceMemory resources in
`Apply_InitialState`; this uses the new (interval-based) reference tracking.

On a large capture from a recent game, this change reduces the amount of copied
data from 2.2GB to 720MB (plus 100MB cleared to 0). This reduces the time spent
in `ApplyInitialContents` from 1800ms to 1500ms.

Ranges of memory are initialized/reset by either clearing to 0, or copying in
the initial state data, according to the `FrameRefType`, as follows:
- Read-only (`eFrameRef_Read`) regions of memory are initialized by copying
  once, before the first replay.
- Read-before-write (`eFrameRef_ReadBeforeWrite`) regions of memory are reset by
  copying before each replay.
- Write-only (`eFrameRef_PartialWrite` and `eFrameRef_CompleteWrite`) regions of
  memory are cleared to 0 before each replay. This is intended to avoid possible
  user confusion when inspecting these regions of memory, as they might
  otherwise show data written later in the frame.
- Unused (`eFrameRef_None`) regions of memory are cleared to 0 once, before the
  first replay.
This commit is contained in:
Benson Joeris
2019-02-22 09:30:21 -05:00
committed by Baldur Karlsson
parent 3abab3bbb9
commit cc8aa679e9
5 changed files with 80 additions and 11 deletions
+2
View File
@@ -188,6 +188,8 @@ public:
inline iterator begin() { return Wrap(StartPoints.begin()); }
inline const_iterator begin() const { return Wrap(StartPoints.begin()); }
inline const_iterator end() const { return Wrap(StartPoints.end()); }
typedef typename std::map<uint64_t, T>::size_type size_type;
inline size_type size() const { return StartPoints.size(); }
// Find the interval containing `x`.
iterator find(uint64_t x)
{
+63 -10
View File
@@ -1975,28 +1975,81 @@ void WrappedVulkan::Apply_InitialState(WrappedVkRes *live, VkInitialContents ini
}
else if(type == eResDeviceMemory)
{
Intervals<InitReqType> resetReq;
ResourceId orig = GetResourceManager()->GetOriginalID(id);
MemRefs *memRefs = NULL;
if(GetResourceManager()->OptimizeInitialState())
memRefs = GetResourceManager()->FindMemRefs(orig);
if(!memRefs)
{
// No information about the memory usage in the frame.
// Pessimistically assume the entire memory needs to be reset.
resetReq.update(0, initial.mem.size, eInitReq_Reset,
[](InitReqType x, InitReqType y) -> InitReqType { return std::max(x, y); });
}
else
{
bool initialized = memRefs->initializedLiveRes == live;
memRefs->initializedLiveRes = live;
for(auto it = memRefs->rangeRefs.begin(); it != memRefs->rangeRefs.end(); it++)
{
InitReqType t = InitReq(it->value());
if(t == eInitReq_Reset || (t == eInitReq_InitOnce && !initialized))
resetReq.update(it->start(), it->finish(), eInitReq_Reset,
[](InitReqType x, InitReqType y) -> InitReqType { return std::max(x, y); });
else if(t == eInitReq_Clear || (t == eInitReq_None && !initialized))
resetReq.update(it->start(), it->finish(), eInitReq_Clear,
[](InitReqType x, InitReqType y) -> InitReqType { return std::max(x, y); });
}
}
VkResult vkr = VK_SUCCESS;
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
VkBuffer srcBuf = initial.buf;
VkDeviceSize datasize = initial.mem.size;
VkDeviceSize dstMemOffs = 0;
VkBuffer dstBuf = m_CreationInfo.m_Memory[id].wholeMemBuf;
if(dstBuf == VK_NULL_HANDLE)
{
RDCERR("Whole memory buffer not present for %llu", id);
return;
}
if(resetReq.size() == 1 && resetReq.begin()->value() == eInitReq_None)
{
RDCDEBUG("Apply_InitialState (Mem %llu): skipped", orig);
return; // no copy or clear required
}
VkCommandBuffer cmd = GetNextCmd();
vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkBuffer dstBuf = m_CreationInfo.m_Memory[id].wholeMemBuf;
VkBufferCopy region = {0, dstMemOffs, datasize};
if(dstBuf != VK_NULL_HANDLE)
ObjDisp(cmd)->CmdCopyBuffer(Unwrap(cmd), Unwrap(srcBuf), Unwrap(dstBuf), 1, &region);
else
RDCERR("Whole memory buffer not present for %llu", id);
std::vector<VkBufferCopy> regions;
uint32_t fillCount = 0;
for(auto it = resetReq.begin(); it != resetReq.end(); it++)
{
if(it->start() >= initial.mem.size)
continue;
VkDeviceSize finish = RDCMIN(it->finish(), initial.mem.size);
VkDeviceSize size = finish - it->start();
switch(it->value())
{
case eInitReq_Clear:
ObjDisp(cmd)->CmdFillBuffer(Unwrap(cmd), Unwrap(dstBuf), it->start(), size, 0);
fillCount++;
break;
case eInitReq_Reset: regions.push_back({it->start(), it->start(), size}); break;
default: break;
}
}
RDCDEBUG("Apply_InitialState (Mem %llu): %d fills, %d copies", orig, fillCount, regions.size());
if(regions.size() > 0)
ObjDisp(cmd)->CmdCopyBuffer(Unwrap(cmd), Unwrap(srcBuf), Unwrap(dstBuf),
(uint32_t)regions.size(), regions.data());
vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
+9
View File
@@ -702,6 +702,15 @@ void VulkanResourceManager::ClearReferencedMemory()
m_MemFrameRefs.clear();
}
MemRefs *VulkanResourceManager::FindMemRefs(ResourceId mem)
{
auto it = m_MemFrameRefs.find(mem);
if(it != m_MemFrameRefs.end())
return &it->second;
else
return NULL;
}
bool VulkanResourceManager::Force_InitialState(WrappedVkRes *res, bool prepare)
{
return false;
+3
View File
@@ -429,7 +429,9 @@ public:
void MergeReferencedMemory(std::map<ResourceId, MemRefs> &memRefs);
void ClearReferencedMemory();
MemRefs *FindMemRefs(ResourceId mem);
inline bool OptimizeInitialState() { return m_OptimizeInitialState; }
private:
bool ResourceTypeRelease(WrappedVkRes *res);
@@ -446,4 +448,5 @@ private:
CaptureState m_State;
WrappedVulkan *m_Core;
std::map<ResourceId, MemRefs> m_MemFrameRefs;
bool m_OptimizeInitialState = false;
};
+3 -1
View File
@@ -1006,8 +1006,10 @@ struct AttachmentInfo
struct MemRefs
{
Intervals<FrameRefType> rangeRefs;
inline MemRefs() {}
WrappedVkRes *initializedLiveRes;
inline MemRefs() : initializedLiveRes(NULL) {}
inline MemRefs(VkDeviceSize offset, VkDeviceSize size, FrameRefType refType)
: initializedLiveRes(NULL)
{
rangeRefs.update(offset, offset + size, refType, ComposeFrameRefs);
}