From cc8aa679e9569316e5fd150f4d087634aafa40c8 Mon Sep 17 00:00:00 2001 From: Benson Joeris Date: Fri, 22 Feb 2019 09:30:21 -0500 Subject: [PATCH] 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. --- renderdoc/core/intervals.h | 2 + renderdoc/driver/vulkan/vk_initstate.cpp | 73 ++++++++++++++++++++---- renderdoc/driver/vulkan/vk_manager.cpp | 9 +++ renderdoc/driver/vulkan/vk_manager.h | 3 + renderdoc/driver/vulkan/vk_resources.h | 4 +- 5 files changed, 80 insertions(+), 11 deletions(-) diff --git a/renderdoc/core/intervals.h b/renderdoc/core/intervals.h index f60ba9947..3ad8a56a2 100644 --- a/renderdoc/core/intervals.h +++ b/renderdoc/core/intervals.h @@ -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::size_type size_type; + inline size_type size() const { return StartPoints.size(); } // Find the interval containing `x`. iterator find(uint64_t x) { diff --git a/renderdoc/driver/vulkan/vk_initstate.cpp b/renderdoc/driver/vulkan/vk_initstate.cpp index 7b52d0905..83d9eb0cd 100644 --- a/renderdoc/driver/vulkan/vk_initstate.cpp +++ b/renderdoc/driver/vulkan/vk_initstate.cpp @@ -1975,28 +1975,81 @@ void WrappedVulkan::Apply_InitialState(WrappedVkRes *live, VkInitialContents ini } else if(type == eResDeviceMemory) { + Intervals 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, ®ion); - else - RDCERR("Whole memory buffer not present for %llu", id); + std::vector 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); diff --git a/renderdoc/driver/vulkan/vk_manager.cpp b/renderdoc/driver/vulkan/vk_manager.cpp index d0e34479b..a639d5089 100644 --- a/renderdoc/driver/vulkan/vk_manager.cpp +++ b/renderdoc/driver/vulkan/vk_manager.cpp @@ -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; diff --git a/renderdoc/driver/vulkan/vk_manager.h b/renderdoc/driver/vulkan/vk_manager.h index b656aa020..343365ba3 100644 --- a/renderdoc/driver/vulkan/vk_manager.h +++ b/renderdoc/driver/vulkan/vk_manager.h @@ -429,7 +429,9 @@ public: void MergeReferencedMemory(std::map &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 m_MemFrameRefs; + bool m_OptimizeInitialState = false; }; diff --git a/renderdoc/driver/vulkan/vk_resources.h b/renderdoc/driver/vulkan/vk_resources.h index df1ae6c86..a73b47fb6 100644 --- a/renderdoc/driver/vulkan/vk_resources.h +++ b/renderdoc/driver/vulkan/vk_resources.h @@ -1006,8 +1006,10 @@ struct AttachmentInfo struct MemRefs { Intervals 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); }