603 lines
14 KiB
C++
603 lines
14 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "Blob.h"
|
|
#include "Device/DeviceBuffer.h"
|
|
#include "TextureGraphEngine.h"
|
|
#include "Device/Mem/Device_Mem.h"
|
|
#include "Transform/BlobTransform.h"
|
|
#include "Device/DeviceManager.h"
|
|
#include "TextureGraphEngine.h"
|
|
#include "Device/Null/Device_Null.h"
|
|
#include "Model/Mix/Mix.h"
|
|
#include "Blobber.h"
|
|
|
|
#include "Job/JobBatch.h"
|
|
|
|
const char* Blob::LODTransformName = "LOD";
|
|
|
|
#if DEBUG_BLOB_REF_KEEPING == 1
|
|
DebugBlobLock::DebugBlobLock()
|
|
{
|
|
if (!TextureGraphEngine::Instance() || !TextureGraphEngine::Blobber())
|
|
return;
|
|
|
|
TextureGraphEngine::Blobber()->GetDebugBlobMutex()->Lock();
|
|
}
|
|
|
|
DebugBlobLock::~DebugBlobLock()
|
|
{
|
|
if (!TextureGraphEngine::Instance() || !TextureGraphEngine::Blobber())
|
|
return;
|
|
|
|
TextureGraphEngine::Blobber()->GetDebugBlobMutex()->Unlock();
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Blob::Blob() : Buffer(Device_Null::Get()->Create(BufferDescriptor(), nullptr))
|
|
{
|
|
}
|
|
|
|
Blob::Blob(DeviceBufferRef InBuffer)
|
|
: Buffer(InBuffer)
|
|
{
|
|
if (InBuffer && InBuffer->IsValid() && !InBuffer->IsNull())
|
|
{
|
|
bIsFinalised = true;
|
|
FinaliseTS = FDateTime::Now();
|
|
}
|
|
}
|
|
|
|
/// Allocate a NULL device buffer by default
|
|
Blob::Blob(const BufferDescriptor& InDesc, CHashPtr InHash) : Buffer(Device_Null::Get()->Create(InDesc, InHash))
|
|
{
|
|
}
|
|
|
|
Blob::~Blob()
|
|
{
|
|
#if DEBUG_BLOB_REF_KEEPING == 1
|
|
|
|
//if (!TextureGraphEngine::IsTestMode() && !TextureGraphEngine::IsDestroying())
|
|
//{
|
|
// DebugBlobLock lock;
|
|
|
|
// if (!_owners.empty())
|
|
// {
|
|
// for (size_t i = 0; i < _owners.size(); i++)
|
|
// {
|
|
// BlobPtrW tblobW = _owners[i];
|
|
|
|
// if (!tblobW.expired())
|
|
// {
|
|
// std::shared_ptr<TiledBlob> tblob = std::static_pointer_cast<TiledBlob>(tblobW.lock());
|
|
// check(!tblob->HasBlobAsTile(this));
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
//if (TextureGraphEngine::Blobber())
|
|
//{
|
|
// check(!TextureGraphEngine::Blobber()->IsBlobReferenced(this));
|
|
//}
|
|
#endif
|
|
}
|
|
|
|
#if DEBUG_BLOB_REF_KEEPING == 1
|
|
void Blob::AddTiledOwner(BlobPtrW owner)
|
|
{
|
|
if (HasOwner(owner))
|
|
return;
|
|
|
|
_owners.push_back(owner);
|
|
}
|
|
|
|
void Blob::AddOwner(BlobPtrW owner)
|
|
{
|
|
if (HasOwner(owner))
|
|
return;
|
|
|
|
_owners.push_back(owner);
|
|
}
|
|
|
|
void Blob::RemoveOwner(BlobPtrW owner)
|
|
{
|
|
auto iter = FindOwner(owner);
|
|
if (iter == _owners.end())
|
|
return;
|
|
|
|
_owners.erase(iter);
|
|
}
|
|
|
|
void Blob::RemoveOwner(Blob* owner)
|
|
{
|
|
auto iter = FindOwner(owner);
|
|
if (iter == _owners.end())
|
|
return;
|
|
|
|
_owners.erase(iter);
|
|
}
|
|
|
|
Blob::OwnerList::iterator Blob::FindOwner(BlobPtrW owner)
|
|
{
|
|
return std::find_if(_owners.begin(), _owners.end(), [&owner](const BlobPtrW& rhs)
|
|
{
|
|
return owner.lock() == rhs.lock();
|
|
});
|
|
}
|
|
|
|
Blob::OwnerList::iterator Blob::FindOwner(Blob* owner)
|
|
{
|
|
return std::find_if(_owners.begin(), _owners.end(), [&owner](const BlobPtrW& rhs)
|
|
{
|
|
return owner == rhs.lock().get();
|
|
});
|
|
}
|
|
|
|
bool Blob::HasOwner(Blob* owner)
|
|
{
|
|
return FindOwner(owner) != _owners.end();
|
|
}
|
|
|
|
bool Blob::HasOwner(BlobPtrW owner)
|
|
{
|
|
return FindOwner(owner) != _owners.end();
|
|
}
|
|
#endif
|
|
|
|
FString Blob::DisplayName() const
|
|
{
|
|
if (!Buffer)
|
|
return TEXT("");
|
|
return Buffer->GetName();
|
|
}
|
|
|
|
void Blob::Touch(uint64 BatchId)
|
|
{
|
|
// Just update the access info for the time being
|
|
if (Buffer)
|
|
{
|
|
Buffer->UpdateAccessInfo(BatchId);
|
|
|
|
// Don't touch the buffer here. This will be done in the blobber idle loop
|
|
// Buffer->Touch(BatchId);
|
|
TextureGraphEngine::GetBlobber()->Touch(this);
|
|
}
|
|
}
|
|
|
|
void Blob::UpdateAccessInfo(uint64 batchId)
|
|
{
|
|
if (Buffer)
|
|
Buffer->UpdateAccessInfo(batchId);
|
|
}
|
|
|
|
DeviceBufferRef Blob::GetBufferRef() const
|
|
{
|
|
return Buffer;
|
|
}
|
|
|
|
void Blob::SetHash(CHashPtr Hash)
|
|
{
|
|
if (Buffer)
|
|
Buffer->SetHash(Hash);
|
|
}
|
|
|
|
AsyncDeviceBufferRef Blob::TransferTo(Device* TargetDevice)
|
|
{
|
|
/// Must have a valid buffer here
|
|
check(Buffer && Buffer->IsValid());
|
|
|
|
DeviceBufferRef ExistingBuffer = Buffer;
|
|
return TargetDevice->Transfer(Buffer).then([this, TargetDevice, ExistingBuffer](DeviceBufferRef NewBuffer) mutable
|
|
{
|
|
check(NewBuffer);
|
|
check(NewBuffer != ExistingBuffer);
|
|
|
|
/// At this point, the old buffer can deallocate
|
|
Buffer = NewBuffer;
|
|
|
|
return Buffer;
|
|
});
|
|
}
|
|
|
|
AsyncBufferResultPtr Blob::Bind(const BlobTransform* Transform, const ResourceBindInfo& BindInfo)
|
|
{
|
|
/// Ok, we must have a RawObj buffer over here to transfer over to the device buffer
|
|
//check(_buffer);
|
|
|
|
Device* Dev = BindInfo.Dev;
|
|
if (!Dev)
|
|
Dev = Buffer->GetOwnerDevice();
|
|
|
|
check(Dev);
|
|
|
|
/// Touch this buffer if we're not tiled ... TiledBlob must touch its own
|
|
/// buffers because it involves touching all the tiles as well ... which may
|
|
/// be un-necessary in a lot of cases
|
|
if (!IsTiled())
|
|
Touch(BindInfo.BatchId);
|
|
|
|
/// If the buffer isn't compatible then we transfer it over to the other new device
|
|
if (!Buffer->IsCompatible(Dev))
|
|
{
|
|
return Dev->Transfer(Buffer).then([this, Transform, BindInfo](DeviceBufferRef Result)
|
|
{
|
|
Buffer = Result;
|
|
return Buffer->Bind(Transform, BindInfo);
|
|
});
|
|
}
|
|
|
|
/// Ok now we can actually bind this
|
|
return Buffer->Bind(Transform, BindInfo);
|
|
}
|
|
|
|
AsyncBufferResultPtr Blob::Unbind(const BlobTransform* Transform, const ResourceBindInfo& BindInfo)
|
|
{
|
|
return Buffer->Unbind(Transform, BindInfo);
|
|
}
|
|
|
|
bool Blob::IsValid() const
|
|
{
|
|
return Buffer->IsValid();
|
|
}
|
|
|
|
AsyncRawBufferPtr Blob::Raw()
|
|
{
|
|
/// Could possibly be going to another thread
|
|
/// Save the current temp Hash
|
|
CHashPtr PrevHash = Buffer->Hash(false);
|
|
|
|
return Buffer->Raw().then([this, PrevHash](RawBufferPtr RawObj)
|
|
{
|
|
const BufferDescriptor& BufferDesc = Buffer->Descriptor();
|
|
CHashPtr Hash = Buffer->Hash(false);
|
|
|
|
if (!BufferDesc.bIsTransient && Hash->IsFinal() && (!PrevHash || !PrevHash->IsFinal()))
|
|
Buffer = Buffer->GetOwnerDevice()->AddInternal(Buffer);
|
|
else
|
|
{
|
|
HashType PrevHashValue = PrevHash ? PrevHash->Value() : DataUtil::GNullHash;
|
|
UE_LOG(LogDevice, VeryVerbose, TEXT("DeviceBuffer has a new Hash without owning reference. Unless this is manually cached by the device, this buffer will be deleted which is undesirable. Name: %s, Hash: %llu [Prev Hash: %llu, Size: %dx%d]"),
|
|
*BufferDesc.Name, PrevHashValue, Hash->Value(), BufferDesc.Width, BufferDesc.Height);
|
|
}
|
|
|
|
return Buffer->Raw_Now();
|
|
});
|
|
}
|
|
|
|
AscynCHashPtr Blob::CalcHash()
|
|
{
|
|
/// If we already have a have a Hash for the buffer then don't bother
|
|
if (Buffer)
|
|
{
|
|
CHashPtr BufferHash = Buffer->Hash(false);
|
|
if (BufferHash && BufferHash->IsFinal())
|
|
return cti::make_ready_continuable(BufferHash);
|
|
}
|
|
|
|
return Raw().then([this]
|
|
{
|
|
return Buffer->Hash(false);
|
|
});
|
|
}
|
|
|
|
bool Blob::IsNull() const
|
|
{
|
|
return Buffer->IsNull();
|
|
}
|
|
|
|
CHashPtr Blob::Hash() const
|
|
{
|
|
/// Use own address as Hash. This will still ensure that we detect object re-use
|
|
/// even if they're late bound or haven't been calculated yet
|
|
return Buffer->Hash(false);
|
|
}
|
|
|
|
void Blob::OnFinaliseInternal(BlobReadyCallback Callback) const
|
|
{
|
|
Callback(this);
|
|
}
|
|
|
|
AsyncBlobResultPtr Blob::OnFinalise() const
|
|
{
|
|
/// If already finalised then just return a fulfilled promise
|
|
if (IsFinalised())
|
|
return cti::make_ready_continuable(this);
|
|
|
|
return cti::make_continuable<const Blob*>([this](auto&& Promise)
|
|
{
|
|
OnFinaliseInternal([this, FWD_PROMISE(Promise)](const Blob* BlobObj) mutable
|
|
{
|
|
Promise.set_value(BlobObj);
|
|
});
|
|
});
|
|
}
|
|
|
|
AsyncBufferResultPtr Blob::Flush(const ResourceBindInfo& BindInfo)
|
|
{
|
|
/// We've got nothing to
|
|
check(Buffer);
|
|
return Buffer->Flush(BindInfo);
|
|
}
|
|
|
|
void Blob::ResetBuffer()
|
|
{
|
|
if (Buffer)
|
|
{
|
|
/// Release the native buffer information
|
|
Buffer->ReleaseNative();
|
|
|
|
Buffer = Device_Null::Get()->Create(Buffer->Descriptor(), nullptr);
|
|
UE_LOG(LogData, Log, TEXT("Resetting buffer: %s"), *DisplayName());
|
|
}
|
|
else
|
|
Buffer = Device_Null::Get()->Create(BufferDescriptor(), nullptr);
|
|
}
|
|
|
|
AsyncPrepareResult Blob::PrepareForWrite(const ResourceBindInfo& BindInfo)
|
|
{
|
|
check(!bIsFinalised || BindInfo.bIsCombined);
|
|
check(Buffer);
|
|
|
|
if(BindInfo.bIsCombined)
|
|
Buffer->Desc = GetDescriptor();
|
|
|
|
return Buffer->PrepareForWrite(BindInfo);
|
|
}
|
|
|
|
bool Blob::HasMinMax() const
|
|
{
|
|
return (MinValue && MaxValue) || (MinMax != nullptr);
|
|
}
|
|
|
|
float Blob::GetMinValue() const
|
|
{
|
|
check(MinValue);
|
|
return *MinValue;
|
|
}
|
|
|
|
float Blob::GetMaxValue() const
|
|
{
|
|
check(MaxValue);
|
|
return *MaxValue;
|
|
}
|
|
|
|
BlobPtr Blob::GetMinMaxBlob()
|
|
{
|
|
check(MinMax);
|
|
return MinMax;
|
|
}
|
|
|
|
void Blob::SetMinMax(BlobPtr InMinMax)
|
|
{
|
|
MinMax = InMinMax;
|
|
|
|
InMinMax->OnFinalise()
|
|
.then([this](const Blob* FinalisedBlob)
|
|
{
|
|
check(FinalisedBlob);
|
|
return MinMax->Raw();
|
|
})
|
|
.then([this](RawBufferPtr Raw)
|
|
{
|
|
check(Raw);
|
|
const float* data = reinterpret_cast<const float*>(Raw->GetData());
|
|
|
|
check(data);
|
|
check(Raw->GetDescriptor().Width == 1 && Raw->GetDescriptor().Height == 1);
|
|
|
|
MinValue = std::make_shared<float>(data[0]);
|
|
MaxValue = std::make_shared<float>(data[1]);
|
|
|
|
/// We don't need to keep this anymore
|
|
MinMax = nullptr;
|
|
});
|
|
}
|
|
|
|
int32 Blob::NumLODLevels() const
|
|
{
|
|
return (int32)LODLevels.size();
|
|
}
|
|
|
|
bool Blob::HasLODLevels() const
|
|
{
|
|
return LODLevels.size() > 0;
|
|
}
|
|
|
|
bool Blob::HasLODLevel(int32 Index) const
|
|
{
|
|
return Index >= 0 && Index <= (int32)LODLevels.size() && !LODLevels[Index - 1].expired();
|
|
}
|
|
|
|
BlobPtrW Blob::GetLODLevel(int32 Level)
|
|
{
|
|
check(Level > 0 && Level <= (int32)LODLevels.size());
|
|
return LODLevels[Level - 1];
|
|
}
|
|
|
|
void Blob::SetLODLevel(int32 Level, BlobPtr LODBlob, BlobPtrW LODParentBlob, BlobPtrW LODSourceBlob, bool bAddToBlobber)
|
|
{
|
|
check(LODBlob);
|
|
check(!LODParentBlob.expired());
|
|
check(!LODSourceBlob.expired());
|
|
|
|
LODParent = LODParentBlob;
|
|
LODSource = LODSourceBlob;
|
|
|
|
LODBlob->bIsLODLevel = true;
|
|
|
|
if (LODLevels.empty())
|
|
{
|
|
/// -1 because we don't wanna keep a weak pointer of Mip Level 0. 'this' is supposed to
|
|
/// the the mip Level 0, so there's no point keeping it in the mip chain
|
|
int32 numLevels = TextureHelper::CalcNumMipLevels(std::max(GetWidth(), GetHeight())) - 1;
|
|
|
|
if (numLevels > 0)
|
|
LODLevels.resize(numLevels);
|
|
}
|
|
|
|
check(Level > 0 && Level <= (int32)LODLevels.size());
|
|
check(LODBlob->GetWidth() < GetWidth() && LODBlob->GetHeight() < GetHeight());
|
|
|
|
/// Make sure we're not replacing an existing lod Level
|
|
//check(_lodLevels[Level - 1].expired() || (!_lodLevels[Level - 1].expired() && _lodLevels[Level - 1].lock() == blob));
|
|
|
|
LODLevels[Level - 1] = LODBlob;
|
|
|
|
BlobPtr ThisBlob = shared_from_this();
|
|
|
|
/// Add hashes to the blobber
|
|
if (bAddToBlobber)
|
|
{
|
|
#if 0 /// TODO
|
|
CHashPtr lodHash = Blob::CalculateMipHash(Hash(), Level);
|
|
TextureGraphEngine::Blobber()->AddResult(lodHash, blob);
|
|
|
|
CHashPtr lodHash_0 = Blob::CalculateMipHash(Hash(), 0);
|
|
TextureGraphEngine::Blobber()->AddResult(lodHash_0, thisBlob);
|
|
#endif ///
|
|
|
|
//CHashPtrVec thisLodHashes = Blob::CalculateMipHashes(lodSource.lock()->Hash(), lodParent.lock()->Hash(), 0);
|
|
//for (CHashPtr& lodHash : thisLodHashes)
|
|
//{
|
|
// TextureGraphEngine::Blobber()->AddResult(lodHash, thisBlob);
|
|
//}
|
|
}
|
|
|
|
for (int32 li = 0; li < Level - 1; li++)
|
|
{
|
|
BlobPtr lodLevel = LODLevels[li].lock();
|
|
|
|
if (lodLevel)
|
|
{
|
|
lodLevel->SetLODLevel(Level - li - 1, LODBlob, ThisBlob, LODSourceBlob, bAddToBlobber);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Blob::UpdateLinkedBlobs(bool bDoFinalise)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
for (BlobPtrW LinkedBlobW : LinkedBlobs)
|
|
{
|
|
BlobPtr LinkedBlob = LinkedBlobW.lock();
|
|
|
|
if (LinkedBlob && bDoFinalise)
|
|
{
|
|
LinkedBlob->FinaliseFrom(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
BlobPtr Blob::GetHistogram() const
|
|
{
|
|
return Histogram ? Histogram : std::static_pointer_cast<Blob>(TextureHelper::GetBlack());
|
|
}
|
|
|
|
void Blob::AddLinkedBlob(BlobPtr LinkedBlob)
|
|
{
|
|
if (IsFinalised())
|
|
{
|
|
LinkedBlob->FinaliseFrom(this);
|
|
return;
|
|
}
|
|
LinkedBlobs.push_back(LinkedBlob);
|
|
}
|
|
|
|
void Blob::SyncWith(Blob* RHS)
|
|
{
|
|
/// Synchronise the histograms
|
|
if (!Histogram && RHS->Histogram)
|
|
Histogram = RHS->Histogram;
|
|
else if (Histogram && !RHS->Histogram)
|
|
RHS->Histogram = Histogram;
|
|
else if (Histogram && RHS->Histogram)
|
|
{
|
|
/// Sync based on whatever has been finalised
|
|
if (RHS->Histogram->IsFinalised() && !Histogram->IsFinalised())
|
|
Histogram->FinaliseFrom(RHS->Histogram.get());
|
|
else if (Histogram->IsFinalised() && !RHS->Histogram->IsFinalised())
|
|
RHS->Histogram->FinaliseFrom(Histogram.get());
|
|
|
|
/// If both have been finalised then there's nothing we should do.
|
|
/// We have two copies within the system and in due course, one
|
|
/// of these will get deleted
|
|
}
|
|
}
|
|
|
|
void Blob::FinaliseFrom(Blob* RHS)
|
|
{
|
|
// We are only saving the buffer transient state when buffer is valid.
|
|
// This buffer will be empty in case of non single blob.
|
|
const bool HadValidBuffer = Buffer && Buffer->IsValid();
|
|
const bool bIsTransient = Buffer && Buffer->IsValid() && Buffer->IsTransient();
|
|
|
|
// We need to save the transient state before copying the RHS buffer.
|
|
Buffer = RHS->Buffer;
|
|
|
|
// Only try to update the buffer transient state when we found a valid buffer from RHS.
|
|
if(HadValidBuffer && Buffer && Buffer->IsValid())
|
|
{
|
|
Buffer->Desc.bIsTransient &= bIsTransient;
|
|
}
|
|
|
|
LODLevels = RHS->LODLevels;
|
|
MinMax = RHS->MinMax;
|
|
MinValue = RHS->MinValue;
|
|
MaxValue = RHS->MaxValue;
|
|
|
|
LODParent = RHS->LODParent;
|
|
LODSource = RHS->LODSource;
|
|
bIsLODLevel = RHS->bIsLODLevel;
|
|
|
|
SyncWith(RHS);
|
|
FinaliseNow(true, nullptr);
|
|
}
|
|
|
|
void Blob::FinaliseNow(bool bNoCalcHash, CHashPtr FixedHash)
|
|
{
|
|
/// If already finalised then nothing to do over here
|
|
if (bIsFinalised)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(IsInGameThread());
|
|
|
|
bIsFinalised = true;
|
|
FinaliseTS = FDateTime::Now();
|
|
|
|
UpdateLinkedBlobs(true);
|
|
}
|
|
|
|
AsyncBufferResultPtr Blob::Finalise(bool bNoCalcHash, CHashPtr FixedHash)
|
|
{
|
|
FinaliseNow(bNoCalcHash, FixedHash);
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
}
|
|
|
|
CHashPtr Blob::CalculateMipHash(CHashPtr MainHash, int32 Level)
|
|
{
|
|
CHashPtrVec Sources =
|
|
{
|
|
MainHash,
|
|
std::make_shared<CHash>(DataUtil::Hash_Int32(Level), true),
|
|
std::make_shared<CHash>(DataUtil::Hash_GenericString_Name(FString(LODTransformName)), true)
|
|
};
|
|
|
|
return CHash::ConstructFromSources(Sources);
|
|
}
|
|
|
|
CHashPtrVec Blob::CalculateMipHashes(CHashPtr MainHash, CHashPtr ParentHash, int32 Level)
|
|
{
|
|
CHashPtr MainHashAtLOD = CalculateMipHash(MainHash, Level);
|
|
CHashPtr ParentHashAtLOD = CalculateMipHash(ParentHash, Level);
|
|
|
|
/// Just return one unique Hash
|
|
if (MainHashAtLOD->Value() != ParentHashAtLOD->Value())
|
|
return { MainHashAtLOD, ParentHashAtLOD };
|
|
|
|
return { MainHashAtLOD };
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|