From 2d4746629786f8f78700b1cfa0eacaa334dfd886 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 31 Jul 2020 14:07:00 +0100 Subject: [PATCH] Combine last write time and last partial use maps into array * We only care about tracking two things: 1. Resources that have been written very recently. These should not be postponed as there's a high chance they'll be written mid-frame and so we'd need their initial contents. 2. Resources that have their last non-complete-write reference was a while ago However in the second case we can acceptably ignore any resources that haven't been written recently either, since if the resource hasn't been written and also hasn't been complete-written then it hasn't been used at all. * So when updating the non-complete-write time we only do this if the resource has had a write reference, and intermittently we remove any resources that haven't had a write at all. * Postponed resources will be exactly the same set, because we treat a resource as postponable if we have no write time for it at all so it's fine to remove old resources from the list. Fewer resources will be skipped, as we now treat resources that have no known age as non-skippable. However in the majority of these cases we expect either for the resource to not be used at all (thus the postpone will never be forced to prepare and we won't serialise anything), or else if it is used the chances are high it will be used read-only so the postpone will still be enough. --- renderdoc/core/resource_manager.h | 160 +++++++++++++----- renderdoc/driver/gl/gl_driver.cpp | 2 + .../driver/vulkan/wrappers/vk_wsi_funcs.cpp | 2 + 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/renderdoc/core/resource_manager.h b/renderdoc/core/resource_manager.h index 5ded3f7ec..c34e10348 100644 --- a/renderdoc/core/resource_manager.h +++ b/renderdoc/core/resource_manager.h @@ -25,6 +25,7 @@ #pragma once +#include #include #include #include "api/replay/resourceid.h" @@ -610,6 +611,7 @@ public: inline void MarkResourceFrameReferenced(ResourceId id, FrameRefType refType); void MarkBackgroundFrameReferenced(const rdcarray> &refs); + void CleanBackgroundFrameReferences(); /////////////////////////////////////////// // Replay-side methods @@ -652,10 +654,8 @@ public: void Prepare_InitialStateIfPostponed(ResourceId id); void SkipOrPostponeOrPrepare_InitialState(ResourceId id, FrameRefType refType); - void UpdateLastWriteTime(ResourceId id, FrameRefType refType); void ResetLastWriteTimes(); void ResetCaptureStartTime(); - void UpdateLastPartialUseTime(ResourceId id, FrameRefType refType); void ResetLastPartialUseTimes(); bool HasPersistentAge(ResourceId id); @@ -685,6 +685,8 @@ protected: virtual void Apply_InitialState(WrappedResourceType live, const InitialContentData &initial) = 0; virtual rdcarray InitialContentResources(); + void UpdateLastWriteAndPartialUseTime(ResourceId id, FrameRefType refType); + // very coarse lock, protects EVERYTHING. This could certainly be improved and it may be a // bottleneck // for performance. Given that the main use cases are write-rarely read-often the lock should be @@ -754,17 +756,31 @@ protected: // over are skipped std::set m_SkippedResourceIDs; - // On marking resource write-referenced in frame, its last write - // time is reset. The time is used to determine persistent resources, - // and is checked against the `PERSISTENT_RESOURCE_AGE`. - std::map m_LastWriteTime; - // m_LastPartialUseTime is referring to: eFrameRef_PartialWrite, eFrameRef_Read, - // eFrameRef_ReadBeforeWrite, eFrameRef_WriteBeforeRead. The goal is to predict - // whether a resource will have a eFrameRef_CompleteWrite reference or not - std::map m_LastPartialUseTime; + struct ResourceRefTimes + { + ResourceId id; + + // On marking resource write-referenced in frame, its last write time is reset. The time is used + // to determine persistent resources, and is checked against the `PERSISTENT_RESOURCE_AGE`. + double writeTime; + + // partialUseTime is referring to: eFrameRef_PartialWrite, eFrameRef_Read, + // eFrameRef_ReadBeforeWrite, eFrameRef_WriteBeforeRead. The goal is to predict whether a + // resource will only ever have a eFrameRef_CompleteWrite reference. If it does, then we don't + // need to serialise the initial contents because we know it will always be fully initialised + // within the frame. + double partialUseTime; + + bool operator<(const ResourceId &o) const { return id < o; } + }; + + // all resources that are written in some way end up in this list. We then check the last time + // they were written, and the last time they were ever partially used (not completely overwritten + // in one atomic chunk). + rdcarray m_ResourceRefTimes; // Timestamp at the beginning of the frame capture. Used to determine which - // resources to refresh for their last write time (see `m_LastWriteTime`). + // resources to refresh for their last write or partial use time (see `ResourceRefTimes`). double m_captureStartTime; PerformanceTimer m_ResourcesUpdateTimer; @@ -822,8 +838,7 @@ void ResourceManager::MarkBackgroundFrameReferenced( { for(const rdcpair &ref : refs) { - UpdateLastPartialUseTime(ref.first, ref.second); - UpdateLastWriteTime(ref.first, ref.second); + UpdateLastWriteAndPartialUseTime(ref.first, ref.second); } } else @@ -837,14 +852,53 @@ void ResourceManager::MarkBackgroundFrameReferenced( if(it != refs.end() && it->first == res.id) { - UpdateLastPartialUseTime(it->first, it->second); - UpdateLastWriteTime(it->first, it->second); + UpdateLastWriteAndPartialUseTime(it->first, it->second); } } } } } +template +void ResourceManager::CleanBackgroundFrameReferences() +{ + SCOPED_LOCK(m_Lock); + + if(IsBackgroundCapturing(m_State)) + { + double now = m_ResourcesUpdateTimer.GetMilliseconds(); + + // retire any old entries, if they were written once they shouldn't be tracked forever. + // + // walk the array. If we find an item we want to remove we increment src otherwise we copy src + // to dst (if they are different). Thus next time dst will point at the item we want to skip, at + // each stage copying src to dst (if they are different). If we find an item we want to remove + // we increment src and continue (thus it will get copied ove + size_t dst = 0, src = 0; + for(dst = 0, src = 0; src < m_ResourceRefTimes.size();) + { + ResourceRefTimes &check = m_ResourceRefTimes[src]; + if(now - check.writeTime > IRRELEVANT_RESOURCE_AGE) + { + // skip src, check the next one + src++; + continue; + } + + // we want to keep src. If dst == src we can just continue on to the next iteration, if not + // then we need to copy src into dst (where dst is the leftovers from previously remove + // entries) + if(dst != src) + m_ResourceRefTimes[dst] = m_ResourceRefTimes[src]; + + dst++; + src++; + } + + m_ResourceRefTimes.resize(dst); + } +} + template template void ResourceManager::MarkResourceFrameReferenced(ResourceId id, @@ -865,8 +919,7 @@ void ResourceManager::MarkResourceFrameReferenced(ResourceId id, } } - UpdateLastPartialUseTime(id, refType); - UpdateLastWriteTime(id, refType); + UpdateLastWriteAndPartialUseTime(id, refType); if(IsBackgroundCapturing(m_State)) return; @@ -1089,15 +1142,6 @@ void ResourceManager::SkipOrPostponeOrPrepare_InitialState(Resour } } -template -inline void ResourceManager::UpdateLastWriteTime(ResourceId id, FrameRefType refType) -{ - if(!IsDirtyFrameRef(refType)) - return; - SCOPED_LOCK(m_Lock); - m_LastWriteTime[id] = m_ResourcesUpdateTimer.GetMilliseconds(); -} - template inline void ResourceManager::ResetCaptureStartTime() { @@ -1111,33 +1155,53 @@ template inline void ResourceManager::ResetLastWriteTimes() { SCOPED_LOCK(m_Lock); - for(auto it = m_LastWriteTime.begin(); it != m_LastWriteTime.end(); ++it) + for(auto it = m_ResourceRefTimes.begin(); it != m_ResourceRefTimes.end(); ++it) { // Reset only those resources which were below the threshold on // capture start. Other resource are already above the threshold. - if(m_captureStartTime - it->second <= PERSISTENT_RESOURCE_AGE) - it->second = m_ResourcesUpdateTimer.GetMilliseconds(); + if(m_captureStartTime - it->writeTime <= PERSISTENT_RESOURCE_AGE) + it->writeTime = m_ResourcesUpdateTimer.GetMilliseconds(); } } template -inline void ResourceManager::UpdateLastPartialUseTime(ResourceId id, - FrameRefType refType) +inline void ResourceManager::UpdateLastWriteAndPartialUseTime(ResourceId id, + FrameRefType refType) { - if(IsCompleteWriteFrameRef(refType)) - return; - SCOPED_LOCK(m_Lock); - m_LastPartialUseTime[id] = m_ResourcesUpdateTimer.GetMilliseconds(); + // parent must hold m_Lock for us + + ResourceRefTimes *it = std::lower_bound(m_ResourceRefTimes.begin(), m_ResourceRefTimes.end(), id); + + if(it == m_ResourceRefTimes.end() || it->id != id) + { + // don't add resources unless it's a dirty ref. After that, we'll keep updating for read and + // write refs to get partialUseTime + if(!IsDirtyFrameRef(refType)) + return; + + // if it's not pointing to the end, figure out where we need to insert it there + size_t idx = it - m_ResourceRefTimes.begin(); + m_ResourceRefTimes.insert(idx, {id, 0.0, 0.0}); + it = m_ResourceRefTimes.begin() + idx; + } + + double now = m_ResourcesUpdateTimer.GetMilliseconds(); + + if(IsDirtyFrameRef(refType)) + it->writeTime = now; + + if(!IsCompleteWriteFrameRef(refType)) + it->partialUseTime = now; } template inline void ResourceManager::ResetLastPartialUseTimes() { SCOPED_LOCK(m_Lock); - for(auto it = m_LastPartialUseTime.begin(); it != m_LastPartialUseTime.end(); ++it) + for(auto it = m_ResourceRefTimes.begin(); it != m_ResourceRefTimes.end(); ++it) { - if(m_captureStartTime - it->second <= IRRELEVANT_RESOURCE_AGE) - it->second = m_ResourcesUpdateTimer.GetMilliseconds(); + if(m_captureStartTime - it->partialUseTime <= IRRELEVANT_RESOURCE_AGE) + it->partialUseTime = m_ResourcesUpdateTimer.GetMilliseconds(); } } @@ -1146,12 +1210,12 @@ inline bool ResourceManager::HasPersistentAge(ResourceId id) { SCOPED_LOCK(m_Lock); - auto it = m_LastWriteTime.find(id); + ResourceRefTimes *it = std::lower_bound(m_ResourceRefTimes.begin(), m_ResourceRefTimes.end(), id); - if(it == m_LastWriteTime.end()) + if(it == m_ResourceRefTimes.end() || it->id != id) return true; - return m_ResourcesUpdateTimer.GetMilliseconds() - it->second >= PERSISTENT_RESOURCE_AGE; + return m_ResourcesUpdateTimer.GetMilliseconds() - it->writeTime >= PERSISTENT_RESOURCE_AGE; } template @@ -1159,12 +1223,12 @@ inline bool ResourceManager::HasIrrelevantAge(ResourceId id) { SCOPED_LOCK(m_Lock); - auto it = m_LastPartialUseTime.find(id); + ResourceRefTimes *it = std::lower_bound(m_ResourceRefTimes.begin(), m_ResourceRefTimes.end(), id); - if(it == m_LastPartialUseTime.end()) - return true; + if(it == m_ResourceRefTimes.end() || it->id != id) + return false; - return m_ResourcesUpdateTimer.GetMilliseconds() - it->second >= IRRELEVANT_RESOURCE_AGE; + return m_ResourcesUpdateTimer.GetMilliseconds() - it->partialUseTime >= IRRELEVANT_RESOURCE_AGE; } template @@ -1796,8 +1860,10 @@ void ResourceManager::ReleaseCurrentResource(ResourceId id) m_CurrentResourceMap.erase(id); m_DirtyResources.erase(id); - m_LastWriteTime.erase(id); - m_LastPartialUseTime.erase(id); + + auto it = std::lower_bound(m_ResourceRefTimes.begin(), m_ResourceRefTimes.end(), id); + if(it != m_ResourceRefTimes.end()) + m_ResourceRefTimes.erase(it - m_ResourceRefTimes.begin()); } template diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index a32bdd115..3bcb0b24f 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -2105,6 +2105,8 @@ void WrappedOpenGL::SwapBuffers(WindowingSystem winSystem, void *windowHandle) RenderDoc::Inst().AddActiveDriver(GetDriverType(), true); + GetResourceManager()->CleanBackgroundFrameReferences(); + if(!activeWindow) return; diff --git a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp index dfacc2bfe..c6a2e2b1a 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp @@ -861,6 +861,8 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR FlushQ(); } + + GetResourceManager()->CleanBackgroundFrameReferences(); } VkResult vkr;