251 lines
7.3 KiB
C++
251 lines
7.3 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RuntimeAssetCacheBuilders.h"
|
|
#include "Serialization/LargeMemoryWriter.h"
|
|
#include "RuntimeAssetCacheModule.h"
|
|
#include "TextureResource.h"
|
|
#include "Engine/Texture.h"
|
|
#include "Engine/Texture2D.h"
|
|
|
|
void URuntimeAssetCacheBuilder_ObjectBase::SaveNewAssetToCache(UObject* NewAsset)
|
|
{
|
|
SetAsset(NewAsset);
|
|
GetFromCacheAsync(OnAssetCacheComplete);
|
|
}
|
|
|
|
void URuntimeAssetCacheBuilder_ObjectBase::SetAsset(UObject* NewAsset)
|
|
{
|
|
Asset = NewAsset;
|
|
OnSetAsset(Asset);
|
|
}
|
|
|
|
FVoidPtrParam URuntimeAssetCacheBuilder_ObjectBase::Build()
|
|
{
|
|
// There was no cached asset, so this is expecting us to return the data that needs to be saved to disk
|
|
// If we have no asset created yet, just return null. That will trigger the async creation of the asset.
|
|
// If we do have an asset, serialize it here into a good format and return a pointer to that memory buffer.
|
|
if (Asset)
|
|
{
|
|
FLargeMemoryWriter Ar(GetSerializedDataSizeEstimate(), /*bIsPersistent*/ true);
|
|
SerializeAsset(Ar);
|
|
|
|
FVoidPtrParam Result(Ar.GetData(), Ar.TotalSize());
|
|
Ar.ReleaseOwnership();
|
|
return Result;
|
|
}
|
|
|
|
// null
|
|
return FVoidPtrParam::NullPtr();
|
|
}
|
|
|
|
void URuntimeAssetCacheBuilder_ObjectBase::GetFromCacheAsync(const FOnAssetCacheComplete& OnComplete)
|
|
{
|
|
OnAssetCacheComplete = OnComplete;
|
|
GetFromCacheAsyncCompleteDelegate.BindDynamic(this, &URuntimeAssetCacheBuilder_ObjectBase::GetFromCacheAsyncComplete);
|
|
CacheHandle = GetRuntimeAssetCache().GetAsynchronous(this, GetFromCacheAsyncCompleteDelegate);
|
|
}
|
|
|
|
void URuntimeAssetCacheBuilder_ObjectBase::GetFromCacheAsyncComplete(int32 Handle, FVoidPtrParam DataPtr)
|
|
{
|
|
if (Handle != CacheHandle)
|
|
{
|
|
// This can sometimes happen when the world changes and everything couldn't cancel correctly. Just ignore any callbacks that don't match handles.
|
|
if (DataPtr.Data != nullptr)
|
|
{
|
|
FMemory::Free(DataPtr.Data);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (DataPtr.Data != nullptr)
|
|
{
|
|
// Success! Finished loading or saving data from cache
|
|
// If saving, then we already have the right data and we can just report success
|
|
if (Asset == nullptr)
|
|
{
|
|
// If loading, we now need to serialize the data into a usable format
|
|
|
|
// Make sure Asset is set up to be loaded into
|
|
OnAssetPreLoad();
|
|
|
|
FBufferReader Ar(DataPtr.Data, DataPtr.DataSize, false);
|
|
SerializeAsset(Ar);
|
|
|
|
// Perform any specific init functions after load
|
|
OnAssetPostLoad();
|
|
}
|
|
|
|
// Free the buffer memory on both save and load
|
|
// On save the buffer gets created in Build()
|
|
// On load the buffer gets created in FRuntimeAssetCacheBackend::GetCachedData()
|
|
FMemory::Free(DataPtr.Data);
|
|
CacheHandle = 0;
|
|
|
|
// Success!
|
|
OnAssetCacheComplete.ExecuteIfBound(this, true);
|
|
}
|
|
else
|
|
{
|
|
// Data not on disk. Kick off the creation process.
|
|
// Once complete, call GetFromCacheAsync() again and it will loop back to this function, but should succeed.
|
|
if (!bProcessedCacheMiss)
|
|
{
|
|
bProcessedCacheMiss = -1;
|
|
OnAssetCacheMiss();
|
|
}
|
|
else
|
|
{
|
|
// Failed
|
|
OnAssetCacheComplete.ExecuteIfBound(this, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UExampleTextureCacheBuilder::OnSetAsset(UObject* NewAsset)
|
|
{
|
|
Texture = Cast<UTexture2D>(NewAsset);
|
|
}
|
|
|
|
void UExampleTextureCacheBuilder::OnAssetCacheMiss_Implementation()
|
|
{
|
|
// Override and create the new asset here (this is where we would render to a render target, then get the result)
|
|
// For this example we will simply load an existing texture
|
|
UTexture2D* NewTexture = LoadObject<UTexture2D>(nullptr, *AssetName);
|
|
|
|
// Make sure the new asset gets properly cached for next time.
|
|
SaveNewAssetToCache(NewTexture);
|
|
}
|
|
|
|
void UExampleTextureCacheBuilder::SerializeAsset(FArchive& Ar)
|
|
{
|
|
if (Texture && Texture->GetPlatformData())
|
|
{
|
|
FTexturePlatformData* PlatformData = Texture->GetPlatformData();
|
|
UEnum* PixelFormatEnum = UTexture::GetPixelFormatEnum();
|
|
|
|
Ar << PlatformData->SizeX;
|
|
Ar << PlatformData->SizeY;
|
|
Ar << PlatformData->PackedData;
|
|
if (Ar.IsLoading())
|
|
{
|
|
FString PixelFormatString;
|
|
Ar << PixelFormatString;
|
|
PlatformData->PixelFormat = (EPixelFormat)PixelFormatEnum->GetValueByName(*PixelFormatString);
|
|
}
|
|
else if (Ar.IsSaving())
|
|
{
|
|
FString PixelFormatString = PixelFormatEnum->GetNameByValue(PlatformData->PixelFormat).GetPlainNameString();
|
|
Ar << PixelFormatString;
|
|
}
|
|
|
|
if (PlatformData->GetHasOptData())
|
|
{
|
|
Ar << PlatformData->OptData;
|
|
}
|
|
|
|
int32 NumMips = PlatformData->Mips.Num();
|
|
int32 FirstMip = 0;
|
|
int32 LastMip = NumMips;
|
|
|
|
TArray<uint32> SavedFlags;
|
|
if (Ar.IsSaving())
|
|
{
|
|
// Force resident mips inline
|
|
SavedFlags.Empty(NumMips);
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; ++MipIndex)
|
|
{
|
|
SavedFlags.Add(PlatformData->Mips[MipIndex].BulkData.GetBulkDataFlags());
|
|
PlatformData->Mips[MipIndex].BulkData.SetBulkDataFlags(BULKDATA_ForceInlinePayload | BULKDATA_SingleUse);
|
|
}
|
|
|
|
// Don't save empty Mips
|
|
while (FirstMip < NumMips && PlatformData->Mips[FirstMip].BulkData.GetBulkDataSize() <= 0)
|
|
{
|
|
FirstMip++;
|
|
}
|
|
for (int32 MipIndex = FirstMip + 1; MipIndex < NumMips; ++MipIndex)
|
|
{
|
|
if (PlatformData->Mips[FirstMip].BulkData.GetBulkDataSize() <= 0)
|
|
{
|
|
// This means there are empty tail mips, which should never happen
|
|
// If it does, simply don't save any mips after this point.
|
|
LastMip = MipIndex;
|
|
break;
|
|
}
|
|
}
|
|
int32 NumMipsSaved = LastMip - FirstMip;
|
|
Ar << NumMipsSaved;
|
|
}
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
Ar << NumMips;
|
|
LastMip = NumMips;
|
|
PlatformData->Mips.Empty(NumMips);
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; ++MipIndex)
|
|
{
|
|
PlatformData->Mips.Add(new FTexture2DMipMap(0, 0));
|
|
}
|
|
}
|
|
|
|
EBulkDataLockFlags LockFlags = Ar.IsSaving() ? LOCK_READ_ONLY : LOCK_READ_WRITE;
|
|
for (int32 MipIndex = FirstMip; MipIndex < LastMip; ++MipIndex)
|
|
{
|
|
FTexture2DMipMap& Mip = PlatformData->Mips[MipIndex];
|
|
int32 SizeX = Mip.SizeX;
|
|
int32 SizeY = Mip.SizeY;
|
|
Ar << SizeX;
|
|
Ar << SizeY;
|
|
Mip.SizeX = SizeX;
|
|
Mip.SizeY = SizeY;
|
|
|
|
int32 BulkDataSizeInBytes = Mip.BulkData.GetBulkDataSize();
|
|
Ar << BulkDataSizeInBytes;
|
|
if (BulkDataSizeInBytes > 0)
|
|
{
|
|
void* BulkMipData = Mip.BulkData.Lock(LockFlags);
|
|
if (Ar.IsLoading())
|
|
{
|
|
int32 ElementCount = BulkDataSizeInBytes / Mip.BulkData.GetElementSize();
|
|
BulkMipData = Mip.BulkData.Realloc(ElementCount);
|
|
}
|
|
Ar.Serialize(BulkMipData, BulkDataSizeInBytes);
|
|
Mip.BulkData.Unlock();
|
|
}
|
|
}
|
|
|
|
// Restore flags
|
|
if (Ar.IsSaving())
|
|
{
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; ++MipIndex)
|
|
{
|
|
PlatformData->Mips[MipIndex].BulkData.SetBulkDataFlags(SavedFlags[MipIndex]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UExampleTextureCacheBuilder::OnAssetPreLoad()
|
|
{
|
|
// Create an object to load the data into
|
|
UTexture2D* NewTexture = NewObject<UTexture2D>();
|
|
NewTexture->SetPlatformData(new FTexturePlatformData());
|
|
NewTexture->NeverStream = true;
|
|
|
|
SetAsset(NewTexture);
|
|
}
|
|
|
|
void UExampleTextureCacheBuilder::OnAssetPostLoad()
|
|
{
|
|
Texture->UpdateResource();
|
|
}
|
|
|
|
int64 UExampleTextureCacheBuilder::GetSerializedDataSizeEstimate()
|
|
{
|
|
int64 DataSize = sizeof(FTexturePlatformData);
|
|
DataSize += sizeof(FString) + (sizeof(TCHAR) * 12); // Guess the size of the pixel format string (most are less than 12 characters, but we don't need to be exact)
|
|
DataSize += Texture->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal); // Size of all the mips
|
|
DataSize += (sizeof(int32) * 3) * Texture->GetNumMips(); // Each mip stores its X and Y size, and its BulkDataSize
|
|
return DataSize;
|
|
}
|