Files
renderdoc/renderdoc/core/resource_manager.h
T
baldurk 632e9302b6 Cache whether chunks are from an allocator
* If chunks come from an allocator they can't be safely deleted because the
  allocator may have been reset and recorded over where these chunks were with
  other data. Fortunately we don't need to do anything to delete them, so
  storing the allocator status up front is sufficient.
2020-10-20 16:12:45 +01:00

1918 lines
60 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include <algorithm>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include "api/replay/rdcflatmap.h"
#include "api/replay/resourceid.h"
#include "common/threading.h"
#include "core/core.h"
#include "os/os_specific.h"
#include "serialise/serialiser.h"
// In what way (read, write, etc) was a resource referenced in a frame -
// used to determine if initial contents are needed and to what degree.
// These values are used both as states (representing the cumulative previous
// accesses to the resource), and state transitions (access by a single
// command, modifying the state). This state machine is illustrated below,
// with states represented in caps, and transitions in lower case.
//
// +------------------ NONE -----------------------------+
// | | |
// read partialWrite completeWrite
// | | |
// V V V
// READ PARTIAL_WRITE --completeWrite--> COMPLETE_WRITE
// | |
// | read
// write |
// | V
// | WRITE_BEFORE_READ
// V |
// READ_BEFORE_WRITE <--write--+
//
// Note:
// * All resources begin implicitly in the None state.
// * The transitions labeled "write" correspond to either PartialWrite or
// CompleteWrite (e.g. in the READ state, either a PartialWrite or a
// CompleteWrite moves to the READ_BEFORE_WRITE state).
// * The state transitions for ReadBeforeWrite are simply the composition of
// the transition for read, followed by the transition for write (e.g.
// ReadBeforeWrite moves from NONE state to READBEFOREWRITE state);
// similarly, the state transitions for WriteBeforeRead are the composition
// of the transition for write, followed by the transition for read.
// * All other transitions (excluding ReadBeforeWrite and WriteBeforeRead)
// that are not explicitly shown leave the state unchanged (e.g. a read in
// the COMPLETE_WRITE state remains in the COMPLETE_WRITE state).
enum FrameRefType
{
// Initial state, no reads or writes
eFrameRef_None = 0,
// Write to some unknown subset of resource.
// As a state, this represents that unlike clear, some part of the
// initial contents might still be visible to later reads.
eFrameRef_PartialWrite = 1,
// Write to the entire resource.
// As a state, this represents that no later reads will even be able to see
// the initial contents, and therefore, the initial contents need not be
// restored for replay.
eFrameRef_CompleteWrite = 2,
// Read from the resource;
// As a state, this represents a read that could have seen the resource's
// initial contents, but the value seen by the read has not been overwritten;
// therefore, the initial contents needs to be restored before the first time
// we replay, but doesn't need to be reset between subsequent replays.
eFrameRef_Read = 3,
// Read followed by a write;
// As a state, this represents a read that could have seen the resource
// initial contents, followed by a write that could have modified that
// initial contents; therefore, the initial contents will need to be reset
// before each time we replay the frame.
eFrameRef_ReadBeforeWrite = 4,
// Partial write followed by read;
// For the purpose of correct replay, this is equivalent to `Read`. However,
// if this resource is inspected by the user before the write, the future
// read could, incorrectly, be observed. This is because read-only resources
// are not reset, so the write from the previous replay may still be present.
eFrameRef_WriteBeforeRead = 5,
// No reference info is available;
// This should only appear durring replay, and any (sub)resource with `Unknown`
// reference type should be conservatively reset before each replay.
eFrameRef_Unknown = 1000000000,
};
bool IncludesRead(FrameRefType refType);
bool IncludesWrite(FrameRefType refType);
const FrameRefType eFrameRef_Minimum = eFrameRef_None;
const FrameRefType eFrameRef_Maximum = eFrameRef_WriteBeforeRead;
// Threshold value for resource "age", i.e. how long it wasn't
// referred with the any write reference.
const double PERSISTENT_RESOURCE_AGE = 3000;
// how long it wasn't referred with any read reference.
const double IRRELEVANT_RESOURCE_AGE = 3000;
DECLARE_REFLECTION_ENUM(FrameRefType);
typedef FrameRefType (*FrameRefCompFunc)(FrameRefType, FrameRefType);
// Compose frame refs that occur in a known order.
// This can be thought of as a state (`first`) and a transition from that state
// (`second`), returning the new state (see the state diagram for
// `FrameRefType` above)
FrameRefType ComposeFrameRefs(FrameRefType first, FrameRefType second);
// Compose frame refs when the order is unknown.
// This is conservative, in that, if there is both a Read and a Write/Clear, it
// assumes the Read occurs before the Write/Clear, forcing that resource to be
// reset for replay.
FrameRefType ComposeFrameRefsUnordered(FrameRefType first, FrameRefType second);
// Compose frame refs for disjoint subresources.
// This is used to compute the overall frame ref for images/memory from the
// frame refs of their subresources.
FrameRefType ComposeFrameRefsDisjoint(FrameRefType x, FrameRefType y);
// Returns whichever of `first` or `second` is valid.
FrameRefType ComposeFrameRefsFirstKnown(FrameRefType first, FrameRefType second);
// Dummy frame ref composition that always keeps the old ref.
FrameRefType KeepOldFrameRef(FrameRefType first, FrameRefType second);
bool IsDirtyFrameRef(FrameRefType refType);
bool IsCompleteWriteFrameRef(FrameRefType refType);
// Captures the possible initialization/reset requirements for resources.
// These requirements are entirely determined by the resource's FrameRefType,
// but this type improves the readability of the code that checks
// init/reset requirements.
enum InitReqType
{
// No initialization required.
eInitReq_None,
// Initialize the resource by clearing.
eInitReq_Clear,
// Initialize the resource by copying initial data.
eInitReq_Copy,
};
enum InitPolicy
{
// Completely disable optimizations--copy initial data into every resource
// before every replay.
eInitPolicy_NoOpt,
// CopyAll--conservative policy which ensures each subresource begins each
// replay with the correct initial data.
//
// Initialization policy:
// Copy initial data into each subresource
//
// Reset policy:
// Copy initial data into each subresource which is written
eInitPolicy_CopyAll,
// ClearUnread--avoid copying initial data which is never read by the replay
// commands. A user inspecting a resource before it is written may observe
// cleared data, rather than the actual initial data.
//
// Initialization policy:
// Copy initial data into each subresource that is read.
// Clear each subresource that is not read.
//
// Reset policy:
// Copy initial data into each subresource where the initial data is read
// and then overwritten.
// Clear each subresource which is written, but whose initial data is not read.
eInitPolicy_ClearUnread,
// Fastest--Initialize/reset as little as possible for correct replay.
// A user inspecting a resource before it is written may observe the data
// from a future write (from the previous replay).
//
// Initialization policy:
// Copy initial data into each subresource that is read.
// Clear each subresource that is not read.
//
// Reset policy:
// Copy initial data into each subresource where the initial data is read
// and then overwritten.
eInitPolicy_Fastest,
};
// Return the initialization/reset requirements for a FrameRefType
inline InitReqType InitReq(FrameRefType refType, InitPolicy policy, bool initialized)
{
if(eFrameRef_Minimum > refType || refType > eFrameRef_Maximum)
return eInitReq_Copy;
#define COPY_ONCE (initialized ? eInitReq_None : eInitReq_Copy)
#define CLEAR_ONCE (initialized ? eInitReq_None : eInitReq_Clear)
switch(policy)
{
case eInitPolicy_NoOpt: return eInitReq_Copy;
case eInitPolicy_CopyAll:
switch(refType)
{
case eFrameRef_None: return COPY_ONCE;
case eFrameRef_Read: return COPY_ONCE;
default: return eInitReq_Copy;
}
case eInitPolicy_ClearUnread:
switch(refType)
{
case eFrameRef_None: return CLEAR_ONCE;
case eFrameRef_Read: return COPY_ONCE;
case eFrameRef_ReadBeforeWrite: return eInitReq_Copy;
case eFrameRef_WriteBeforeRead: return eInitReq_Copy;
default: return eInitReq_Clear;
}
case eInitPolicy_Fastest:
switch(refType)
{
case eFrameRef_None: return CLEAR_ONCE;
case eFrameRef_Read: return COPY_ONCE;
case eFrameRef_ReadBeforeWrite: return eInitReq_Copy;
case eFrameRef_WriteBeforeRead: return COPY_ONCE;
default: return CLEAR_ONCE;
}
default: RDCERR("Unknown initialization policy (%d).", policy); return eInitReq_Copy;
}
#undef COPY_ONCE
#undef CLEAR_ONCE
}
// handle marking a resource referenced for read or write and storing RAW access etc.
template <typename Compose>
bool MarkReferenced(std::map<ResourceId, FrameRefType> &refs, ResourceId id, FrameRefType refType,
Compose comp)
{
auto refit = refs.find(id);
if(refit == refs.end())
{
refs[id] = refType;
return true;
}
else
{
refit->second = comp(refit->second, refType);
}
return false;
}
inline bool MarkReferenced(std::map<ResourceId, FrameRefType> &refs, ResourceId id,
FrameRefType refType)
{
return MarkReferenced(refs, id, refType, ComposeFrameRefs);
}
// verbose prints with IDs of each dirty resource and whether it was prepared,
// and whether it was serialised.
#define VERBOSE_DIRTY_RESOURCES OPTION_OFF
namespace ResourceIDGen
{
ResourceId GetNewUniqueID();
void SetReplayResourceIDs();
};
struct ResourceRecord;
class ResourceRecordHandler
{
public:
virtual void MarkDirtyResource(ResourceId id) = 0;
virtual void RemoveResourceRecord(ResourceId id) = 0;
virtual void MarkResourceFrameReferenced(ResourceId id, FrameRefType refType) = 0;
virtual void DestroyResourceRecord(ResourceRecord *record) = 0;
};
// This is a generic resource record, that APIs can inherit from and use.
// A resource is an API object that gets tracked on its own, has dependencies on other resources
// and has its own stream of chunks.
//
// This is used to track the necessary resources for a frame, and include only those required
// for the captured frame in its log. It also handles anything resource-specific such as
// shadow CPU copies of data.
struct ResourceRecord
{
ResourceRecord(ResourceId id, bool lock)
: RefCount(1),
ResID(id),
UpdateCount(0),
DataInSerialiser(false),
DataPtr(NULL),
DataOffset(0),
Length(0),
DataWritten(false),
InternalResource(false)
{
m_ChunkLock = NULL;
if(lock)
m_ChunkLock = new Threading::CriticalSection();
}
void DisableChunkLocking() { SAFE_DELETE(m_ChunkLock); }
~ResourceRecord() { SAFE_DELETE(m_ChunkLock); }
void AddParent(ResourceRecord *r)
{
if(r == this)
return;
if(Parents.indexOf(r) < 0)
{
r->AddRef();
Parents.push_back(r);
}
}
void MarkParentsDirty(ResourceRecordHandler *mgr)
{
for(auto it = Parents.begin(); it != Parents.end(); ++it)
mgr->MarkDirtyResource((*it)->GetResourceID());
}
void MarkParentsReferenced(ResourceRecordHandler *mgr, FrameRefType refType)
{
for(auto it = Parents.begin(); it != Parents.end(); ++it)
mgr->MarkResourceFrameReferenced((*it)->GetResourceID(), refType);
}
void FreeParents(ResourceRecordHandler *mgr)
{
for(auto it = Parents.begin(); it != Parents.end(); ++it)
(*it)->Delete(mgr);
Parents.clear();
}
void MarkDataUnwritten() { DataWritten = false; }
void Insert(std::map<int64_t, Chunk *> &recordlist)
{
bool dataWritten = DataWritten;
DataWritten = true;
for(auto it = Parents.begin(); it != Parents.end(); ++it)
{
if(!(*it)->DataWritten)
{
(*it)->Insert(recordlist);
}
}
if(!dataWritten)
{
for(auto it = m_Chunks.begin(); it != m_Chunks.end(); ++it)
recordlist[it->id] = it->chunk;
}
}
void AddRef() { Atomic::Inc32(&RefCount); }
int GetRefCount() const { return RefCount; }
void Delete(ResourceRecordHandler *mgr);
ResourceId GetResourceID() const { return ResID; }
void AddChunk(Chunk *chunk, int64_t ID = 0)
{
if(ID == 0)
ID = GetID();
LockChunks();
m_Chunks.push_back(StoredChunk(ID, chunk));
UnlockChunks();
}
void LockChunks()
{
if(m_ChunkLock)
m_ChunkLock->Lock();
}
void UnlockChunks()
{
if(m_ChunkLock)
m_ChunkLock->Unlock();
}
bool HasChunks() const { return !m_Chunks.empty(); }
size_t NumChunks() const { return m_Chunks.size(); }
void SwapChunks(ResourceRecord *other)
{
LockChunks();
other->LockChunks();
m_Chunks.swap(other->m_Chunks);
m_FrameRefs.swap(other->m_FrameRefs);
other->UnlockChunks();
UnlockChunks();
}
void AppendFrom(ResourceRecord *other)
{
LockChunks();
other->LockChunks();
for(auto it = other->m_Chunks.begin(); it != other->m_Chunks.end(); ++it)
AddChunk(it->chunk->Duplicate());
for(auto it = other->Parents.begin(); it != other->Parents.end(); ++it)
AddParent(*it);
other->UnlockChunks();
UnlockChunks();
}
void DeleteChunks()
{
LockChunks();
for(auto it = m_Chunks.begin(); it != m_Chunks.end(); ++it)
it->chunk->Delete(it->fromAllocator != 0);
m_Chunks.clear();
UnlockChunks();
}
Chunk *GetLastChunk() const
{
RDCASSERT(HasChunks());
return m_Chunks.back().chunk;
}
int64_t GetLastChunkID() const
{
RDCASSERT(HasChunks());
return m_Chunks.back().id;
}
void PopChunk() { m_Chunks.pop_back(); }
byte *GetDataPtr() { return DataPtr + DataOffset; }
bool HasDataPtr() { return DataPtr != NULL; }
void SetDataOffset(uint64_t offs) { DataOffset = offs; }
void SetDataPtr(byte *ptr) { DataPtr = ptr; }
template <typename Compose>
bool MarkResourceFrameReferenced(ResourceId id, FrameRefType refType, Compose comp);
inline bool MarkResourceFrameReferenced(ResourceId id, FrameRefType refType)
{
return MarkResourceFrameReferenced(id, refType, ComposeFrameRefs);
}
void AddResourceReferences(ResourceRecordHandler *mgr);
void AddReferencedIDs(std::set<ResourceId> &ids)
{
for(auto it = m_FrameRefs.begin(); it != m_FrameRefs.end(); ++it)
ids.insert(it->first);
}
uint64_t Length;
int UpdateCount;
bool DataInSerialiser;
// anything internal that shouldn't be automatically pulled in by 'Ref All Resources' or have
// initial contents stored. This could either be a type of object that would break if its chunks
// were inserted into the initialisation phase (like an D3D11 DeviceContext which contains
// commands) or just debug objects created during capture as helpers which shouldn't be included.
//
// The implication is that they are handled specially for being inserted into the capture, or
// aren't inserted at all. Note that if a resource is frame-referenced, it will be included
// regardless but still without initial contents, capture drivers should be careful.
bool InternalResource;
bool DataWritten;
protected:
int32_t RefCount;
byte *DataPtr;
uint64_t DataOffset;
ResourceId ResID;
rdcarray<ResourceRecord *> Parents;
int64_t GetID()
{
static int64_t globalIDCounter = 10;
return Atomic::Inc64(&globalIDCounter);
}
struct StoredChunk
{
StoredChunk(int64_t i, Chunk *c)
{
id = i;
// we store this here because by the time it comes to delete the chunks the allocator may have
// already been reset and the contents trashed.
fromAllocator = c->IsFromAllocator() ? 1 : 0;
chunk = c;
}
int64_t id : 63;
int64_t fromAllocator : 1;
Chunk *chunk;
};
rdcarray<StoredChunk> m_Chunks;
Threading::CriticalSection *m_ChunkLock;
std::map<ResourceId, FrameRefType> m_FrameRefs;
};
template <typename Compose>
bool ResourceRecord::MarkResourceFrameReferenced(ResourceId id, FrameRefType refType, Compose comp)
{
if(id == ResourceId())
return false;
return MarkReferenced(m_FrameRefs, id, refType, comp);
}
// the resource manager is a utility class that's not required but is likely wanted by any API
// implementation.
// It keeps track of resource records, which resources are alive and allows you to query for them by
// ID. It tracks
// which resources are marked as dirty (needing their initial contents fetched before capture).
//
// For APIs that wrap their resources it provides tracking for that.
//
// In the replay application it will also track which 'live' resources are representing which
// 'original'
// resources from the application when it was captured.
template <typename Configuration>
class ResourceManager : public ResourceRecordHandler
{
public:
typedef typename Configuration::WrappedResourceType WrappedResourceType;
typedef typename Configuration::RealResourceType RealResourceType;
typedef typename Configuration::RecordType RecordType;
typedef typename Configuration::InitialContentData InitialContentData;
ResourceManager(CaptureState &state);
virtual ~ResourceManager();
void Shutdown();
///////////////////////////////////////////
// Capture-side methods
// while capturing, resource records containing chunk streams and metadata for resources
RecordType *GetResourceRecord(ResourceId id);
bool HasResourceRecord(ResourceId id);
RecordType *AddResourceRecord(ResourceId id);
inline void RemoveResourceRecord(ResourceId id);
void DestroyResourceRecord(ResourceRecord *record);
// while capturing or replaying, resources and their live IDs
void AddCurrentResource(ResourceId id, WrappedResourceType res);
bool HasCurrentResource(ResourceId id);
WrappedResourceType GetCurrentResource(ResourceId id);
void ReleaseCurrentResource(ResourceId id);
// insert the chunks for the resources referenced in the frame
void InsertReferencedChunks(WriteSerialiser &ser);
// mark resource records as unwritten, ready to be written to a new logfile.
void MarkUnwrittenResources();
// clear the list of frame-referenced resources - e.g. if you're about to recapture a frame
void ClearReferencedResources();
// indicates this resource could have been modified by the GPU,
// so it's now suspect and the data we have on it might well be out of date
// and to be correct its contents should be serialised out at the start
// of the frame.
inline void MarkDirtyResource(ResourceId res);
// returns if the resource has been marked as dirty
bool IsResourceDirty(ResourceId res);
// call callbacks to prepare initial contents for dirty resources
void PrepareInitialContents();
InitialContentData GetInitialContents(ResourceId id);
void SetInitialContents(ResourceId id, InitialContentData contents);
void SetInitialChunk(ResourceId id, Chunk *chunk);
// generate chunks for initial contents and insert.
void InsertInitialContentsChunks(WriteSerialiser &ser);
// for initial contents that don't need a chunk - apply them here. This allows any patching to
// creation-time chunks to happen before they're written to disk.
void ApplyInitialContentsNonChunks(WriteSerialiser &ser);
// Serialise out which resources need initial contents, along with whether their
// initial contents are in the serialised stream (e.g. RTs might still want to be
// cleared on frame init).
void Serialise_InitialContentsNeeded(WriteSerialiser &ser);
// mark resource referenced somewhere in the main frame-affecting calls.
// That means this resource should be included in the final serialise out
template <typename Compose>
void MarkResourceFrameReferenced(ResourceId id, FrameRefType refType, Compose comp);
inline void MarkResourceFrameReferenced(ResourceId id, FrameRefType refType);
void MarkBackgroundFrameReferenced(const rdcflatmap<ResourceId, FrameRefType> &refs);
void CleanBackgroundFrameReferences();
///////////////////////////////////////////
// Replay-side methods
// Live resources to replace serialised IDs
void AddLiveResource(ResourceId origid, WrappedResourceType livePtr);
bool HasLiveResource(ResourceId origid);
WrappedResourceType GetLiveResource(ResourceId origid);
void EraseLiveResource(ResourceId origid);
// when asked for a given id, return the resource for a replacement id
void ReplaceResource(ResourceId from, ResourceId to);
bool HasReplacement(ResourceId from);
void RemoveReplacement(ResourceId id);
// get the original ID for a real ID that may be a replacement. i.e. if ID 123 is ID 10000005
// live, and 10000005 live is replaced with 10000839, then calling this function with either ID
// 10000005 or ID 10000839 will return ID 123.
ResourceId GetUnreplacedOriginalID(ResourceId id);
// fetch original ID for a real ID or vice-versa.
ResourceId GetOriginalID(ResourceId id);
ResourceId GetLiveID(ResourceId id);
// Serialise in which resources need initial contents and set them up.
void CreateInitialContents(ReadSerialiser &ser);
// Free any initial contents that are prepared (for after capture is complete)
void FreeInitialContents();
// Apply the initial contents for the resources that need them, used at the start of a frame
void ApplyInitialContents();
// Resource wrapping, allows for querying and adding/removing of wrapper layers around resources
bool AddWrapper(WrappedResourceType wrap, RealResourceType real);
bool HasWrapper(RealResourceType real);
WrappedResourceType GetWrapper(RealResourceType real);
void RemoveWrapper(RealResourceType real);
void ResetLastWriteTimes();
void ResetCaptureStartTime();
void ResetLastPartialUseTimes();
bool HasPersistentAge(ResourceId id);
bool HasIrrelevantAge(ResourceId id);
bool IsResourcePostponed(ResourceId id);
bool IsResourceSkipped(ResourceId id);
bool ShouldPostpone(ResourceId id);
bool ShouldSkip(ResourceId id);
virtual bool IsResourceTrackedForPersistency(const WrappedResourceType &res) { return false; }
protected:
friend InitialContentData;
// 'interface' to implement by derived classes
virtual ResourceId GetID(WrappedResourceType res) = 0;
virtual bool ResourceTypeRelease(WrappedResourceType res) = 0;
virtual bool Need_InitialStateChunk(ResourceId id, const InitialContentData &initial)
{
return true;
}
virtual bool Prepare_InitialState(WrappedResourceType res) = 0;
virtual uint64_t GetSize_InitialState(ResourceId id, const InitialContentData &initial) = 0;
virtual bool Serialise_InitialState(WriteSerialiser &ser, ResourceId id, RecordType *record,
const InitialContentData *initialData) = 0;
virtual void Create_InitialState(ResourceId id, WrappedResourceType live, bool hasData) = 0;
virtual void Apply_InitialState(WrappedResourceType live, const InitialContentData &initial) = 0;
virtual rdcarray<ResourceId> InitialContentResources();
void UpdateLastWriteAndPartialUseTime(ResourceId id, FrameRefType refType);
void Prepare_InitialStateIfPostponed(ResourceId id, bool midframe);
void SkipOrPostponeOrPrepare_InitialState(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 optimised for that as we only want to make sure we're not modifying the objects
// together, by far the most common operation is looking up data.
Threading::CriticalSection m_Lock;
// we only need to lock during capturing, on replay we have single threaded access.
bool m_Capturing;
// easy optimisation win - don't use maps everywhere. It's convenient but not optimal, and
// profiling will likely prove that some or all of these could be a problem
// used during capture - map from real resource to its wrapper (other way can be done just with an
// Unwrap)
std::map<RealResourceType, WrappedResourceType> m_WrapperMap;
// used during capture - holds resources referenced in current frame (and how they're referenced)
std::map<ResourceId, FrameRefType> m_FrameReferencedResources;
// used during capture - holds resources marked as dirty, needing initial contents
std::set<ResourceId> m_DirtyResources;
struct InitialContentDataOrChunk
{
Chunk *chunk = NULL;
InitialContentData data;
void Free(ResourceManager *mgr)
{
if(chunk)
{
chunk->Delete();
chunk = NULL;
}
data.Free(mgr);
}
};
// used during capture or replay - holds initial contents
std::map<ResourceId, InitialContentDataOrChunk> m_InitialContents;
// used during capture or replay - map of resources currently alive with their real IDs, used in
// capture and replay.
std::unordered_map<ResourceId, WrappedResourceType> m_CurrentResourceMap;
// used during replay - maps back and forth from original id to live id and vice-versa
std::unordered_map<ResourceId, ResourceId> m_OriginalIDs, m_LiveIDs;
// used during replay - holds resources allocated and the original id that they represent
std::unordered_map<ResourceId, WrappedResourceType> m_LiveResourceMap;
// used during capture - holds resource records by id.
std::unordered_map<ResourceId, RecordType *> m_ResourceRecords;
Threading::RWLock m_ResourceRecordLock;
// used during replay - holds current resource replacements
// replaced -> replacement
std::unordered_map<ResourceId, ResourceId> m_Replacements;
// replacement -> replaced (for looking up original IDs)
std::unordered_map<ResourceId, ResourceId> m_Replaced;
// During initial resources preparation, persistent resources are
// postponed until serializing to RDC file.
std::unordered_set<ResourceId> m_PostponedResourceIDs;
// During initial resources preparation, resources that are completely written
// over are skipped
std::unordered_set<ResourceId> m_SkippedResourceIDs;
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<ResourceRefTimes> m_ResourceRefTimes;
// Timestamp at the beginning of the frame capture. Used to determine which
// resources to refresh for their last write or partial use time (see `ResourceRefTimes`).
double m_captureStartTime;
PerformanceTimer m_ResourcesUpdateTimer;
// The capture state is propagated by a specific driver.
CaptureState &m_State;
};
template <typename Configuration>
ResourceManager<Configuration>::ResourceManager(CaptureState &state) : m_State(state)
{
m_Capturing = IsCaptureMode(state);
if(RenderDoc::Inst().GetCrashHandler())
RenderDoc::Inst().GetCrashHandler()->RegisterMemoryRegion(this, sizeof(ResourceManager));
}
template <typename Configuration>
void ResourceManager<Configuration>::Shutdown()
{
FreeInitialContents();
while(!m_LiveResourceMap.empty())
{
auto it = m_LiveResourceMap.begin();
ResourceId id = it->first;
ResourceTypeRelease(it->second);
auto removeit = m_LiveResourceMap.find(id);
if(removeit != m_LiveResourceMap.end())
m_LiveResourceMap.erase(removeit);
}
RDCASSERT(m_ResourceRecords.empty());
}
template <typename Configuration>
ResourceManager<Configuration>::~ResourceManager()
{
RDCASSERT(m_LiveResourceMap.empty());
RDCASSERT(m_InitialContents.empty());
RDCASSERT(m_ResourceRecords.empty());
if(RenderDoc::Inst().GetCrashHandler())
RenderDoc::Inst().GetCrashHandler()->UnregisterMemoryRegion(this);
}
template <typename Configuration>
void ResourceManager<Configuration>::MarkBackgroundFrameReferenced(
const rdcflatmap<ResourceId, FrameRefType> &refs)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(IsBackgroundCapturing(m_State))
{
if(refs.size() <= m_ResourceRefTimes.size())
{
for(auto it = refs.begin(); it != refs.end(); ++it)
UpdateLastWriteAndPartialUseTime(it->first, it->second);
}
else
{
for(const ResourceRefTimes &res : m_ResourceRefTimes)
{
auto it = refs.find(res.id);
if(it != refs.end())
UpdateLastWriteAndPartialUseTime(it->first, it->second);
}
}
}
}
template <typename Configuration>
void ResourceManager<Configuration>::CleanBackgroundFrameReferences()
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
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 <typename Configuration>
template <typename Compose>
void ResourceManager<Configuration>::MarkResourceFrameReferenced(ResourceId id,
FrameRefType refType, Compose comp)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(id == ResourceId())
return;
if(IsActiveCapturing(m_State))
{
SkipOrPostponeOrPrepare_InitialState(id, refType);
if(IsDirtyFrameRef(refType))
{
Prepare_InitialStateIfPostponed(id, true);
}
}
UpdateLastWriteAndPartialUseTime(id, refType);
if(IsBackgroundCapturing(m_State))
return;
bool newRef = MarkReferenced(m_FrameReferencedResources, id, refType, comp);
if(newRef)
{
RecordType *record = GetResourceRecord(id);
if(record)
record->AddRef();
}
}
template <typename Configuration>
void ResourceManager<Configuration>::MarkResourceFrameReferenced(ResourceId id, FrameRefType refType)
{
return MarkResourceFrameReferenced(id, refType, ComposeFrameRefs);
}
template <typename Configuration>
void ResourceManager<Configuration>::MarkDirtyResource(ResourceId res)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(res == ResourceId())
return;
m_DirtyResources.insert(res);
}
template <typename Configuration>
bool ResourceManager<Configuration>::IsResourceDirty(ResourceId res)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(res == ResourceId())
return false;
return m_DirtyResources.find(res) != m_DirtyResources.end();
}
template <typename Configuration>
void ResourceManager<Configuration>::SetInitialContents(ResourceId id, InitialContentData contents)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
RDCASSERT(id != ResourceId());
auto it = m_InitialContents.find(id);
if(it != m_InitialContents.end())
it->second.Free(this);
m_InitialContents[id].data = contents;
}
template <typename Configuration>
void ResourceManager<Configuration>::SetInitialChunk(ResourceId id, Chunk *chunk)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
RDCASSERT(id != ResourceId());
RDCASSERT(chunk->GetChunkType<SystemChunk>() == SystemChunk::InitialContents);
InitialContentDataOrChunk &data = m_InitialContents[id];
if(data.chunk)
data.chunk->Delete();
data.chunk = chunk;
}
template <typename Configuration>
typename Configuration::InitialContentData ResourceManager<Configuration>::GetInitialContents(
ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(id == ResourceId())
return InitialContentData();
if(m_InitialContents.find(id) != m_InitialContents.end())
return m_InitialContents[id].data;
return InitialContentData();
}
// use a namespace so this doesn't pollute the global namesapce
namespace ResourceManagerInternal
{
struct WrittenRecord
{
ResourceId id;
bool written;
};
};
DECLARE_REFLECTION_STRUCT(ResourceManagerInternal::WrittenRecord);
template <class SerialiserType>
void DoSerialise(SerialiserType &ser, ResourceManagerInternal::WrittenRecord &el)
{
SERIALISE_MEMBER(id);
SERIALISE_MEMBER(written);
}
template <typename Configuration>
void ResourceManager<Configuration>::Serialise_InitialContentsNeeded(WriteSerialiser &ser)
{
using namespace ResourceManagerInternal;
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
// which resources need initial contents? either those which we didn't have proper initialisation
// for (because they were dirty one way or another) or resources that are written mid-frame and so
// need to be reset.
rdcarray<WrittenRecord> NeededInitials;
// reasonable estimate, and these records are small
NeededInitials.reserve(m_FrameReferencedResources.size() + m_InitialContents.size());
// all resources that were recorded as being modified should be included in the list of those
// needing initial contents
for(auto it = m_FrameReferencedResources.begin(); it != m_FrameReferencedResources.end(); ++it)
{
RecordType *record = GetResourceRecord(it->first);
if(IsDirtyFrameRef(it->second))
{
WrittenRecord wr = {it->first, record ? record->DataInSerialiser : true};
NeededInitials.push_back(wr);
}
}
// any resources that had initial contents generated should also be included, even if they're only
// referenced read-only, as anything not in this list will have its initial contents freed on
// replay (see CreateInitialContents). However we only need to keep resources that are referenced
// (unless we have ref all resources on)
for(auto it = m_InitialContents.begin(); it != m_InitialContents.end(); ++it)
{
bool include = RenderDoc::Inst().GetCaptureOptions().refAllResources;
ResourceId id = it->first;
if(m_FrameReferencedResources.find(id) != m_FrameReferencedResources.end())
include = true;
if(include)
{
WrittenRecord wr = {id, true};
NeededInitials.push_back(wr);
}
}
uint64_t chunkSize = uint64_t(NeededInitials.size() * sizeof(WrittenRecord) + 16);
SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContentsList, chunkSize);
SERIALISE_ELEMENT(NeededInitials);
}
template <typename Configuration>
void ResourceManager<Configuration>::FreeInitialContents()
{
while(!m_InitialContents.empty())
{
auto it = m_InitialContents.begin();
it->second.Free(this);
if(!m_InitialContents.empty())
m_InitialContents.erase(m_InitialContents.begin());
}
m_PostponedResourceIDs.clear();
m_SkippedResourceIDs.clear();
}
template <typename Configuration>
void ResourceManager<Configuration>::Prepare_InitialStateIfPostponed(ResourceId id, bool midframe)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(!IsResourcePostponed(id))
return;
if(midframe)
RDCLOG("Preparing resource %s after it has been postponed.", ToStr(id).c_str());
WrappedResourceType res = GetCurrentResource(id);
Prepare_InitialState(res);
m_PostponedResourceIDs.erase(id);
}
template <typename Configuration>
void ResourceManager<Configuration>::SkipOrPostponeOrPrepare_InitialState(ResourceId id,
FrameRefType refType)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(!IsResourceSkipped(id))
return;
// the first time we encounter a skipped resource, we can choose to
// skip this resource forever, convert it to be postponed, or prepare
// it immediately. We can't retrieve its initial state once it has
// been written over.
m_SkippedResourceIDs.erase(id);
// skip this forever if the first encounter is a complete write
if(IsCompleteWriteFrameRef(refType))
{
RDCDEBUG("Resource %s skipped forever on refType of %s)", ToStr(id).c_str(),
ToStr(refType).c_str());
return;
}
// If this resource is only being read, we might as well try to
// postpone it to conserve memory consumption.
if(!IsDirtyFrameRef(refType) && IsResourceTrackedForPersistency(GetCurrentResource(id)))
{
m_PostponedResourceIDs.insert(id);
RDCDEBUG("Resource %s converted from skipped to postponed on refType of %s", ToStr(id).c_str(),
ToStr(refType).c_str());
SetInitialContents(id, InitialContentData());
}
else
{
WrappedResourceType res = GetCurrentResource(id);
RDCDEBUG("Preparing resource %s after it has been skipped on refType of %s", ToStr(id).c_str(),
ToStr(refType).c_str());
Prepare_InitialState(res);
}
}
template <typename Configuration>
inline void ResourceManager<Configuration>::ResetCaptureStartTime()
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
// This time is used to analyze which resources to refresh
// for their last write time.
m_captureStartTime = m_ResourcesUpdateTimer.GetMilliseconds();
}
template <typename Configuration>
inline void ResourceManager<Configuration>::ResetLastWriteTimes()
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
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->writeTime <= PERSISTENT_RESOURCE_AGE)
it->writeTime = m_ResourcesUpdateTimer.GetMilliseconds();
}
}
template <typename Configuration>
inline void ResourceManager<Configuration>::UpdateLastWriteAndPartialUseTime(ResourceId id,
FrameRefType refType)
{
// 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 <typename Configuration>
inline void ResourceManager<Configuration>::ResetLastPartialUseTimes()
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
for(auto it = m_ResourceRefTimes.begin(); it != m_ResourceRefTimes.end(); ++it)
{
if(m_captureStartTime - it->partialUseTime <= IRRELEVANT_RESOURCE_AGE)
it->partialUseTime = m_ResourcesUpdateTimer.GetMilliseconds();
}
}
template <typename Configuration>
inline bool ResourceManager<Configuration>::HasPersistentAge(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
ResourceRefTimes *it = std::lower_bound(m_ResourceRefTimes.begin(), m_ResourceRefTimes.end(), id);
if(it == m_ResourceRefTimes.end() || it->id != id)
return true;
return m_ResourcesUpdateTimer.GetMilliseconds() - it->writeTime >= PERSISTENT_RESOURCE_AGE;
}
template <typename Configuration>
inline bool ResourceManager<Configuration>::HasIrrelevantAge(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
ResourceRefTimes *it = std::lower_bound(m_ResourceRefTimes.begin(), m_ResourceRefTimes.end(), id);
if(it == m_ResourceRefTimes.end() || it->id != id)
return false;
return m_ResourcesUpdateTimer.GetMilliseconds() - it->partialUseTime >= IRRELEVANT_RESOURCE_AGE;
}
template <typename Configuration>
inline bool ResourceManager<Configuration>::IsResourcePostponed(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
return m_PostponedResourceIDs.find(id) != m_PostponedResourceIDs.end();
}
template <typename Configuration>
inline bool ResourceManager<Configuration>::IsResourceSkipped(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
return m_SkippedResourceIDs.find(id) != m_SkippedResourceIDs.end();
}
template <typename Configuration>
inline bool ResourceManager<Configuration>::ShouldPostpone(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
WrappedResourceType res = GetCurrentResource(id);
if(!IsResourceTrackedForPersistency(res))
return false;
return HasPersistentAge(id);
}
template <typename Configuration>
inline bool ResourceManager<Configuration>::ShouldSkip(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
WrappedResourceType res = GetCurrentResource(id);
if(!IsResourceTrackedForPersistency(res))
return false;
return HasIrrelevantAge(id);
}
template <typename Configuration>
void ResourceManager<Configuration>::CreateInitialContents(ReadSerialiser &ser)
{
using namespace ResourceManagerInternal;
std::set<ResourceId> ids;
rdcarray<WrittenRecord> NeededInitials;
SERIALISE_ELEMENT(NeededInitials);
for(const WrittenRecord &wr : NeededInitials)
{
ResourceId id = wr.id;
ids.insert(id);
// if this resource exists and we don't have initial contents for it serialised, create some for
// reset purposes.
if(HasLiveResource(id) && m_InitialContents.find(id) == m_InitialContents.end())
Create_InitialState(id, GetLiveResource(id), wr.written);
}
// any initial contents that we ended up with which we don't need can be freed now
for(auto it = m_InitialContents.begin(); it != m_InitialContents.end();)
{
ResourceId id = it->first;
if(ids.find(id) == ids.end())
{
it->second.Free(this);
++it;
m_InitialContents.erase(id);
}
else
{
++it;
}
}
}
template <typename Configuration>
void ResourceManager<Configuration>::ApplyInitialContents()
{
RDCDEBUG("Applying initial contents");
rdcarray<ResourceId> resources = InitialContentResources();
for(auto it = resources.begin(); it != resources.end(); ++it)
{
ResourceId id = *it;
const InitialContentDataOrChunk &data = m_InitialContents[id];
WrappedResourceType live = GetLiveResource(id);
Apply_InitialState(live, data.data);
}
RDCDEBUG("Applied %d", (uint32_t)resources.size());
}
template <typename Configuration>
rdcarray<ResourceId> ResourceManager<Configuration>::InitialContentResources()
{
rdcarray<ResourceId> resources;
for(auto it = m_InitialContents.begin(); it != m_InitialContents.end(); ++it)
{
ResourceId id = it->first;
if(HasLiveResource(id))
{
resources.push_back(id);
}
}
return resources;
}
template <typename Configuration>
void ResourceManager<Configuration>::MarkUnwrittenResources()
{
SCOPED_READLOCK(m_ResourceRecordLock);
for(auto it = m_ResourceRecords.begin(); it != m_ResourceRecords.end(); ++it)
it->second->MarkDataUnwritten();
}
template <typename Configuration>
void ResourceManager<Configuration>::InsertReferencedChunks(WriteSerialiser &ser)
{
std::map<int64_t, Chunk *> sortedChunks;
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
RDCDEBUG("%u frame resource records", (uint32_t)m_FrameReferencedResources.size());
if(RenderDoc::Inst().GetCaptureOptions().refAllResources)
{
SCOPED_READLOCK(m_ResourceRecordLock);
float num = float(m_ResourceRecords.size());
float idx = 0.0f;
for(auto it = m_ResourceRecords.begin(); it != m_ResourceRecords.end(); ++it)
{
RenderDoc::Inst().SetProgress(CaptureProgress::AddReferencedResources, idx / num);
idx += 1.0f;
if(m_FrameReferencedResources.find(it->first) == m_FrameReferencedResources.end() &&
it->second->InternalResource)
continue;
it->second->Insert(sortedChunks);
}
}
else
{
float num = float(m_FrameReferencedResources.size());
float idx = 0.0f;
for(auto it = m_FrameReferencedResources.begin(); it != m_FrameReferencedResources.end(); ++it)
{
RenderDoc::Inst().SetProgress(CaptureProgress::AddReferencedResources, idx / num);
idx += 1.0f;
RecordType *record = GetResourceRecord(it->first);
if(record)
record->Insert(sortedChunks);
}
}
RDCDEBUG("%u frame resource chunks", (uint32_t)sortedChunks.size());
for(auto it = sortedChunks.begin(); it != sortedChunks.end(); it++)
it->second->Write(ser);
RDCDEBUG("inserted to serialiser");
}
template <typename Configuration>
void ResourceManager<Configuration>::PrepareInitialContents()
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
RDCDEBUG("Preparing up to %u potentially dirty resources", (uint32_t)m_DirtyResources.size());
uint32_t prepared = 0;
uint32_t postponed = 0;
uint32_t skipped = 0;
float num = float(m_DirtyResources.size());
float idx = 0.0f;
for(auto it = m_DirtyResources.begin(); it != m_DirtyResources.end(); ++it)
{
ResourceId id = *it;
RenderDoc::Inst().SetProgress(CaptureProgress::PrepareInitialStates, idx / num);
idx += 1.0f;
// if somehow this resource has been deleted but is still dirty, we can't prepare it. Resources
// deleted prior to beginning the frame capture cannot linger and be needed - we only need to
// care about resources deleted after this point (mid-capture)
if(!HasCurrentResource(id))
continue;
RecordType *record = GetResourceRecord(id);
WrappedResourceType res = GetCurrentResource(id);
// don't prepare internal resources, or those without a record
if(record == NULL || record->InternalResource)
continue;
if(ShouldSkip(id))
{
m_SkippedResourceIDs.insert(id);
skipped++;
continue;
}
if(ShouldPostpone(id))
{
m_PostponedResourceIDs.insert(id);
// Set empty contents here, it'll be prepared on serialization.
SetInitialContents(id, InitialContentData());
postponed++;
continue;
}
prepared++;
#if ENABLED(VERBOSE_DIRTY_RESOURCES)
RDCDEBUG("Prepare Resource %s", ToStr(id).c_str());
#endif
Prepare_InitialState(res);
}
RDCDEBUG("Prepared %u dirty resources, postponed %u, skipped %u", prepared, postponed, skipped);
}
template <typename Configuration>
void ResourceManager<Configuration>::InsertInitialContentsChunks(WriteSerialiser &ser)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
uint32_t dirty = 0;
uint32_t skipped = 0;
RDCDEBUG("Checking %u resources with initial contents", (uint32_t)m_InitialContents.size());
float num = float(m_InitialContents.size());
float idx = 0.0f;
for(auto it = m_InitialContents.begin(); it != m_InitialContents.end(); ++it)
{
ResourceId id = it->first;
RenderDoc::Inst().SetProgress(CaptureProgress::SerialiseInitialStates, idx / num);
idx += 1.0f;
if(m_FrameReferencedResources.find(id) == m_FrameReferencedResources.end() &&
!RenderDoc::Inst().GetCaptureOptions().refAllResources)
{
#if ENABLED(VERBOSE_DIRTY_RESOURCES)
RDCDEBUG("Dirty resource %s is GPU dirty but not referenced - skipping", ToStr(id).c_str());
#endif
skipped++;
continue;
}
RecordType *record = GetResourceRecord(id);
if(record == NULL)
{
#if ENABLED(VERBOSE_DIRTY_RESOURCES)
RDCDEBUG("Resource %s has no resource record - skipping", ToStr(id).c_str());
#endif
continue;
}
if(record->InternalResource)
{
#if ENABLED(VERBOSE_DIRTY_RESOURCES)
RDCDEBUG("Resource %s is special - skipping", ToStr(id).c_str());
#endif
continue;
}
#if ENABLED(VERBOSE_DIRTY_RESOURCES)
RDCDEBUG("Serialising dirty Resource %s", ToStr(id).c_str());
#endif
// Load postponed resource if needed.
Prepare_InitialStateIfPostponed(id, false);
dirty++;
if(!Need_InitialStateChunk(id, it->second.data))
{
// this was handled in ApplyInitialContentsNonChunks(), do nothing as there's no point copying
// the data again (it's already been serialised).
continue;
}
if(it->second.chunk)
{
it->second.chunk->Write(ser);
}
else
{
uint64_t size = GetSize_InitialState(id, it->second.data);
SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents, size);
Serialise_InitialState(ser, id, record, &it->second.data);
}
// Reset back to empty contents, unloading the actual resource.
SetInitialContents(id, InitialContentData());
}
RDCDEBUG("Serialised %u resources, skipped %u unreferenced", dirty, skipped);
}
template <typename Configuration>
void ResourceManager<Configuration>::ApplyInitialContentsNonChunks(WriteSerialiser &ser)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
for(auto it = m_InitialContents.begin(); it != m_InitialContents.end(); ++it)
{
ResourceId id = it->first;
if(m_FrameReferencedResources.find(id) == m_FrameReferencedResources.end() &&
!RenderDoc::Inst().GetCaptureOptions().refAllResources)
{
continue;
}
RecordType *record = GetResourceRecord(id);
if(!record || record->InternalResource)
continue;
if(!Need_InitialStateChunk(id, it->second.data))
Serialise_InitialState(ser, id, record, &it->second.data);
}
}
template <typename Configuration>
void ResourceManager<Configuration>::ClearReferencedResources()
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
for(auto it = m_FrameReferencedResources.begin(); it != m_FrameReferencedResources.end(); ++it)
{
RecordType *record = GetResourceRecord(it->first);
if(record)
{
if(IncludesWrite(it->second))
MarkDirtyResource(it->first);
record->Delete(this);
}
}
m_FrameReferencedResources.clear();
}
template <typename Configuration>
void ResourceManager<Configuration>::ReplaceResource(ResourceId from, ResourceId to)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(HasLiveResource(to))
{
m_Replacements[from] = to;
m_Replaced[to] = from;
}
}
template <typename Configuration>
bool ResourceManager<Configuration>::HasReplacement(ResourceId from)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
return m_Replacements.find(from) != m_Replacements.end();
}
template <typename Configuration>
void ResourceManager<Configuration>::RemoveReplacement(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
auto it = m_Replacements.find(id);
if(it == m_Replacements.end())
return;
m_Replaced.erase(it->second);
m_Replacements.erase(it);
}
template <typename Configuration>
typename Configuration::RecordType *ResourceManager<Configuration>::GetResourceRecord(ResourceId id)
{
SCOPED_READLOCK(m_ResourceRecordLock);
auto it = m_ResourceRecords.find(id);
if(it == m_ResourceRecords.end())
return NULL;
return it->second;
}
template <typename Configuration>
bool ResourceManager<Configuration>::HasResourceRecord(ResourceId id)
{
SCOPED_READLOCK(m_ResourceRecordLock);
auto it = m_ResourceRecords.find(id);
if(it == m_ResourceRecords.end())
return false;
return true;
}
template <typename Configuration>
typename Configuration::RecordType *ResourceManager<Configuration>::AddResourceRecord(ResourceId id)
{
SCOPED_WRITELOCK(m_ResourceRecordLock);
RDCASSERT(m_ResourceRecords.find(id) == m_ResourceRecords.end(), id);
return (m_ResourceRecords[id] = new RecordType(id));
}
template <typename Configuration>
void ResourceManager<Configuration>::RemoveResourceRecord(ResourceId id)
{
SCOPED_WRITELOCK(m_ResourceRecordLock);
RDCASSERT(m_ResourceRecords.find(id) != m_ResourceRecords.end(), id);
m_ResourceRecords.erase(id);
}
template <typename Configuration>
void ResourceManager<Configuration>::DestroyResourceRecord(ResourceRecord *record)
{
delete(RecordType *)record;
}
template <typename Configuration>
bool ResourceManager<Configuration>::AddWrapper(WrappedResourceType wrap, RealResourceType real)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
bool ret = true;
if(wrap == (WrappedResourceType)RecordType::NullResource ||
real == (RealResourceType)RecordType::NullResource)
{
RDCERR("Invalid state creating resource wrapper - wrapped or real resource is NULL");
ret = false;
}
if(m_WrapperMap[real] != (WrappedResourceType)RecordType::NullResource)
{
RDCERR("Overriding wrapper for resource");
ret = false;
}
m_WrapperMap[real] = wrap;
return ret;
}
template <typename Configuration>
void ResourceManager<Configuration>::RemoveWrapper(RealResourceType real)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(real == (RealResourceType)RecordType::NullResource || !HasWrapper(real))
{
RDCERR(
"Invalid state removing resource wrapper - real resource is NULL or doesn't have wrapper");
return;
}
m_WrapperMap.erase(m_WrapperMap.find(real));
}
template <typename Configuration>
bool ResourceManager<Configuration>::HasWrapper(RealResourceType real)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(real == (RealResourceType)RecordType::NullResource)
return false;
return (m_WrapperMap.find(real) != m_WrapperMap.end());
}
template <typename Configuration>
typename Configuration::WrappedResourceType ResourceManager<Configuration>::GetWrapper(
RealResourceType real)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(real == (RealResourceType)RecordType::NullResource)
return (WrappedResourceType)RecordType::NullResource;
if(real != (RealResourceType)RecordType::NullResource && !HasWrapper(real))
{
RDCERR(
"Invalid state removing resource wrapper - real resource isn't NULL and doesn't have "
"wrapper");
}
return m_WrapperMap[real];
}
template <typename Configuration>
void ResourceManager<Configuration>::AddLiveResource(ResourceId origid, WrappedResourceType livePtr)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(origid == ResourceId() || livePtr == (WrappedResourceType)RecordType::NullResource)
{
RDCERR("Invalid state adding resource mapping - id is invalid or live pointer is NULL");
}
m_OriginalIDs[GetID(livePtr)] = origid;
m_LiveIDs[origid] = GetID(livePtr);
if(m_LiveResourceMap.find(origid) != m_LiveResourceMap.end())
{
RDCERR("Releasing live resource for duplicate creation: %s", ToStr(origid).c_str());
ResourceTypeRelease(m_LiveResourceMap[origid]);
m_LiveResourceMap.erase(origid);
}
m_LiveResourceMap[origid] = livePtr;
}
template <typename Configuration>
bool ResourceManager<Configuration>::HasLiveResource(ResourceId origid)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(origid == ResourceId())
return false;
return (m_Replacements.find(origid) != m_Replacements.end() ||
m_LiveResourceMap.find(origid) != m_LiveResourceMap.end());
}
template <typename Configuration>
typename Configuration::WrappedResourceType ResourceManager<Configuration>::GetLiveResource(
ResourceId origid)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(origid == ResourceId())
return (WrappedResourceType)RecordType::NullResource;
RDCASSERT(HasLiveResource(origid), origid);
if(m_Replacements.find(origid) != m_Replacements.end())
return GetLiveResource(m_Replacements[origid]);
if(m_LiveResourceMap.find(origid) != m_LiveResourceMap.end())
return m_LiveResourceMap[origid];
return (WrappedResourceType)RecordType::NullResource;
}
template <typename Configuration>
void ResourceManager<Configuration>::EraseLiveResource(ResourceId origid)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
RDCASSERT(HasLiveResource(origid), origid);
m_LiveResourceMap.erase(origid);
}
template <typename Configuration>
void ResourceManager<Configuration>::AddCurrentResource(ResourceId id, WrappedResourceType res)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
m_CurrentResourceMap[id] = res;
}
template <typename Configuration>
bool ResourceManager<Configuration>::HasCurrentResource(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
return m_CurrentResourceMap.find(id) != m_CurrentResourceMap.end();
}
template <typename Configuration>
typename Configuration::WrappedResourceType ResourceManager<Configuration>::GetCurrentResource(
ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
if(id == ResourceId())
return (WrappedResourceType)RecordType::NullResource;
if(m_Replacements.find(id) != m_Replacements.end())
return GetCurrentResource(m_Replacements[id]);
return m_CurrentResourceMap[id];
}
template <typename Configuration>
void ResourceManager<Configuration>::ReleaseCurrentResource(ResourceId id)
{
SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);
// We potentially need to prepare this resource on Active Capture,
// if it was postponed, but is about to go away.
if(IsActiveCapturing(m_State))
{
Prepare_InitialStateIfPostponed(id, true);
}
m_CurrentResourceMap.erase(id);
m_DirtyResources.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 <typename Configuration>
ResourceId ResourceManager<Configuration>::GetOriginalID(ResourceId id)
{
if(id == ResourceId())
return id;
RDCASSERT(m_OriginalIDs.find(id) != m_OriginalIDs.end(), id);
return m_OriginalIDs[id];
}
template <typename Configuration>
ResourceId ResourceManager<Configuration>::GetUnreplacedOriginalID(ResourceId id)
{
if(id == ResourceId())
return id;
if(m_Replaced.find(id) != m_Replaced.end())
return m_Replaced[id];
RDCASSERT(m_OriginalIDs.find(id) != m_OriginalIDs.end(), id);
return m_OriginalIDs[id];
}
template <typename Configuration>
ResourceId ResourceManager<Configuration>::GetLiveID(ResourceId id)
{
if(id == ResourceId())
return id;
RDCASSERT(m_LiveIDs.find(id) != m_LiveIDs.end(), id);
return m_LiveIDs[id];
}