Files
2025-05-18 13:04:45 +08:00

1060 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Blobber.h"
#include "Device/Device.h"
#include "TextureGraphEngine.h"
#include "TextureGraphEngineGameInstance.h"
#include "Device/DeviceManager.h"
#include "Job/TempHashService.h"
#include "Job/BlobHasherService.h"
#include "Job/Scheduler.h"
#include "Blob.h"
#include "TiledBlob.h"
#include "FxMat/RenderMaterial_Thumbnail.h"
DEFINE_LOG_CATEGORY(LogBlob);
DECLARE_CYCLE_STAT(TEXT("Blobber_Update"), STAT_Blobber_Update, STATGROUP_TextureGraphEngine);
DECLARE_CYCLE_STAT(TEXT("Blobber_Find"), STAT_Blobber_Find, STATGROUP_TextureGraphEngine);
Blobber::Blobber()
{
ObserverSource = std::make_shared<BlobberObserverSource>();
ShouldPrintStats = false;
}
Blobber::~Blobber()
{
TransientBlobs.clear();
BlobCache.Empty();
}
Blobber::BlobCacheEntryPtr Blobber::FindInternal(HashType Hash)
{
if (!bEnableCache)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("CACHING DISABLED!"));
return nullptr;
}
if (TextureGraphEngine::IsDestroying())
return nullptr;
SCOPE_CYCLE_COUNTER(STAT_Blobber_Find);
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Internal: %#016lx"), Hash);
FScopeLock CacheLock(&BlobLookupLock);
HashType OrgHash = Hash;
static const int32 MaxIter = 4;
int32 NumIter = 0;
while (Hash != DataUtil::GNullHash && NumIter++ < MaxIter)
{
check(Hash != DataUtil::GNullHash);
const BlobCacheEntryPtr* BlobEntry = BlobCache.Find(Hash, false);
/// if we already have something then we just return that
if (BlobEntry != nullptr)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Internal: %#016llu => %s"), Hash, *(*BlobEntry)->BlobObj->Name());
return *BlobEntry;
}
/// Otherwise see if its in the mappings
{
FScopeLock HashLock(&HashMutex);
auto HashIter = HashMappings.find(Hash);
/// if no mapping was found
/// then just ignore it ...
if (HashIter == HashMappings.end())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Internal: %#016lx => NONE"), Hash);
return nullptr;
}
/// there's a cyclical Hash link, then ignore it as well
if (HashIter->second->Value() == Hash)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Internal: %#016lx => NONE"), Hash);
return nullptr;
}
/// Otherwise, we need to use the RHS of the mapping
CHashPtr MappedHash = HashIter->second;
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Internal Remapped: %#016llu => %#016llu"), Hash, MappedHash->Value());
if (Hash == *MappedHash)
{
HashMappings.erase(HashIter);
return nullptr;
}
check(Hash != *MappedHash);
Hash = MappedHash->Value();
/// This is a cyclical link
check(Hash != OrgHash);
}
}
return nullptr;
}
bool Blobber::IsBlobCached(HashType Hash)
{
BlobCacheEntryPtr Entry = FindInternal(Hash);
return Entry != nullptr;
}
BlobRef Blobber::FindSingle(HashType Hash)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Single: %#016lx"), Hash);
BlobCacheEntryPtr Entry = FindInternal(Hash);
if (Entry)
{
if (Entry->BlobObj->IsTiled())
{
/// This has to be either a 1x1 tiled BlobObj OR
TiledBlobPtr TiledResult = Entry->AsTiledBlob();
if (TiledResult->Rows() == 1 && TiledResult->Cols() == 1)
{
BlobPtr Tile = TiledResult->GetTile(0, 0);
check(Tile);
return Tile;
}
/// A multi tiled BlobObj with exactly the same
/// image in all the Tiles
/// In this case we return null as a single BlobObj must be created
return BlobRef(std::static_pointer_cast<Blob>(TiledResult), false);
}
return BlobRef(Entry->BlobObj, false);
}
return BlobRef();
}
TiledBlobRef Blobber::FindTiled(HashType Hash)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Find_Tiled: %#016lx"), Hash);
BlobCacheEntryPtr Entry = FindInternal(Hash);
if (Entry)
{
if (!Entry->BlobObj->IsTiled())
{
BlobPtrTiles Tiles(1, 1);
Tiles[0][0] = BlobRef(Entry->BlobObj);
/// Make the simple BlobObj into a 1x1 tiled BlobObj
return TiledBlobRef(std::make_shared<TiledBlob>(Entry->BlobObj->GetDescriptor(), Tiles), true, false);
}
return TiledBlobRef(Entry->AsTiledBlob(), false);
}
return TiledBlobRef();
}
void Blobber::UpdateTransient()
{
auto Iter = TransientBlobs.begin();
while (Iter != TransientBlobs.end())
{
BlobCacheEntryPtr& Entry = *Iter;
// If this is the only entry then we need to remove it
if (Entry->BlobObj.use_count() == 1)
{
// remove this blob
auto Hash = Entry->BlobObj->Hash()->Value();
UE_LOG(LogBlob, VeryVerbose, TEXT("Removing bIsTransient blob %s (Hash: %llu)"), *Entry->BlobObj->DisplayName(), Hash);
BlobCache.Remove(Hash);
Iter = TransientBlobs.erase(Iter);
}
else
{
++Iter;
}
}
}
void Blobber::UpdateTouchedHashes()
{
check(IsInGameThread());
for (CHashPtr Hash : TouchedHashes)
{
auto HashValue = Hash->Value();
const BlobCacheEntryPtr* Entry = BlobCache.Find(HashValue, true);
if (Entry && *Entry)
{
BlobCache.Touch(HashValue);
(*Entry)->AccessTimestamp = Util::Time();
auto BlobObj = (*Entry)->BlobObj;
if (BlobObj && BlobObj->GetBufferRef())
BlobObj->GetBufferRef()->GetOwnerDevice()->Touch(HashValue);
}
}
TouchedHashes.clear();
}
Blobber::BlobCacheEntry::~BlobCacheEntry()
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Deleting blob cache entry ..."));
}
void Blobber::UpdateBlobCache()
{
#if UE_BUILD_DEBUG
if (!EnableGC)
{
UE_LOG(LogBlob, Warning, TEXT("GC DISABLED!"));
return;
}
#endif /// UE_BUILD_DEBUG
check(IsInGameThread());
FScopeLock Lock(&BlobLookupLock);
auto Iter = BlobCache.GetCache().begin();
bool bStop = false;
HashTypeVec HashesToRemove;
int32 IterCount = 0;
double PrevTimestamp = -1;
while (Iter != BlobCache.GetCache().end() && !bStop)
{
BlobCacheEntryPtr& Entry = *Iter;
check(Entry && Entry->BlobObj);
// if the blob has a single ref count left then we can safely de-cache it
if (Entry->BlobObj.use_count() == 1)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Removing permanent blob %s (Hash: %llu)"), *Entry->BlobObj->DisplayName(), Entry->BlobObj->Hash()->Value());
HashesToRemove.push_back(Iter.Key());
HashTypeVec IntermediateHashes = Entry->BlobObj->Hash()->GetIntermediateHashes();
if (!IntermediateHashes.empty())
HashesToRemove.insert(HashesToRemove.end(), IntermediateHashes.begin(), IntermediateHashes.end());
if (Entry->BlobObj->Hash()->Value() != Iter.Key())
HashesToRemove.push_back(Entry->BlobObj->Hash()->Value());
}
else
{
// If the last blob had more than one ref count then any other blobs will certainly be more
// recently used. So we can safely stop iteration at this point
//bStop = true;
}
double CurrTimestamp = Entry->AccessTimestamp;
check(PrevTimestamp < 0 || PrevTimestamp <= CurrTimestamp);
PrevTimestamp = CurrTimestamp;
IterCount++;
++Iter;
}
if (!HashesToRemove.empty())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Removing num items from the cache: %llu"), HashesToRemove.size());
for (size_t RemoveIndex = 0; RemoveIndex < HashesToRemove.size(); RemoveIndex++)
{
HashType HashToRemove = HashesToRemove[RemoveIndex];
BlobCache.Remove(HashToRemove);
auto MappingIter = HashMappings.find(HashToRemove);
if (MappingIter != HashMappings.end())
HashMappings.erase(MappingIter);
}
}
}
AsyncJobResultPtr Blobber::UpdateIdle()
{
if (LastIdleUpdateTimestamp > InvalidateTimestamp)
return cti::make_ready_continuable<JobResultPtr>(std::make_shared<JobResult>());
// First we update the bIsTransient blobs. This will remove any unnecessary blobs from the cache
UpdateTransient();
// Make sure we are updating any touched hashes
UpdateTouchedHashes();
// Finally we're going to update the blob cache and see what we can remove
UpdateBlobCache();
// Print stats on Idle updates (Temporary and eventually cleaned up, but I want everyone to see the blobber cache for the time being)
PrintStats();
LastIdleUpdateTimestamp = Util::Time();
return cti::make_ready_continuable<JobResultPtr>(std::make_shared<JobResult>());
}
void Blobber::Update(float DT)
{
SCOPE_CYCLE_COUNTER(STAT_Blobber_Update);
//if (ShouldPrintStats)
//{
// float Delta = Util::TimeDelta(TimeStatsPrinted);
// if (Delta > Device::PrintStatsInterval)
// {
// PrintStats();
// }
//}
if (!TextureGraphEngine::IsTestMode())
ObserverSource->Broadcast();
}
void Blobber::PrintStats()
{
TimeStatsPrinted = Util::Time();
FScopeLock Lock(&BlobLookupLock);
size_t NumObjects = BlobCache.GetCache().Num();
/// we don't print stats for devices that have no objects at all
if (!NumObjects)
return;
size_t NumTransient = TransientBlobs.size();
size_t NumThumbnails = 0;
size_t NumLargeImages = 0;
float ThumbnailMemUsage = 0;
float LargeImageMemUsage = 0;
size_t NumBuffers = 0;
size_t NumTiled = 0;
auto Iter = BlobCache.GetCache().begin();
while (Iter != BlobCache.GetCache().end())
{
BlobCacheEntryPtr& Entry = *Iter;
BlobPtr BlobObj = Entry->BlobObj;
const BufferDescriptor& Desc = BlobObj->GetDescriptor();
float* MemUsage = &LargeImageMemUsage;
if (BlobObj->GetWidth() <= RenderMaterial_Thumbnail::GThumbWidth || BlobObj->GetHeight() <= RenderMaterial_Thumbnail::GThumbHeight)
{
NumThumbnails++;
MemUsage = &ThumbnailMemUsage;
}
else
NumLargeImages++;
if (BlobObj->IsTiled())
NumTiled++;
auto Buffer = BlobObj->GetBufferRef();
if (Buffer)
{
*MemUsage += (float)Buffer->MemSize();
NumBuffers++;
}
++Iter;
}
static constexpr float MBConv = 1024.0f * 1024.0f;
ThumbnailMemUsage = ThumbnailMemUsage / MBConv;
LargeImageMemUsage = LargeImageMemUsage / MBConv;
UE_LOG(LogBlob, VeryVerbose, TEXT("===== BEGIN Blobber STATS ====="));
UE_LOG(LogBlob, VeryVerbose, TEXT("Total Objects : %llu (Transient: %llu, Tiled: %llu)"), NumObjects, NumTransient, NumTiled);
UE_LOG(LogBlob, VeryVerbose, TEXT("Num Large Images : %llu (Thumbnails: %llu)"), NumLargeImages, NumThumbnails);
UE_LOG(LogBlob, VeryVerbose, TEXT("Large Image Mem : %0.2f MB (Thumbnail Mem: %0.2f MB)"), LargeImageMemUsage, ThumbnailMemUsage);
UE_LOG(LogBlob, VeryVerbose, TEXT("===== END Blobber STATS ====="));
}
BlobRef Blobber::Create(Device* Dev, RawBufferPtr Raw)
{
check(Dev);
check(Raw);
check(!Raw->Hash()->IsTemp());
DeviceBufferRef DevBuffer = Dev->Create(Raw);
return Create(DevBuffer);
}
BlobRef Blobber::Create(DeviceBufferRef DevBuffer, bool NoCache /* = false */)
{
check(!DevBuffer->Hash()->IsTemp());
BlobPtr BlobObj = std::make_shared<Blob>(DevBuffer);
return !NoCache ? AddInternal(BlobObj, BlobCacheOptions()) : BlobRef(BlobObj);
}
BlobRef Blobber::Create(const BufferDescriptor& Desc)
{
Device* NullDevice = TextureGraphEngine::GetDeviceManager()->GetDevice(DeviceType::Null);
RawBufferPtr Raw = std::make_shared<RawBuffer>(Desc);
DeviceBufferRef DevBuffer = NullDevice->Create(Raw);
BlobPtr BlobObj = std::make_shared<Blob>(DevBuffer);
return AddInternal(BlobObj, BlobCacheOptions());
}
void Blobber::UpdateGloballyUniqueHashInternal(HashType OldValue)
{
/// not thread-safe
auto Iter = GlobalHashes.find(OldValue);
if (Iter != GlobalHashes.end())
{
CHashPtr Hash = Iter->second;
if (Hash->Value() != OldValue)
{
/// Remove the pointer from the old index and make it point to the new value
GlobalHashes.erase(Iter);
GlobalHashes[Hash->Value()] = Hash;
}
}
}
void Blobber::ClearCache()
{
check(IsInGameThread());
check(TextureGraphEngine::IsTestMode()); /// Only allowed in test mode
FScopeLock Lock(&BlobLookupLock);
#if DEBUG_BLOB_REF_KEEPING == 1
FScopeLock RefLock(&ReferencedBlobsLock);
ReferencedBlobs.clear();
#endif
TransientBlobs.clear();
BlobCache.Empty();
HashMappings.clear();
GlobalHashes.clear();
for (size_t di = 0; di < TextureGraphEngine::GetDeviceManager()->GetNumDevices(); di++)
{
if (TextureGraphEngine::GetDeviceManager()->GetDevice(di))
TextureGraphEngine::GetDeviceManager()->GetDevice(di)->ClearCache();
}
}
#if DEBUG_BLOB_REF_KEEPING == 1
void Blobber::AddReferencedBlob(Blob* BlobObj, JobArg* Arg)
{
FScopeLock Lock(&ReferencedBlobsLock);
auto Iter = ReferencedBlobs.find(BlobObj);
if (Iter == ReferencedBlobs.end())
{
DebugRefBlob Ref;
Ref.RefCount = 1;
if (Arg)
Ref.JobArgs.push_back(Arg);
ReferencedBlobs[BlobObj] = Ref;
}
else
{
if (Arg)
Iter->second.JobArgs.push_back(Arg);
Iter->second.RefCount++;
}
}
void Blobber::RemoveReferencedBlob(Blob* BlobObj, JobArg* Arg)
{
FScopeLock Lock(&ReferencedBlobsLock);
auto Iter = ReferencedBlobs.find(BlobObj);
check(Iter != ReferencedBlobs.end());
Iter->second.RefCount--;
if (Arg)
{
auto ArgIter = std::find(Iter->second.JobArgs.begin(), Iter->second.JobArgs.end(), Arg);
if (ArgIter != Iter->second.JobArgs.end())
Iter->second.JobArgs.erase(ArgIter);
}
if (Iter->second.RefCount <= 0)
ReferencedBlobs.erase(Iter);
}
bool Blobber::IsBlobReferenced(Blob* BlobObj)
{
FScopeLock Lock(&ReferencedBlobsLock);
auto Iter = ReferencedBlobs.find(BlobObj);
//if (Iter != ReferencedBlobs.end())
//{
// /// Ok, check whether this is in the cache as another object
// BlobCacheEntry* Entry = Find_Internal(Hash);
// /// No entry found, then this is the last reference of this hash being deleted
// if (!Entry)
// return true;
// return Entry->BlobObj.get() == BlobObj ? true : false;
//}
//return false;
return Iter != ReferencedBlobs.end();
}
#endif
void Blobber::UpdateMappingInternal(HashType OldValue, CHashPtr NewValue)
{
check(IsInGameThread());
/// not thread-safe
auto Iter = HashMappings.find(OldValue);
if (Iter != HashMappings.end())
{
CHashPtr RHS = Iter->second;
/// If RHS is the same as the new value, then we don't need to do anything else
/// as the mapping already exists
if (*RHS == *NewValue)
return;
/// Add_Internal this as a mapping as well only if the underlying Hash has changed
/// and has been finalised
if (RHS->Value() != OldValue)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Hash Remapping: %llu => %llu => %llu"), OldValue, RHS->Value(), NewValue->Value());
AddHashMapping(RHS->Value(), NewValue);
}
}
UE_LOG(LogBlob, VeryVerbose, TEXT("Hash Remapping: %llu => %llu"), OldValue, NewValue->Value());
/// Put new has mapping for the old value
AddHashMapping(OldValue, NewValue);
}
void Blobber::UpdateBlobLookupInternal(HashType OldValue, BlobPtr BlobObj)
{
CHashPtr NewValue = BlobObj->Hash();
FScopeLock Lock(&BlobLookupLock);
const BlobCacheEntryPtr* OldEntry = BlobCache.Find(OldValue, false);
/// Remove the old BlobObj from the system
if (OldEntry)
{
check(*OldEntry);
if (BlobObj != (*OldEntry)->BlobObj || OldValue != NewValue->Value())
{
/// Copy the Options over before Remove call
auto Options = (*OldEntry)->Options;
BlobCache.Remove(OldValue);
/// Clear this out since this is certainly deleted now
OldEntry = nullptr;
AddBlobEntry(NewValue->Value(), BlobObj, Options);
}
}
}
void Blobber::UpdateBlobHash(HashType OldHash, BlobRef InBlob)
{
check(IsInGameThread());
BlobPtr BlobObj = InBlob.lock();
check(BlobObj);
CHashPtr Hash = BlobObj->Hash();
// TODO: I had to comment this check otherwise we would crash when using Insight and trying to represetn the BlobObj images
// check(Hash->IsFinal());
if (OldHash == Hash->Value())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Unchanged Hash passed for update: %llu. Ignoring ..."), OldHash);
return;
}
{
FScopeLock Lock(&HashMutex);
/// Here we check to see if there's an existing mapping for the final blob hash
if (Hash->IsFinal())
{
auto BlobHashIter = HashMappings.find(Hash->Value());
if (BlobHashIter != HashMappings.end())
HashMappings.erase(BlobHashIter);
}
UpdateGloballyUniqueHashInternal(OldHash);
UpdateMappingInternal(OldHash, Hash);
}
UpdateBlobLookupInternal(OldHash, BlobObj);
if (!TextureGraphEngine::IsTestMode())
ObserverSource->RemapHash(OldHash, Hash->Value());
}
void Blobber::UpdateHash(HashType OldHash, CHashPtr Hash)
{
check(IsInGameThread());
/// Both hashes have to be valid
check(OldHash != DataUtil::GNullHash && Hash->IsValid());
if (OldHash == Hash->Value())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Unchanged Hash passed for update: %llu. Ignoring ..."), OldHash);
return;
}
BlobCacheEntryPtr Entry = FindInternal(OldHash);
if (!Entry)
{
AddHashMapping(OldHash, Hash);
return;
}
BlobPtr BlobObj = Entry->BlobObj;
if (!BlobObj->IsTiled())
{
CHashPtr ExistingHash = BlobObj->Hash();
/// If the Existing Hash of the buffer is already final, then that means that the
/// BlobObj Hasher service has gotten to it before this. In this case we just add a
/// new mapping, as the buffer's Hash has already been finalised
if (ExistingHash->IsFinal())
{
AddHashMapping(Hash->Value(), ExistingHash);
AddHashMapping(OldHash, ExistingHash);
}
else
BlobObj->GetBufferRef()->SetHash(Hash);
}
else
{
TiledBlobPtr TiledBlobObj = std::static_pointer_cast<TiledBlob>(BlobObj);
TiledBlobObj->HashValue = Hash;
}
UpdateBlobHash(OldHash, BlobObj);
}
CHashPtr Blobber::AddGloballyUniqueHashInternal(CHashPtr Hash)
{
check(Hash);
/// For internal use only [not thread-safe]
auto Iter = GlobalHashes.find(Hash->Value());
if (Iter == GlobalHashes.end())
{
GlobalHashes[Hash->Value()] = Hash;
return Hash;
}
return Iter->second;
}
void Blobber::Touch(const Blob* BlobObj)
{
check(IsInGameThread());
CHashPtr Hash = BlobObj->Hash();
TouchedHashes.push_back(Hash);
InvalidateTimestamp = Util::Time();
// /// Don't wanna do anything if its not the final Hash
// if (!Hash || !Hash->IsFinal())
// return;
/// We also don't touch tiled buffers
#if 0
if (BlobObj->IsTiled())
return;
#endif /// Deprecate
#if 0
size_t numDevices = Engine::DVM()->NumDevices();
HashType blobHash = Hash->Value();
for (size_t dvi = 0; dvi < numDevices; dvi++)
{
Device* device = Engine::DVM()->GetDevice(dvi);
if (device)
device->Touch(blobHash);
}
#endif
}
void Blobber::AddHashMapping(HashType LHS, CHashPtr RHS)
{
/// Prevent duplicate from being added
if (LHS == RHS->Value())
return;
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber AddHashMapping: %#016lx => %#016lx"), LHS, RHS->Value());
auto ExistingMapping = HashMappings.find(RHS->Value());
/// Check that no cyclical value can exist
if(!(ExistingMapping == HashMappings.end() || ExistingMapping->second->Value() != LHS))
{
return;
}
/// TODO: Check for cyclical references as well
HashMappings[LHS] = RHS;
}
TiledBlobRef Blobber::AddTiledResult(TiledBlobPtr Result, BlobCacheOptions Options /* = {} */)
{
check(Result);
CHashPtr LHash = Result->Hash();
return AddTiledResult(LHash, Result, Options);
}
TiledBlobRef Blobber::AddTiledResult(CHashPtr LHash, TiledBlobPtr Result, BlobCacheOptions Options /* = {} */)
{
BlobRef Ret = AddResult(LHash, std::static_pointer_cast<Blob>(Result), Options);
check(Ret->IsTiled());
TiledBlobPtr CachedPtr = std::static_pointer_cast<TiledBlob>(Ret.get());
check(CachedPtr->IsTiled());
if (CachedPtr && Result != CachedPtr)
{
CachedPtr->AddLinkedBlob(Result);
}
/// If we got a different pointer back from the blobber then that means that this result was already cached into
/// the system. We need to copy it over to the original pointer if it's a finalised blob
/// so that anyone keeping a copy of Result has the exact same view as the original result
/// and we don't have do any awkward pointer adjustment shenanigans to make things work
//if (Result && CachedPtr->IsFinalised() && Result != CachedPtr)
//{
// /// Copy the contents over
// *Result = *CachedPtr;
//}
return TiledBlobRef(std::static_pointer_cast<TiledBlob>(CachedPtr), Ret.IsKeepStrong(), false);
}
BlobRef Blobber::AddResult(CHashPtr LHash, BlobRef Result, BlobCacheOptions Options)
{
CHashPtr RHash = Result->Hash();
check(RHash && RHash->IsValid());
if (*RHash != *LHash)
{
FScopeLock Lock(&HashMutex);
auto Iter = HashMappings.find(LHash->Value());
/// if this didn't exist exist before then we add it
if (Iter == HashMappings.end())
{
/// Add_Internal the mapping
AddHashMapping(LHash->Value(), RHash);
if (!TextureGraphEngine::IsTestMode())
ObserverSource->AddHash(LHash->Value());
}
else
{
CHashPtr ExistingRHash = Iter->second;
/// Make sure that the BlobObj has the same RHash
if (ExistingRHash != RHash && *ExistingRHash != *RHash)
{
/// There shouldn't be an Existing mapping for RHash
//check(HashMappings.find(RHash->Value()) == HashMappings.end() || *ExistingRHash == *RHash);
/// Ok here's another mapping that we need to add
AddHashMapping(RHash->Value(), ExistingRHash);
/// We only need to set this for single blobs
if (!Result->IsTiled())
{
/// replace with the pointer with the oldest one that we've had
Result->GetBufferRef()->SetHash(ExistingRHash);
}
}
}
}
return Result;
}
BlobRef Blobber::AddResult(CHashPtr LHash, BlobPtr InBlob, BlobCacheOptions Options /* = {} */)
{
UE_LOG(LogBlob, VeryVerbose, TEXT("AddResult: %#016lx => %s"), LHash->Value(), *InBlob->Name());
check(LHash && LHash->IsValid());
check(InBlob);
BlobRef Result = AddInternal(InBlob, Options);
check(Result.get());
return AddResult(LHash, Result, Options);
}
BlobPtr Blobber::Remove(HashType Hash)
{
FScopeLock Lock(&BlobLookupLock);
BlobCacheEntryPtr Entry = BlobCache.Remove(Hash);
auto BlobObj = Entry->BlobObj;
return BlobObj;
}
void Blobber::AddBlobEntryThreadSafe(HashType Hash, BlobPtr BlobObj, BlobCacheOptions Options)
{
if (!bEnableCache)
return;
FScopeLock Lock(&BlobLookupLock);
AddBlobEntry(Hash, BlobObj, Options);
}
void Blobber::AddBlobEntry(HashType Hash, BlobPtr BlobObj, BlobCacheOptions Options)
{
if (!bEnableCache)
return;
#if DEBUG_BLOB_REF_KEEPING == 1
AddReferencedBlob(BlobObj.get(), nullptr);
#endif
/// Sanity check that we're not adding this multiple times
const BlobCacheEntryPtr* ExistingEntry = BlobCache.Find(Hash, false);
if (ExistingEntry && (*ExistingEntry)->BlobObj == BlobObj)
return;
check(!ExistingEntry || (*ExistingEntry)->BlobObj != BlobObj);
BlobCacheEntryPtr Entry = std::make_shared<BlobCacheEntry>();
Entry->BlobObj = BlobObj;
Entry->Options = Options;
Entry->CreateTimestamp = Util::Time();
Entry->AccessTimestamp = Entry->CreateTimestamp;
if (Options.Discard || Options.NoCacheBatch || BlobObj->IsTransient())
TransientBlobs.push_back(Entry);
BlobCache.Insert(Hash, Entry);
InvalidateTimestamp = Util::Time();
}
void Blobber::RemoveHashMapping(HashType Hash)
{
UE_LOG(LogBlob, Warning, TEXT("Removing incorrect hash mapping: %#016lx"), Hash);
FScopeLock HashLock(&HashMutex);
auto HashIter = HashMappings.find(Hash);
/// if no mapping was found
/// then just ignore it ...
if (HashIter == HashMappings.end())
return;
/// there's a cyclical Hash link, then ignore it as well
if (HashIter->second->Value() == Hash)
return;
/// Otherwise, we need to use the RHS of the mapping
CHashPtr MappedHash = HashIter->second;
HashMappings.erase(HashIter);
Hash = MappedHash->Value();
}
BlobRef Blobber::AddInternal(BlobPtr BlobObj, BlobCacheOptions Options)
{
CHashPtr Hash = BlobObj->Hash();
check(Hash && !Hash->IsNull());
UE_LOG(LogBlob, VeryVerbose, TEXT("Blobber Add_Internal BlobObj: %#016lx => %s"), Hash->Value(), *BlobObj->Name());
BlobRef Existing = Find(Hash->Value());
/// if we already have something then we just return that
if (Existing)
{
/// Make sure whether tiled and un-tiled information should match up
if (BlobObj->IsTiled() != Existing->IsTiled())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Tiled and non-tiled Result mix-up: %s"), *BlobObj->Name());
/// Two scenarios to consider here.
/// 1. If the incoming BlobObj is a TiledBlob but the Existing one isn't
if (BlobObj->IsTiled() && !Existing->IsTiled())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Incoming BlobObj is tiled and Existing isn't: %s"), *BlobObj->Name());
TiledBlobPtr TiledBlobObj = std::static_pointer_cast<TiledBlob>(BlobObj);
/// Two further scenarios here:
/// 1. Ok, if the incoming BlobObj is a 1x1 tiled BlobObj then we can do the simple handling
/// of replacing the simple BlobObj with a tiled BlobObj.
if (TiledBlobObj->Rows() == 1 && TiledBlobObj->Cols() == 1)
{
TiledBlobObj->SetTile(0, 0, Existing);
return BlobRef(std::static_pointer_cast<Blob>(TiledBlobObj), true, false);
}
/// We need to remove this hash mapping
RemoveHashMapping(Hash->Value());
Existing = BlobRef();
}
/// 2. If the incoming BlobObj is un-tiled but the Existing one IS tiled
else if (!BlobObj->IsTiled() && Existing->IsTiled())
{
UE_LOG(LogBlob, VeryVerbose, TEXT("Incoming BlobObj is non-tiled and Existing is: %s"), *BlobObj->Name());
TiledBlobPtr ExistingTiled = std::static_pointer_cast<TiledBlob>(Existing.lock());
/// The hashes can only match up if the Existing (tiled) BlobObj is 1x1
if (ExistingTiled->Rows() == 1 && ExistingTiled->Cols() == 1)
{
//check(ExistingTiled->Rows() == 1 && ExistingTiled->Cols() == 1);
//check(*ExistingTiled->GetTile(0, 0)->Hash() == *Hash);
return ExistingTiled->GetTile(0, 0).lock();
}
/// We need to remove this hash mapping
RemoveHashMapping(Hash->Value());
Existing = BlobRef();
}
}
if (Existing)
{
check(BlobObj->IsTiled() == Existing->IsTiled());
return Existing;
}
}
/// Don't do this with temp hashes of blobs because they could be the same as
/// the job hashes and may Result in the two being linked perpetually
if (Hash->IsFinal())
{
CHashPtr UniqueHash = AddGloballyUniqueHash(Hash);
if (UniqueHash != Hash)
BlobObj->SetHash(UniqueHash);
}
/// Add it to the lookup
AddBlobEntryThreadSafe(Hash->Value(), BlobObj, BlobCacheOptions());
if (!BlobObj->IsTiled())
{
bool bIsTransient = BlobObj->GetBufferRef() ? BlobObj->GetBufferRef()->Descriptor().bIsTransient : false;
if (!bIsTransient && !Options.Discard && !Options.NoCacheBatch && !BlobObj->IsTiled())
{
if (BlobObj->GetBufferRef() && !Hash->IsFinal())
{
BlobHasherServicePtr BlobHasher = TextureGraphEngine::GetScheduler()->GetBlobHasherService().lock();
if (BlobHasher)
{
BlobHasher->Add(BlobRef(BlobObj, true, false));
}
}
UE_LOG(LogBlob, Verbose, TEXT("Added BlobObj with Hash: %llu [Name: %s]"), Hash->Value(), *BlobObj->Name());
}
}
return BlobObj;
}
// BlobRef Blobber::Add_Internal(BlobUPtr Blob_, BlobCacheOptions Options)
// {
// BlobPtr BlobObj = std::move(Blob_);
// return Add_Internal(BlobObj, Options);
// }
void Blobber::RegisterObserverSource(const BlobberObserverSourcePtr& observerSource)
{
if (TextureGraphEngine::IsTestMode())
return;
if (observerSource)
{
ObserverSource = observerSource;
}
else
{
ObserverSource = std::make_shared<BlobberObserverSource>();
}
}
void BlobberObserverSource::AddHash(HashType Hash)
{
FScopeLock observerLock(&ObserverLock);
AddedHashStack.emplace_back(Hash);
}
void BlobberObserverSource::RemapHash(HashType OldHash, HashType NewHash)
{
FScopeLock observerLock(&ObserverLock);
RemappedHashStack.emplace_back(OldHash);
RemappedHashStack.emplace_back(NewHash);
}
void BlobberObserverSource::Broadcast()
{
HashArray addedHashes;
HashArray remappedHashes;
{ /// Just here, record the removed Hash value for observers
FScopeLock observerLock(&ObserverLock);
addedHashes = std::move(AddedHashStack);
remappedHashes = std::move(RemappedHashStack);
AddedHashStack.clear();
RemappedHashStack.clear();
}
if (addedHashes.size() || remappedHashes.size())
{
BlobberUpdated(std::move(addedHashes), std::move(remappedHashes));
Version++;
}
}