1319 lines
33 KiB
C++
1319 lines
33 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "TiledBlob.h"
|
|
#include "Device/DeviceBuffer.h"
|
|
#include "Transform/BlobTransform.h"
|
|
#include "Device/DeviceManager.h"
|
|
#include "Device/FX/Device_FX.h"
|
|
#include "Model/Mix/Mix.h"
|
|
#include "Job/Job.h"
|
|
|
|
#include "Profiling/StatGroup.h"
|
|
#include "TextureGraphEngineGameInstance.h"
|
|
#include "Data/Blobber.h"
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("TiledBlob_TileBuffer"), STAT_TiledBlob_TileBuffer, STATGROUP_TextureGraphEngine);
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
TiledBlob::TiledBlob(const BufferDescriptor& InDesc, const BlobPtrTiles& Tiles)
|
|
: Blob(InDesc, nullptr)
|
|
, Tiles(Tiles)
|
|
, Desc(InDesc)
|
|
, bTiledTarget(Tiles.Rows() > 1 || Tiles.Cols() > 1)
|
|
{
|
|
if (Desc.Format != BufferFormat::LateBound)
|
|
{
|
|
Desc.Width = std::max(Desc.Width, static_cast<uint32>(Tiles.Rows()));
|
|
Desc.Height = std::max(Desc.Height, static_cast<uint32>(Tiles.Cols()));
|
|
}
|
|
|
|
CalcHashNow();
|
|
Buffer.reset();
|
|
|
|
bIsFinalised = true;
|
|
|
|
for (size_t RowId = 0; RowId < Tiles.Rows() && bIsFinalised; RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols() && bIsFinalised; ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
|
|
/// Cannot have tiled BlobObj as part of another tiled BlobObj
|
|
check(!Tile->IsTiled());
|
|
|
|
bIsFinalised &= Tile->bIsFinalised;
|
|
}
|
|
}
|
|
|
|
if (bIsFinalised)
|
|
FinaliseTS = FDateTime::Now();
|
|
}
|
|
|
|
std::shared_ptr<TiledBlob> TiledBlob::InitFromTiles(const BufferDescriptor& InDesc, BlobPtrTiles& Tiles)
|
|
{
|
|
BufferDescriptor Desc = InDesc;
|
|
|
|
Desc.Width = std::max(Desc.Width, static_cast<uint32>(Tiles.Rows()));
|
|
Desc.Height = std::max(Desc.Height, static_cast<uint32>(Tiles.Cols()));
|
|
|
|
TiledBlobPtr TBlob = std::make_shared<TiledBlob>(Desc, Tiles);
|
|
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
|
|
/// Cannot have tiled BlobObj as part of another tiled BlobObj
|
|
check(!Tile->IsTiled());
|
|
}
|
|
}
|
|
|
|
/// Mark as finalised
|
|
TBlob->bIsFinalised = true;
|
|
|
|
return TBlob;
|
|
}
|
|
|
|
TiledBlob::TiledBlob(BlobRef BlobObj)
|
|
: TiledBlob(BlobObj->GetDescriptor(), BlobPtrTiles({BlobObj}, 1, 1))
|
|
{
|
|
/// Cannot have tiled BlobObj inside another
|
|
check(BlobObj && !BlobObj->IsTiled());
|
|
}
|
|
|
|
TiledBlob::TiledBlob(DeviceBufferRef Buffer)
|
|
: Blob(Buffer)
|
|
, Tiles(0, 0)
|
|
, Desc(Buffer->Descriptor())
|
|
{
|
|
/// no hash to calculate so far
|
|
}
|
|
|
|
TiledBlob::TiledBlob(const BufferDescriptor& InDesc, size_t NumRows, size_t NumCols, CHashPtr InHashValue)
|
|
: Blob(InDesc, InHashValue)
|
|
, Tiles(NumRows, NumCols)
|
|
, HashValue(InHashValue)
|
|
, Desc(InDesc)
|
|
{
|
|
check(NumRows > 0 && NumCols > 0);
|
|
|
|
if (Desc.Format != BufferFormat::LateBound)
|
|
{
|
|
Desc.Width = std::max(Desc.Width, static_cast<uint32>(Tiles.Rows()));
|
|
Desc.Height = std::max(Desc.Height, static_cast<uint32>(Tiles.Cols()));
|
|
}
|
|
|
|
check(HashValue);
|
|
}
|
|
|
|
TiledBlob::~TiledBlob()
|
|
{
|
|
UE_LOG(LogData, VeryVerbose, TEXT("Deleting TiledBlob: %s [%dx%d]"), *Desc.Name, Desc.Width, Desc.Height);
|
|
Tiles.Clear();
|
|
}
|
|
|
|
TiledBlobPtr TiledBlob::AsTiledBlob(BlobPtr BlobObj)
|
|
{
|
|
if (BlobObj->IsTiled())
|
|
return std::static_pointer_cast<TiledBlob>(BlobObj);
|
|
return std::make_shared<TiledBlob>(BlobObj);
|
|
}
|
|
|
|
void TiledBlob::FinaliseFrom(Blob* RHS)
|
|
{
|
|
check(RHS->IsTiled());
|
|
TiledBlob* RHSTiled = static_cast<TiledBlob*>(RHS);
|
|
|
|
Tiles = RHSTiled->Tiles;
|
|
SetHash(RHSTiled->HashValue);
|
|
|
|
bool bIsTransient = Desc.bIsTransient;
|
|
Desc = RHSTiled->Desc;
|
|
|
|
/// Retain the transient information as cached tiled blob could be transient and this one isn't or vice-versa
|
|
Desc.bIsTransient = bIsTransient;
|
|
|
|
bReady = RHSTiled->bReady;
|
|
bTiledTarget = RHSTiled->bTiledTarget;
|
|
SingleBlob = RHSTiled->SingleBlob;
|
|
|
|
Blob::FinaliseFrom(RHS);
|
|
}
|
|
|
|
void TiledBlob::SetTransient()
|
|
{
|
|
Desc.bIsTransient = true;
|
|
}
|
|
|
|
#if DEBUG_BLOB_REF_KEEPING == 1
|
|
bool TiledBlob::HasBlobAsTile(Blob* BlobObj) const
|
|
{
|
|
check(IsInGameThread());
|
|
check(BlobObj);
|
|
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
if (Tiles[RowId][ColId] == BlobObj)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void TiledBlob::SetTiles(const BlobPtrTiles& InTiles)
|
|
{
|
|
Tiles = InTiles;
|
|
CalcHashNow();
|
|
}
|
|
|
|
void TiledBlob::SetTile(int32 X, int32 Y, BlobRef Tile)
|
|
{
|
|
#if DEBUG_BLOB_REF_KEEPING == 1
|
|
if (Tiles[RowId][ColId])
|
|
Tiles[RowId][ColId]->RemoveOwner(this);
|
|
#endif
|
|
|
|
auto Self = shared_from_this();
|
|
check(Tile);
|
|
|
|
#if DEBUG_BLOB_REF_KEEPING == 1
|
|
Tile.lock()->AddOwner(Self);
|
|
#endif
|
|
|
|
Tiles[X][Y] = BlobRef(Tile);
|
|
}
|
|
|
|
TiledBlob& TiledBlob::operator = (const TiledBlob& RHS)
|
|
{
|
|
Buffer = RHS.Buffer;
|
|
bIsFinalised = RHS.bIsFinalised;
|
|
FinaliseTS = RHS.FinaliseTS;
|
|
ReplayCount = RHS.ReplayCount;
|
|
|
|
Tiles = RHS.Tiles;
|
|
SetHash(RHS.HashValue);
|
|
|
|
/// Ensure that 1x1 tiled BlobObj hashes are calculated correctly
|
|
check((Tiles.Rows() > 1 && Tiles.Cols() > 1) ||
|
|
((Tiles.Rows() == 1 && Tiles.Cols() == 1) && (HashValue->IsFinal() || (HashValue->IsTemp() && HashValue->NumSources() == 2))));
|
|
|
|
Desc = RHS.Desc;
|
|
bReady = RHS.bReady;
|
|
bTiledTarget = RHS.bTiledTarget;
|
|
|
|
MinMax = RHS.MinMax;
|
|
MinValue = RHS.MinValue;
|
|
MaxValue = RHS.MaxValue;
|
|
|
|
LODParent = RHS.LODParent;
|
|
LODSource = RHS.LODSource;
|
|
LODLevels = RHS.LODLevels;
|
|
bIsLODLevel = RHS.bIsLODLevel;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void TiledBlob::ResolveLateBound(const BufferDescriptor& InDesc, bool bOverrideExisting /* = false */)
|
|
{
|
|
auto ExistingDesc = Desc;
|
|
|
|
Desc = InDesc;
|
|
|
|
/// If there is size information in the existing descriptor then that will
|
|
/// override the size coming from the Ref. This is for things like mip-mapping
|
|
/// and other transforms that wanna produce fixed sized images
|
|
|
|
if (!bOverrideExisting)
|
|
{
|
|
if (ExistingDesc.Width > 0)
|
|
Desc.Width = ExistingDesc.Width;
|
|
|
|
if (ExistingDesc.Height > 0)
|
|
Desc.Height = ExistingDesc.Height;
|
|
|
|
if (ExistingDesc.ItemsPerPoint > 0)
|
|
Desc.ItemsPerPoint = ExistingDesc.ItemsPerPoint;
|
|
|
|
Desc.Metadata = ExistingDesc.Metadata;
|
|
Desc.Name = ExistingDesc.Name;
|
|
}
|
|
|
|
Desc.Width = std::max(Desc.Width, static_cast<uint32>(Tiles.Rows()));
|
|
Desc.Height = std::max(Desc.Height, static_cast<uint32>(Tiles.Cols()));
|
|
|
|
/// Make sure the descriptor is still not late bound!
|
|
check(Desc.Format != BufferFormat::LateBound);
|
|
check(Desc.Width > 0 && Desc.Height > 0);
|
|
check(Desc.ItemsPerPoint > 0);
|
|
}
|
|
|
|
void TiledBlob::ResolveLateBound(BlobPtr Ref)
|
|
{
|
|
check(Ref);
|
|
|
|
/// Copy the descriptor over and release the Ref
|
|
auto RefDesc = Ref->GetDescriptor();
|
|
|
|
ResolveLateBound(RefDesc, false);
|
|
}
|
|
|
|
void TiledBlob::CopyResolveLateBound(BlobPtr RHS_)
|
|
{
|
|
check(RHS_ != nullptr);
|
|
|
|
TiledBlob* RHS = dynamic_cast<TiledBlob*>(RHS_.get());
|
|
|
|
/// If this isn't a
|
|
if (RHS)
|
|
*this = *RHS;
|
|
else
|
|
{
|
|
/// Single BlobObj type
|
|
Tiles.Resize(1, 1);
|
|
SetTile(0, 0, RHS_);
|
|
|
|
Desc = RHS_->GetDescriptor();
|
|
|
|
CalcHashNow();
|
|
|
|
bReady = true;
|
|
bTiledTarget = false;
|
|
}
|
|
|
|
bIsFinalised = true;
|
|
FinaliseTS = FDateTime::Now();
|
|
}
|
|
|
|
DeviceBufferRef TiledBlob::GetBufferRef() const
|
|
{
|
|
if (bTiledTarget)
|
|
return Buffer;
|
|
|
|
check(Tiles.Rows() == 1 && Tiles.Cols() == 1);
|
|
check(Tiles[0][0]);
|
|
|
|
return Tiles[0][0]->GetBufferRef();
|
|
}
|
|
|
|
void TiledBlob::MakeSingleBlob_Internal()
|
|
{
|
|
/// Turn this into a single BlobObj
|
|
check(IsInGameThread());
|
|
check(HashValue && HashValue->IsValid());
|
|
|
|
if (Rows() > 1 || Cols() > 1)
|
|
{
|
|
check(Buffer && Buffer->IsValid());
|
|
|
|
SingleBlob = std::make_shared<Blob>(Buffer);
|
|
Tiles.Clear();
|
|
|
|
Tiles.Resize(1, 1);
|
|
Tiles[0][0] = SingleBlob;
|
|
}
|
|
|
|
Buffer = DeviceBufferRef();
|
|
|
|
UpdateLinkedBlobs(false);
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob::MakeSingleBlob()
|
|
{
|
|
/// Turn this into a single BlobObj
|
|
check(IsInGameThread());
|
|
check(HashValue && HashValue->IsValid());
|
|
|
|
/// If we already have a Buffer
|
|
if ((Buffer && bReady) || !bTiledTarget) //If its not a tiledTarget then all rendering is done on main Buffer
|
|
{
|
|
MakeSingleBlob_Internal();
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
}
|
|
|
|
return CombineTiles(false, false).then([this]()
|
|
{
|
|
MakeSingleBlob_Internal();
|
|
return std::make_shared<BufferResult>();
|
|
});
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob::CombineTiles(bool bTouch, bool bIsArray, uint64 BatchId /* = 0 */)
|
|
{
|
|
/// If we already have a buffer then we don't need anything else
|
|
if (bReady)
|
|
{
|
|
if (Buffer && Buffer.IsValid())
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
else if (SingleBlob)
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
}
|
|
|
|
check(Tiles[0][0]);
|
|
const BlobPtr Tile0 = Tiles[0][0];
|
|
Device* Dev = Tile0->GetBufferRef()->GetOwnerDevice();
|
|
|
|
if (Rows() == 1 && Cols() == 1)
|
|
{
|
|
check(Tile0->GetBufferRef() && Tile0->GetBufferRef().IsValid());
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
}
|
|
|
|
T_Tiles<DeviceBufferRef> TileBuffers(Rows(), Cols());
|
|
|
|
for (int32 RowId = 0; RowId < Rows(); RowId++)
|
|
{
|
|
for (int32 ColId = 0; ColId < Cols(); ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
|
|
const BlobPtr Tile = GetTile(RowId, ColId);
|
|
check(Tile->GetBufferRef());
|
|
|
|
TileBuffers[RowId][ColId] = Tile->GetBufferRef();
|
|
|
|
/// Touch the Tiles here
|
|
if (bTouch)
|
|
Tile->Touch(BatchId);
|
|
}
|
|
}
|
|
|
|
/// If we don't have a hash then calculate it now
|
|
if (!HashValue)
|
|
CalcHashNow();
|
|
|
|
/// We've already calculated the hash from all the child blobs
|
|
UE_LOG(LogData, VeryVerbose, TEXT("*** Combine TiledBlob: %s [Hash: %llu] [T0: %s] ***"), *Desc.Name, HashValue->Value(), *Tile0->Name());
|
|
|
|
const CombineSplitArgs CombineArgs
|
|
{
|
|
.Buffer = Buffer,
|
|
.Tiles = TileBuffers,
|
|
.IsArray = bIsArray,
|
|
.BufferHash = HashValue
|
|
};
|
|
|
|
return Dev->CombineFromTiles(CombineArgs).then([this](DeviceBufferRef CombinedBuffer)
|
|
{
|
|
check(CombinedBuffer->Hash(false) && CombinedBuffer->Hash(false)->IsValid());
|
|
Buffer = CombinedBuffer;
|
|
bReady = true;
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
});
|
|
}
|
|
|
|
void TiledBlob::TouchTiles(uint64 BatchId)
|
|
{
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
const BlobPtr& Tile = Tiles[RowId][ColId];
|
|
Tile->Touch(BatchId);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TiledBlob::Touch(uint64 BatchId)
|
|
{
|
|
Blob::Touch(BatchId);
|
|
|
|
/// IMPORTANT: Do not touch the Tiles here ... they must be separately
|
|
/// at Bind locations, strategically to minimise looping overhead
|
|
UpdateAccessInfo(BatchId);
|
|
}
|
|
|
|
AscynCHashPtr TiledBlob::CalcHash()
|
|
{
|
|
if (HashValue && HashValue->IsFinal())
|
|
return cti::make_ready_continuable(HashValue);
|
|
|
|
UE_LOG(LogBlob, VeryVerbose, TEXT("Finalising TiledBlob hash: %s"), *Desc.Name);
|
|
|
|
std::vector<std::decay_t<AscynCHashPtr>, std::allocator<std::decay_t<AscynCHashPtr>>> Promises;
|
|
std::unordered_map<HashType, bool> handled;
|
|
|
|
size_t NumRows = Tiles.Rows();
|
|
size_t NumCols = Tiles.Cols();
|
|
|
|
for (size_t RowId = 0; RowId < NumRows; RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < NumCols; ColId++)
|
|
{
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
CHashPtr TileHash = Tile->Hash();
|
|
|
|
if (Tile &&
|
|
(!TileHash || !TileHash->IsFinal()) &&
|
|
handled.find(TileHash->Value()) == handled.end())
|
|
{
|
|
Promises.push_back(Tile->CalcHash());
|
|
handled[TileHash->Value()] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Promises.empty())
|
|
{
|
|
return cti::when_all(Promises.begin(), Promises.end()).then([this](std::vector<CHashPtr>) mutable
|
|
{
|
|
CalcHashNow();
|
|
check(HashValue->IsFinal());
|
|
return HashValue;
|
|
});
|
|
}
|
|
|
|
CalcHashNow();
|
|
|
|
return cti::make_ready_continuable(HashValue);
|
|
}
|
|
|
|
void TiledBlob::SetHash(CHashPtr InHashValue)
|
|
{
|
|
check(InHashValue);
|
|
check(IsInGameThread() || InHashValue->IsFinal());
|
|
|
|
if (HashValue && *HashValue == *InHashValue)
|
|
return;
|
|
|
|
/// Update the hash in the blobber
|
|
if (HashValue)
|
|
HashValue = CHash::UpdateHash(InHashValue, HashValue);
|
|
else
|
|
HashValue = InHashValue;
|
|
}
|
|
|
|
void TiledBlob::CalcHashNow() const
|
|
{
|
|
if (!Tiles.Rows() || !Tiles.Cols() || !Tiles[0][0])
|
|
return;
|
|
|
|
/// Common scenario of 1x1 sized flat textures
|
|
if (Tiles.Rows() == 1 && Tiles.Cols() == 1)
|
|
{
|
|
if (!Tiles[0][0] || !Tiles[0][0]->IsValid())
|
|
return;
|
|
|
|
Desc = Tiles[0][0]->GetDescriptor();
|
|
const_cast<TiledBlob*>(this)->SetHash(Tiles[0][0]->Hash());
|
|
|
|
return;
|
|
}
|
|
|
|
HashValue = nullptr;
|
|
|
|
const size_t NumRows = Tiles.Rows();
|
|
const size_t NumCols = Tiles.Cols();
|
|
bool CalculateDim = false;
|
|
|
|
if (!Desc.Width || !Desc.Height)
|
|
{
|
|
Desc = Tiles[0][0]->GetDescriptor();
|
|
Desc.Width = 0;
|
|
Desc.Height = 0;
|
|
CalculateDim = true;
|
|
}
|
|
|
|
T_Tiles<CHashPtr> TileHashes(NumRows, NumCols);
|
|
|
|
for (size_t RowId = 0; RowId < NumRows; RowId++)
|
|
{
|
|
if (CalculateDim && RowId == 0)
|
|
Desc.Width += Tiles[RowId][0]->GetWidth();
|
|
|
|
for (size_t ColId = 0; ColId < NumCols; ColId++)
|
|
{
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
|
|
if (!Tile)
|
|
return;
|
|
|
|
CHashPtr TileHash = Tile->Hash();
|
|
|
|
if (!TileHash)
|
|
return;
|
|
|
|
TileHashes[RowId][ColId] = TileHash;
|
|
|
|
if (CalculateDim && ColId == 0)
|
|
Desc.Width += Tiles[ColId][0]->GetWidth();
|
|
}
|
|
}
|
|
|
|
const_cast<TiledBlob*>(this)->SetHash(CHash::ConstructFromSources(TileHashes.Tiles()));
|
|
}
|
|
|
|
CHashPtr TiledBlob::Hash() const
|
|
{
|
|
if (!HashValue)
|
|
CalcHashNow();
|
|
|
|
return HashValue;
|
|
}
|
|
|
|
AsyncDeviceBufferRef TiledBlob::TransferTo(Device* Target)
|
|
{
|
|
auto TargetName = Device::DeviceType_Names(Target->GetType());
|
|
|
|
/// Go through my Tile blobs and make sure they are compatible with the Target
|
|
std::vector<AsyncDeviceBufferRef> Promises;
|
|
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
check(Tile);
|
|
|
|
if (!Tile->GetBufferRef()->IsCompatible(Target))
|
|
{
|
|
UE_LOG(LogData, Log, TEXT("TransferTo %s TiledBlob: %s Tile [%d, %d]"), *TargetName, *Desc.Name, RowId, ColId);
|
|
Promises.emplace_back(Tile->TransferTo(Target));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Promises.empty())
|
|
{
|
|
return cti::when_all(Promises.begin(), Promises.end()).then([this, TargetName](auto) mutable
|
|
{
|
|
/// Return this tiledBlob Buffer which is empty but this is just to fit the signature
|
|
UE_LOG(LogData, Log, TEXT("TransferTo %s TiledBlob: %s DONE"), *TargetName, *Desc.Name);
|
|
return Buffer;
|
|
});
|
|
}
|
|
|
|
return cti::make_ready_continuable(Buffer);
|
|
}
|
|
|
|
#define TILED_BLOB_DEVICE_OPTIMISED_PATH
|
|
|
|
AsyncBufferResultPtr TiledBlob::Bind(const BlobTransform* Transform, const ResourceBindInfo& BindInfo)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
/// Manually touch this at the beginning and don't worry about it
|
|
Touch(BindInfo.BatchId);
|
|
|
|
/// If we have any Tiles then
|
|
if (Tiles.Rows() && Tiles.Cols())
|
|
{
|
|
/// Use the optimal Dev implementation for combining the Tiles into a single BlobObj
|
|
if (Tiles.Rows() > 1 || Tiles.Cols() > 1)
|
|
{
|
|
#ifdef TILED_BLOB_DEVICE_OPTIMISED_PATH
|
|
if (bReady)
|
|
{
|
|
/// Touch the Tiles since we won't be looping over these
|
|
TouchTiles(BindInfo.BatchId);
|
|
return Blob::Bind(Transform, BindInfo);
|
|
}
|
|
|
|
//check(_buffer);
|
|
|
|
/// try to salvage in production builds [if it ever gets to it]
|
|
if (!Buffer)
|
|
Buffer = BindInfo.Dev->Create(Desc, HashValue);
|
|
//return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
|
|
/// Buffer is already there ... just return
|
|
//if (_buffer)
|
|
// return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
|
|
return CombineTiles(true, BindInfo.bIsCombined, BindInfo.BatchId).then([this, Transform, BindInfo]
|
|
{
|
|
TouchTiles(BindInfo.BatchId);
|
|
Buffer->GetName() = BindInfo.Target;
|
|
return Blob::Bind(Transform, BindInfo);
|
|
});
|
|
|
|
//T_Tiles<DeviceBufferRef> Tiles(Tiles.Rows(), Tiles.Cols());
|
|
|
|
//for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
//{
|
|
// for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
// {
|
|
// check(Tiles[RowId][ColId]->Buffer());
|
|
// Tiles[RowId][ColId] = Tiles[RowId][ColId]->Buffer();
|
|
|
|
// /// Touch the Tiles here
|
|
// Tiles[RowId][ColId]->Touch();
|
|
// }
|
|
//}
|
|
|
|
///// If we don't have a hash then calculate it now
|
|
//if (!_hash)
|
|
// CalcHashNow();
|
|
//
|
|
///// We've already calculated the hash from all the child blobs
|
|
//UE_LOG(LogData, Log, TEXT("*** Combine TiledBlob: %s [Hash: %llu] [T0: %s] ***"), *_desc.name, _hash->Value(), *Tiles[0][0]->Name());
|
|
//return BindInfo.Dev->CombineFromTiles(_buffer, Tiles).then([this, Transform, BindInfo](DeviceBufferRef Buffer)
|
|
//{
|
|
// _buffer = Buffer;
|
|
// _buffer->Name() = BindInfo.Target;
|
|
// _ready = true;
|
|
// return Blob::Bind(Transform, BindInfo);
|
|
//});
|
|
#else
|
|
/// TODO: right now we don't have a good enough way method of rendering render targets as
|
|
/// Tiles onto a large textures. However, that should be fairly simple to implement. Until
|
|
/// we implement that, we'll just use the dirty way of using Raw data to combine all the
|
|
/// textures into one!
|
|
//_buffer = BindInfo.Dev->Find(_hash, true);
|
|
|
|
/// If we don't have a Buffer, then try to create one
|
|
if (!_ready)
|
|
{
|
|
RawBufferPtrTiles Tiles(Tiles.Rows(), Tiles.Cols());
|
|
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
RawBufferPtr TileRaw = Tiles[RowId][ColId]->Raw();
|
|
check(TileRaw);
|
|
Tiles[RowId][ColId] = TileRaw;
|
|
}
|
|
}
|
|
|
|
/// We've already calculated the hash from all the child blobs
|
|
|
|
UE_LOG(LogData, VeryVerbose, TEXT("*** BEGIN Combine TiledBlob: %s [Hash: %llu] [T0: %s] ***"), *_desc.name, _hash, *Tiles[0][0]->Name());
|
|
double startTime = Util::Time();
|
|
RawBufferPtr Raw = TextureHelper::CombineRaw_Tiles(Tiles, _hash, true);
|
|
UE_LOG(LogData, VeryVerbose, TEXT("*** END Combine TiledBlob: %s [Hash: %llu] [T0: %s] ***"), *_desc.name, _hash, *Tiles[0][0]->Name());
|
|
|
|
Raw->Name() = BindInfo.Target;
|
|
return _buffer->UpdateRaw(Raw, Raw->Hash(), 0).then([this, &BindInfo]()
|
|
{
|
|
return Blob::Bind(Transform, BindInfo);
|
|
});
|
|
//_buffer = BindInfo.Dev->Create(Raw);
|
|
|
|
_ready = true;
|
|
}
|
|
|
|
return Blob::Bind(Transform, BindInfo);
|
|
#endif /// TILED_BLOB_DEVICE_OPTIMISED_PATH
|
|
}
|
|
|
|
check(Tiles[0][0]);
|
|
return Tiles[0][0]->Bind(Transform, BindInfo);
|
|
}
|
|
|
|
check(Buffer);
|
|
return Blob::Bind(Transform, BindInfo);
|
|
}
|
|
|
|
void TiledBlob::ResetBuffer()
|
|
{
|
|
//Resetting the Buffer means deallocation of UObject associated with Render Target. We dont need that
|
|
//Instead this would be garbage collected on Dev cache and reused when required
|
|
//Blob::ResetBuffer();
|
|
|
|
bReady = false;
|
|
}
|
|
|
|
AsyncPrepareResult TiledBlob::PrepareForWrite(const ResourceBindInfo& BindInfo_)
|
|
{
|
|
bool bCreateTexArray = false;
|
|
|
|
if (BindInfo_.bIsCombined)
|
|
{
|
|
bCreateTexArray = true;
|
|
}
|
|
|
|
if ((Buffer || bReady) && !bCreateTexArray)
|
|
{
|
|
bReady = true;
|
|
return cti::make_ready_continuable(0);
|
|
}
|
|
|
|
if ((Tiles.Rows() > 1 && Tiles.Cols() > 1) || bCreateTexArray)
|
|
{
|
|
if (!Buffer || bCreateTexArray)
|
|
{
|
|
ResourceBindInfo BindInfo = BindInfo_;
|
|
|
|
#ifndef TILED_BLOB_DEVICE_OPTIMISED_PATH
|
|
BindInfo.staticBuffer = true;
|
|
#endif
|
|
|
|
check(BindInfo.Dev);
|
|
if(!Buffer)
|
|
Buffer = BindInfo.Dev->Create(Desc, HashValue);
|
|
|
|
return Blob::PrepareForWrite(BindInfo);
|
|
}
|
|
}
|
|
|
|
check(Tiles[0][0])
|
|
|
|
return Tiles[0][0]->PrepareForWrite(BindInfo_).then([this]()
|
|
{
|
|
Buffer = Tiles[0][0]->GetBufferRef();
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob::Unbind(const BlobTransform* Transform, const ResourceBindInfo& BindInfo)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (Tiles.Rows() > 1 || Tiles.Cols() > 1)
|
|
{
|
|
/// If this is not a tiled Target and it was a write Target, then the combined Buffer is already ready
|
|
if (BindInfo.bWriteTarget) {
|
|
bReady = true;
|
|
check(Buffer); /// Buffer must be valid for write targets
|
|
}
|
|
|
|
/// Don't try to Unbind a buffer that isn't ready yet. The buffer might not even be created yet.
|
|
if (!bReady || !Buffer)
|
|
return cti::make_ready_continuable<BufferResultPtr>(std::make_shared<BufferResult>());
|
|
|
|
//_ready = false;
|
|
return Blob::Unbind(Transform, BindInfo).then([this](BufferResultPtr Result)
|
|
{
|
|
/// We don't need the Raw Buffer here anymore [tiled buffers are large and expensive!]
|
|
/// though we still want to keep the native Dev buffers
|
|
/// However, before we do that we need to ensure that the Buffer has been transferred
|
|
/// to the Tiles, if it was being rendered to in non-tiled mode
|
|
|
|
/// If its already a tiled Target, then we don't need to do anything at all
|
|
if (bTiledTarget)
|
|
{
|
|
ResetBuffer();
|
|
return Result;
|
|
}
|
|
|
|
/// Otherwise, we must Tile it now
|
|
return Result;
|
|
});
|
|
}
|
|
else
|
|
return Tiles[0][0]->Unbind(Transform, BindInfo);
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob::TileBuffer(DeviceBufferRef Buffer, BlobPtrTiles& Tiles)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_TiledBlob_TileBuffer)
|
|
const size_t NumRows = Tiles.Rows();
|
|
const size_t NumCols = Tiles.Cols();
|
|
T_Tiles<DeviceBufferRef>* TilesBuffers = new T_Tiles<DeviceBufferRef>(NumRows, NumCols);
|
|
|
|
int totalTiles = Tiles.Rows() * Tiles.Cols();
|
|
|
|
/// Incase the Buffer is present in Tiles, it would be copied over. This is to avoid allocation of RenderTarget
|
|
/// This allocation is being done on prepare resources due to optimization (e.g lazy evaluation)
|
|
for (size_t RowId = 0; RowId < NumRows; RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < NumCols; ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
(*TilesBuffers)(RowId, ColId) = Tiles[RowId][ColId]->GetBufferRef();
|
|
}
|
|
}
|
|
|
|
check(Buffer->GetOwnerDevice());
|
|
|
|
CombineSplitArgs splitArgs { Buffer, *TilesBuffers};
|
|
|
|
return Buffer->GetOwnerDevice()->SplitToTiles(splitArgs).then([Buffer, &Tiles, TilesBuffers](DeviceBufferRef) mutable
|
|
{
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
DeviceBufferRef TileBuffer = (*TilesBuffers)[RowId][ColId];
|
|
BlobPtr Tile = std::make_shared<Blob>(TileBuffer);
|
|
Tiles[RowId][ColId] = BlobRef(Tile);
|
|
}
|
|
}
|
|
|
|
/// Clear the pointer
|
|
TilesBuffers->Clear();
|
|
delete TilesBuffers;
|
|
|
|
return std::make_shared<BufferResult>();
|
|
});
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob::Flush(const ResourceBindInfo& BindInfo)
|
|
{
|
|
if (!Tiles.Rows() || !Tiles.Cols())
|
|
{
|
|
int32 NumRows = BindInfo.NumTilesX;
|
|
int32 NumCols = BindInfo.NumTilesY;
|
|
|
|
check(NumRows > 0 && NumCols > 0);
|
|
check(Buffer);
|
|
|
|
Tiles.Resize(NumRows, NumCols);
|
|
}
|
|
|
|
bReady = false;
|
|
|
|
return Blob::Flush(BindInfo)
|
|
.then([this](BufferResultPtr Result)
|
|
{
|
|
return TiledBlob::TileBuffer(Buffer, Tiles);
|
|
})
|
|
.then([this](BufferResultPtr Result)
|
|
{
|
|
/// Clear the Buffer here. We've now read back from this now
|
|
Buffer.reset();
|
|
ResetBuffer();
|
|
return Result;
|
|
});
|
|
}
|
|
|
|
bool TiledBlob::IsValid() const
|
|
{
|
|
/// If we have a Buffer, then we just check for the validity of that Buffer
|
|
if (Buffer)
|
|
return Buffer->IsValid();
|
|
|
|
/// Otherwise, iterate over the Tiles and check for the validity of each of the Tiles
|
|
bool Valid = true;
|
|
|
|
for (size_t RowId = 0; RowId < Rows() && Valid; RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Cols() && Valid; ColId++)
|
|
{
|
|
Valid &= Tiles[RowId][ColId] && Tiles[RowId][ColId]->IsValid();
|
|
}
|
|
}
|
|
|
|
return Valid;
|
|
}
|
|
|
|
bool TiledBlob::IsNull() const
|
|
{
|
|
return !IsValid();
|
|
}
|
|
|
|
FString TiledBlob::DisplayName() const
|
|
{
|
|
return Desc.Name;
|
|
}
|
|
|
|
void TiledBlob::AddLinkedBlob(BlobPtr LinkedBlob)
|
|
{
|
|
check(IsInGameThread());
|
|
check(LinkedBlob->IsTiled());
|
|
Blob::AddLinkedBlob(LinkedBlob);
|
|
}
|
|
|
|
bool TiledBlob::CanCalculateHash() const
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
for (size_t RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
|
|
BlobPtr TileBlob = Tiles[RowId][ColId];
|
|
DeviceBufferRef TileBuffer = TileBlob->GetBufferRef();
|
|
CHashPtr TileHash = TileBuffer->Hash();
|
|
|
|
if (!TileHash || !TileHash->IsFinal() || !TileBlob->CanCalculateHash())
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TiledBlob::SetMinMax(BlobPtr MinMax_)
|
|
{
|
|
/// TODO
|
|
check(MinMax_->IsTiled());
|
|
|
|
MinMax = MinMax_;
|
|
}
|
|
|
|
void TiledBlob::SetLODLevel(int32 Level, BlobPtr Blob_, BlobPtrW LODParent_, BlobPtrW LODSource_, bool AddToBlobber)
|
|
{
|
|
check(Blob_->IsTiled());
|
|
check(!LODParent_.expired());
|
|
check(!LODSource_.expired());
|
|
check(IsInGameThread());
|
|
|
|
LODParent = LODParent_;
|
|
LODSource = LODSource_;
|
|
|
|
TiledBlobPtr BlobObj = std::static_pointer_cast<TiledBlob>(Blob_);
|
|
|
|
/// Make sure everything matches up
|
|
check(Rows() == BlobObj->Rows());
|
|
check(Cols() == BlobObj->Cols());
|
|
|
|
BlobObj->bIsLODLevel = true;
|
|
|
|
std::vector<std::decay_t<AsyncBlobResultPtr>, std::allocator<std::decay_t<AsyncBlobResultPtr>>> Promises;
|
|
|
|
Promises.emplace_back(OnFinalise());
|
|
Promises.emplace_back(BlobObj->OnFinalise());
|
|
|
|
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
|
|
const int32 NumLevels = TextureHelper::CalcNumMipLevels(std::max(GetWidth(), GetHeight())) - 1;
|
|
|
|
if (NumLevels > 0)
|
|
LODLevels.resize(NumLevels);
|
|
}
|
|
|
|
check(Level > 0 && Level <= static_cast<int32>(LODLevels.size()));
|
|
|
|
LODLevels[Level - 1] = BlobObj;
|
|
BlobPtr SelfPtr = shared_from_this();
|
|
TiledBlobPtr Self = std::static_pointer_cast<TiledBlob>(SelfPtr);
|
|
|
|
for (int32 li = 0; li < Level - 1; li++)
|
|
{
|
|
BlobPtr lodLevel = LODLevels[li].lock();
|
|
|
|
if (lodLevel)
|
|
{
|
|
lodLevel->SetLODLevel(Level - li - 1, BlobObj, Self, LODSource_, AddToBlobber);
|
|
}
|
|
}
|
|
|
|
cti::when_all(Promises.begin(), Promises.end()).then([this, BlobObj, Self, Level, LODParent_, LODSource_, AddToBlobber]() mutable
|
|
{
|
|
Util::OnGameThread([this, BlobObj, Self, Level, LODParent_, LODSource_, AddToBlobber]()
|
|
{
|
|
for (size_t RowId = 0; RowId < Rows(); RowId++)
|
|
{
|
|
for (size_t ColId = 0; ColId < Cols(); ColId++)
|
|
{
|
|
BlobRef mipBlobXY = BlobObj->GetTile(RowId, ColId);
|
|
BlobRef baseBlobXY = Self->GetTile(RowId, ColId);
|
|
|
|
baseBlobXY->SetLODLevel(Level, mipBlobXY.get(), LODParent_, LODSource_, AddToBlobber);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void TiledBlob::FinaliseNow(bool bNoCalcHash, CHashPtr FixedHash)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
UE_LOG(LogJob, Verbose, TEXT("Finalised tiled BlobObj: %s"), *Desc.Name);
|
|
|
|
/// Debug: check that all Tiles are valid
|
|
for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
Tile->FinaliseNow(bNoCalcHash, nullptr);
|
|
}
|
|
}
|
|
|
|
if (SingleBlob)
|
|
SingleBlob->FinaliseNow(bNoCalcHash, nullptr);
|
|
|
|
bIsFinalised = true;
|
|
FinaliseTS = FDateTime::Now();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
TiledBlob_Promise::TiledBlob_Promise(TiledBlobPtr Source) : TiledBlob(Source->GetDescriptor(), Source->GetTiles())
|
|
{
|
|
}
|
|
|
|
TiledBlob_Promise::TiledBlob_Promise(DeviceBufferRef Buffer) : TiledBlob(Buffer)
|
|
{
|
|
}
|
|
|
|
TiledBlob_Promise::TiledBlob_Promise(const BufferDescriptor& Desc, size_t NumRows, size_t NumCols, CHashPtr Hash) : TiledBlob(Desc, NumRows, NumCols, Hash)
|
|
{
|
|
}
|
|
|
|
TiledBlob_Promise::~TiledBlob_Promise()
|
|
{
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob_Promise::Bind(const BlobTransform* Transform, const ResourceBindInfo& BindInfo)
|
|
{
|
|
check(IsInGameThread());
|
|
return TiledBlob::Bind(Transform, BindInfo);
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob_Promise::Unbind(const BlobTransform* Transform, const ResourceBindInfo& BindInfo)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
/// Cannot bind a promised BlobObj until it has been finalised
|
|
check(!bTiledTarget || IsValid());
|
|
return TiledBlob::Unbind(Transform, BindInfo);
|
|
}
|
|
|
|
void TiledBlob_Promise::AddLinkedBlob(BlobPtr LinkedBlob)
|
|
{
|
|
check(IsInGameThread());
|
|
check(LinkedBlob->IsTiled());
|
|
|
|
TiledBlobPtr TiledLinkedBlob = std::static_pointer_cast<TiledBlob>(LinkedBlob);
|
|
|
|
/// If the number of rows and columns don't match up then we don't do anything
|
|
if (TiledLinkedBlob->Rows() != Rows() || TiledLinkedBlob->Cols() != Cols())
|
|
return;
|
|
|
|
/// 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 (IsFinalised())
|
|
{
|
|
TiledLinkedBlob->FinaliseFrom(this);
|
|
}
|
|
|
|
if (TiledLinkedBlob->IsPromise())
|
|
{
|
|
TiledBlob_PromisePtr TiledPromiseLinkedBlob = std::static_pointer_cast<TiledBlob_Promise>(LinkedBlob);
|
|
TiledPromiseLinkedBlob->CachedBlob = std::static_pointer_cast<TiledBlob_Promise>(shared_from_this());
|
|
}
|
|
|
|
TiledBlob::AddLinkedBlob(LinkedBlob);
|
|
}
|
|
|
|
void TiledBlob_Promise::FinaliseNow(bool bNoCalcHash, CHashPtr FixedHash)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
UE_LOG(LogJob, Verbose, TEXT("Finalised promised BlobObj: %s"), *Desc.Name);
|
|
TiledBlob::FinaliseNow(bNoCalcHash, FixedHash);
|
|
|
|
/// Trigger the callbacks - Should always be the last step of finalize
|
|
NotifyCallbacks();
|
|
|
|
UpdateLinkedBlobs(true);
|
|
}
|
|
|
|
void TiledBlob_Promise::FinaliseFrom(TiledBlobPtr RHS)
|
|
{
|
|
FinaliseFrom(std::static_pointer_cast<Blob>(RHS).get());
|
|
}
|
|
|
|
void TiledBlob_Promise::FinaliseFrom(Blob* RHS)
|
|
{
|
|
check(RHS->IsTiled());
|
|
TiledBlob* RHSTiled = static_cast<TiledBlob*>(RHS);
|
|
|
|
if (RHSTiled->IsPromise())
|
|
{
|
|
TiledBlob_Promise* RHSTiledPromise = static_cast<TiledBlob_Promise*>(RHS);
|
|
bMakeSingleBlob = RHSTiledPromise->bMakeSingleBlob;
|
|
}
|
|
|
|
TiledBlob::FinaliseFrom(RHS);
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob_Promise::Finalise(bool bNoCalcHash, CHashPtr FixedHash)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (bIsFinalised)
|
|
return cti::make_ready_continuable<BufferResultPtr>(std::make_shared<BufferResult>());
|
|
|
|
/// cannot re-finalise something that's already been finalised
|
|
check(!bIsFinalised);
|
|
bIsFinalised = true;
|
|
FinaliseTS = FDateTime::Now();
|
|
|
|
/// If it is a tiled Target then finalise now
|
|
if (bTiledTarget || bMakeSingleBlob)
|
|
{
|
|
/// If this is not due to be turned into a single BlobObj then just return
|
|
if (!bMakeSingleBlob)
|
|
{
|
|
FinaliseNow(bNoCalcHash, FixedHash);
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
}
|
|
|
|
/// Otherwise we turn into a single BlobObj
|
|
return MakeSingleBlob()
|
|
.then([this, bNoCalcHash, FixedHash](BufferResultPtr SingleBuffer) {
|
|
if (bMakeSingleBlob)
|
|
FinaliseNow(bNoCalcHash, FixedHash);
|
|
|
|
return SingleBuffer;
|
|
});
|
|
}
|
|
|
|
/// We cannot make a surface that's already non-tiled
|
|
check(!bMakeSingleBlob && Desc.Width >= Tiles.Rows() && Desc.Height >= Tiles.Cols());
|
|
|
|
/// Otherwise we need to Tile the Buffer first
|
|
return TiledBlob::TileBuffer(Buffer, Tiles)
|
|
.then([this, bNoCalcHash, FixedHash](BufferResultPtr Result) mutable
|
|
{
|
|
T_Tiles<CHashPtr> TileHashes(Tiles.Rows(), Tiles.Cols());
|
|
|
|
for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
|
|
{
|
|
for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++)
|
|
{
|
|
check(Tiles[RowId][ColId]);
|
|
|
|
BlobPtr Tile = Tiles[RowId][ColId];
|
|
if (!bNoCalcHash)
|
|
Tile->CalcHash();
|
|
else
|
|
{
|
|
CHashPtr TileHash = Tile->Hash();
|
|
check(TileHash);
|
|
|
|
TileHashes[RowId][ColId] = TileHash;
|
|
}
|
|
}
|
|
}
|
|
|
|
CHashPtr FullHash = CHash::ConstructFromSources(TileHashes.Tiles());
|
|
|
|
/// Clear the Buffer here. We don't need it anymore
|
|
ResetBuffer();
|
|
|
|
/// Reset this back
|
|
bTiledTarget = true;
|
|
|
|
/// Tiled blobs always recalculate their has to match those of the children
|
|
FinaliseNow(true, FullHash);
|
|
|
|
return Result;
|
|
});
|
|
|
|
}
|
|
|
|
void TiledBlob_Promise::OnFinaliseInternal(BlobReadyCallback Callback) const
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
/// If we're already finalised then we can just return
|
|
if (bIsFinalised)
|
|
{
|
|
Callback(this);
|
|
return;
|
|
}
|
|
|
|
/// Otherwise we just queue the callbacks
|
|
Callbacks.push_back(std::move(Callback));
|
|
}
|
|
|
|
void TiledBlob_Promise::SetTile(int32 RowId, int32 ColId, BlobRef InTile)
|
|
{
|
|
check(IsValidTileIndex(RowId, ColId));
|
|
check(!InTile.expired());
|
|
|
|
BlobPtr Tile = InTile.lock();
|
|
|
|
/// Cannot have tiled BlobObj as part of another tiled BlobObj
|
|
check(!Tile->IsTiled());
|
|
|
|
TiledBlob::SetTile(RowId, ColId, InTile);
|
|
|
|
if (bMakeSingleBlob && Rows() == 1 && Cols() == 1)
|
|
Buffer = Tile->GetBufferRef();
|
|
|
|
/// Reset the hash
|
|
HashValue = nullptr;
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob_Promise::MakeSingleBlob()
|
|
{
|
|
if (!CachedBlob.expired())
|
|
{
|
|
return CachedBlob.lock()->MakeSingleBlob();
|
|
}
|
|
|
|
/// If already finalised then return
|
|
if (bIsFinalised)
|
|
return TiledBlob::MakeSingleBlob();
|
|
|
|
/// Otherwise set a flag
|
|
bMakeSingleBlob = true;
|
|
|
|
/// Set the size of the Tiles
|
|
Tiles.Clear();
|
|
Tiles.Resize(1, 1);
|
|
|
|
SingleBlob = std::make_shared<Blob>(Buffer);
|
|
TiledBlob::SetTile(0, 0, SingleBlob);
|
|
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
}
|
|
|
|
AsyncBufferResultPtr TiledBlob_Promise::Flush(const ResourceBindInfo& BindInfo)
|
|
{
|
|
if (((Tiles.Rows() && Tiles.Cols()) || !Buffer) && bTiledTarget)
|
|
return cti::make_ready_continuable(std::make_shared<BufferResult>());
|
|
|
|
return TiledBlob::Flush(BindInfo);
|
|
}
|
|
|
|
TiledBlob_Promise& TiledBlob_Promise::operator = (const TiledBlob& RHS)
|
|
{
|
|
TiledBlob::operator = (RHS);
|
|
return *this;
|
|
}
|
|
|
|
TiledBlob_Promise& TiledBlob_Promise::operator = (const TiledBlob_Promise& RHS)
|
|
{
|
|
TiledBlob::operator = (RHS);
|
|
bMakeSingleBlob = RHS.bMakeSingleBlob;
|
|
return *this;
|
|
}
|
|
|
|
void TiledBlob_Promise::CopyResolveLateBound(BlobPtr RHS_)
|
|
{
|
|
check(RHS_ != nullptr);
|
|
|
|
TiledBlob_Promise* RHS = dynamic_cast<TiledBlob_Promise*>(RHS_.get());
|
|
|
|
/// If this isn't a
|
|
if (RHS)
|
|
*this = *RHS;
|
|
else if (dynamic_cast<TiledBlob*>(RHS_.get()))
|
|
TiledBlob::CopyResolveLateBound(RHS_);
|
|
else
|
|
{
|
|
TiledBlob::CopyResolveLateBound(RHS_);
|
|
bMakeSingleBlob = true;
|
|
}
|
|
|
|
NotifyCallbacks();
|
|
}
|
|
|
|
void TiledBlob_Promise::NotifyCallbacks()
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
/// Trigger the callbacks - Should always be the last step of finalize
|
|
for (BlobReadyCallback& Callback : Callbacks)
|
|
Callback(this);
|
|
|
|
Callbacks.clear();
|
|
}
|
|
|
|
void TiledBlob_Promise::ResetForReplay()
|
|
{
|
|
bIsFinalised = false;
|
|
++ReplayCount;
|
|
}
|