// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "BinCache.h" #include "CoreMinimal.h" #include "Device/DeviceBuffer.h" #include "Helper/DataUtil.h" #include "RawBuffer.h" #include "TiledBlob.h" #include #include "Helper/Util.h" class Device; struct JobResult; typedef std::shared_ptr JobResultPtr; typedef cti::continuable AsyncJobResultPtr; typedef T_BlobRef BlobRef; typedef T_BlobRef TiledBlobRef; DECLARE_LOG_CATEGORY_EXTERN(LogBlob, Log, Verbose); typedef std::vector HashTypes; class JobArg; class TEXTUREGRAPHENGINE_API BlobberObserverSource { public: using HashArray = std::vector; void AddHash(HashType Hash); void RemapHash(HashType OldHash, HashType NewHash); protected: /// Protected interface of emitters called by the scheduler to notify the observers friend class Blobber; FCriticalSection ObserverLock; HashArray AddedHashStack; HashArray RemappedHashStack; uint32 Version = 1; /// Trigger the broadcast of the changes, this call is issued from the Blobber::Update function void Broadcast(); /// The customisable handler method triggered from Broadcast if any buffers were added or removed virtual void BlobberUpdated(HashArray&& InAddedHashes, HashArray&& InRemappedHashes) {} public: BlobberObserverSource() = default; virtual ~BlobberObserverSource() {} uint32 GetVersion() const { return Version; } /// The version of the current state of the Observer. Incremented when Broadcast trigger }; typedef std::shared_ptr BlobberObserverSourcePtr; struct BlobCacheOptions { bool Discard = false; /// Whether the BlobObj is part of a "discard" cycle (user tweaking usually) bool NoCacheBatch = false; /// Whether the batch is marked for no caching }; class TEXTUREGRAPHENGINE_API Blobber { friend class Blob; friend class DeviceTransferService; friend class Device; friend class DeviceBuffer; public: struct BlobCacheEntry { BlobPtr BlobObj; /// The actual BlobObj object within the cache entry BlobCacheOptions Options; /// The options associated with this BlobObj entry double CreateTimestamp = Util::Time(); /// The timestamp when this entry was created double AccessTimestamp = Util::Time(); /// The timestamp when this entry was last accessed ~BlobCacheEntry(); FORCEINLINE TiledBlobPtr AsTiledBlob() const { return std::static_pointer_cast(BlobObj); } }; using BlobCacheEntryPtr = std::shared_ptr; typedef BinCache BlobLRUCache; private: using BlobLUT = std::unordered_map; using HashMappingTable = std::unordered_map; using BlobCacheEntryPtrVec = std::vector; using CHashPtrVec = std::vector; //typedef std::map> TempHashTable; mutable FCriticalSection BlobLookupLock; /// Mutex for the actions (mutable because FScopeLock needs non-const object) BlobLUT BlobLookup; /// The buffer cache that we have BlobCacheEntryPtrVec TransientBlobs; /// List of transient blobs BlobLRUCache BlobCache; /// Blob cache used for lookups and saving CHashPtrVec TouchedHashes; /// Hashes that have been touched since the last idle update double LastIdleUpdateTimestamp = 0;/// The last time the idle loop was run double InvalidateTimestamp = 0; /// The last time the blobber cache was invalidated size_t MemUsed = 0; /// How much memory are we using size_t MemUsedNative = 0; /// Native memory used for all the blobs within the system double TimeStatsPrinted = 0; /// The last time when stats were printed bool ShouldPrintStats = false; /// Whether to print stats all all FCriticalSection HashMutex; /// Mutex for the hashing tables HashMappingTable GlobalHashes; /// All the globally unique hashes within the system. While the Hash object itself /// can be created as and when needed, these are hashes that are used to detect /// duplicate data and jobs. These can evolve from temp to final hashes and can /// change many times during the course of the mix and application. In order to ease /// the burden of updating too many Hash tables within the system. We keep a globally /// unique mapping from the Hash value (HashType) to a Hash pointer. This Hash pointer /// is unique within the system and can only be one for one of such hashes. /// The benefit that we get is that once a temp Hash is updated internally within the /// CHash object. We just update the value from the old mapping to the new mapping /// and we won't have to modify any other mappings within the Blobber structure since /// all the RHS values in the Hash table are CHashPtr types. HashMappingTable HashMappings; /// These are mappings from one Hash to another. The LHS is the Hash of the job /// and the RHS tells what (if any) that results in. Its entirely possible /// that we don't have that mapped BlobObj in the cache or within the system /// as it may have garbage collected bool bEnableCache = true; /// Enable and disable caching within the TextureGraph system #if UE_BUILD_DEBUG bool EnableGC = true; /// This is for debugging purposes only. Never turn it off otherwise public: FORCEINLINE void SetEnableGC(bool InEnableGC) { EnableGC = InEnableGC; } FORCEINLINE bool IsGCEnabled() const { return EnableGC; } #endif /// UE_BUILD_DEBUG #if DEBUG_BLOB_REF_KEEPING == 1 struct DebugRefBlob { int32 RefCount = 0; std::vector JobArgs; }; typedef std::unordered_map RefBlobMap; mutable FCriticalSection ReferencedBlobsLock; /// Mutex for the referenced blob LUT RefBlobMap ReferencedBlobs; /// Blobs that are referenced (Debug only!) public: void AddReferencedBlob(Blob* BlobObj, JobArg* Arg); void RemoveReferencedBlob(Blob* BlobObj, JobArg* Arg); bool IsBlobReferenced(Blob* BlobObj); private: #endif BlobberObserverSourcePtr ObserverSource; /// Observer where the lifecycle of the blobs and hashes is recorded #if DEBUG_BLOB_REF_KEEPING == 1 FCriticalSection DebugBlobMutex; /// The TiledBlobs that contain this blob as a tile #endif void AddHashMapping(HashType LHS, CHashPtr RHS); void AddBlobEntry(HashType Hash, BlobPtr BlobObj, BlobCacheOptions Options); void AddBlobEntryThreadSafe(HashType Hash, BlobPtr BlobObj, BlobCacheOptions Options); void RemoveHashMapping(HashType Hash); void PrintStats(); BlobRef AddInternal(BlobPtr BlobObj, BlobCacheOptions Options); BlobPtr Remove(HashType Hash); CHashPtr AddGloballyUniqueHashInternal(CHashPtr Hash); void UpdateGloballyUniqueHashInternal(HashType OldValue); void UpdateMappingInternal(HashType OldValue, CHashPtr NewValue); void UpdateBlobLookupInternal(HashType OldValue, BlobPtr BlobObj); void Touch(const Blob* BlobObj); BlobCacheEntryPtr FindInternal(HashType Hash); BlobRef AddResult(CHashPtr LHash, BlobRef Result, BlobCacheOptions Options); void UpdateTransient(); void UpdateTouchedHashes(); void UpdateBlobCache(); public: Blobber(); ~Blobber(); BlobRef FindSingle(HashType Hash); TiledBlobRef FindTiled(HashType Hash); BlobRef Create(const BufferDescriptor& Desc); BlobRef Create(Device* Dev, RawBufferPtr Raw); BlobRef Create(DeviceBufferRef DevBuffer, bool NoCache = false); BlobRef AddResult(CHashPtr LHash, BlobPtr InBlob, BlobCacheOptions Options = {}); void ClearCache(); /// In order to minimise the changes brought upon by weak reference work we've made this function. /// If we were to force this to take a unique_ptr it would've resulted in lots of changes /// around asset manager and other bits of code (Jobs etc), that we can do without. /// Eventually, this should either go away, or be converted to accept a unique pointer to force stop /// shared_ptr being passed around TiledBlobRef AddTiledResult(CHashPtr LHash, TiledBlobPtr Result, BlobCacheOptions Options = {}); TiledBlobRef AddTiledResult(TiledBlobPtr Result, BlobCacheOptions Options = {}); void UpdateBlobHash(HashType OldHash, BlobRef InBlob); void UpdateHash(HashType OldHash, CHashPtr NewHash); void Update(float DT); AsyncJobResultPtr UpdateIdle(); bool IsBlobCached(HashType Hash); void RegisterObserverSource(const BlobberObserverSourcePtr& observerSource); /// template T_BlobRef Find(HashType Hash) { BlobCacheEntryPtr Entry = FindInternal(Hash); if (Entry == nullptr) return nullptr; return T_BlobRef(std::static_pointer_cast(Entry->BlobObj)); } template <> BlobRef Find(HashType Hash) { return FindSingle(Hash); } template <> TiledBlobRef Find(HashType Hash) { return FindTiled(Hash); } BlobRef Find(HashType Hash) { return Find(Hash); } ////////////////////////////////////////////////////////////////////////// /// Inline functions ////////////////////////////////////////////////////////////////////////// FORCEINLINE CHashPtr AddGloballyUniqueHash(CHashPtr Hash) { FScopeLock lock(&HashMutex); return AddGloballyUniqueHashInternal(Hash); } FORCEINLINE void SetEnableCache(bool InEnableCache) { bEnableCache = InEnableCache; } FORCEINLINE bool IsCacheEnabled() const { return bEnableCache; } FORCEINLINE BlobberObserverSourcePtr GetObserverSource() const { return ObserverSource; } #if DEBUG_BLOB_REF_KEEPING == 1 FCriticalSection* GetDebugBlobMutex() { return &DebugBlobMutex; } #endif }; typedef std::unique_ptr BlobberPtr; //////////////////////////////////////////////////////////////////////////////////////////// template void T_BlobRef::CheckShouldKeepStrongRef() { #if BLOB_REF_SIMPLE == 0 /// If its already set then don't care if (KeepStrong || !Hash) return; /// If its a transient blob then keep the reference if (PtrWeak.lock()->IsTransient()) { KeepStrong = true; return; } /// Try to find this blob in the cache T_BlobRef CachedBlob = TextureGraphEngine::Blobber()->Find(Hash->Value()); /// If we can't find it in the cache if (!CachedBlob) KeepStrong = true; #endif } template std::shared_ptr T_BlobRef::get() const { #if BLOB_REF_SIMPLE == 0 if (PtrStrong) return CheckReleaseStrongRef(); /// If the pointer hasn't expired then we get the if (!PtrWeak.expired()) { auto SPtr = PtrWeak.lock(); /// Must have a valid hash pointer at the very least at this point check(Hash); /// always try to update the hash to the latest if it's not final if (!Hash->IsFinal()) Hash = SPtr->Hash(); return SPtr; } /// If there's no hash, then just return if (!Hash) return nullptr; /// This can happen when the engine is being destroyed if (!TextureGraphEngine::Blobber()) return nullptr; /// If we don't have this object anymore, then we need /// to access it from the blobber T_BlobRef CachedBlob = TextureGraphEngine::Blobber()->Find(Hash->Value()); check(CachedBlob); /// Update the pointer and the hash *this = CachedBlob; return std::static_pointer_cast(PtrWeak.lock()); #else return PtrStrong; #endif }