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

952 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Device_FX.h"
#include "TextureGraphEngine.h"
#include "DeviceBuffer_FX.h"
#include "Device/Mem/Device_Mem.h"
#include "Device/Mem/DeviceBuffer_Mem.h"
#include "Kismet/KismetRenderingLibrary.h"
#include "TextureGraphEngineGameInstance.h"
#include "Stats/Stats.h"
#include "FxMat/MaterialManager.h"
#include "Engine/Canvas.h"
#include "FxMat/RenderMaterial_FX.h"
#include "2D/Tex.h"
#include "Device/DeviceManager.h"
#include "Device/DeviceNativeTask.h"
#include "2D/TexArray.h"
#include <Engine/TextureRenderTarget2DArray.h>
#include <ScreenRendering.h>
#include <TextureResource.h>
#include <Engine/TextureRenderTarget2D.h>
#include "ClearQuad.h"
DECLARE_GPU_STAT_NAMED(Stat_CombineToTiles, TEXT("CombineToTilesProfile"));
DECLARE_GPU_STAT_NAMED(Stat_SplitFromTiles, TEXT("SplitFromTilesProfile"));
DECLARE_CYCLE_STAT(TEXT("Device_FX_CombineToTiles"), STAT_Device_FX_CombineFromTiles, STATGROUP_TextureGraphEngine);
DECLARE_CYCLE_STAT(TEXT("Device_FX_FillTextureArray"), STAT_Device_FX_FillTextureArray, STATGROUP_TextureGraphEngine);
DECLARE_CYCLE_STAT(TEXT("Device_FX_DrawTilesToBuffer"), STAT_Device_FX_DrawTilesToBuffer, STATGROUP_TextureGraphEngine);
DECLARE_CYCLE_STAT(TEXT("Device_FX_SplitToTiles"), STAT_Device_FX_SplitToTiles, STATGROUP_TextureGraphEngine);
DECLARE_CYCLE_STAT(TEXT("Device_FX_AllocateRendertarget"), STAT_Device_FX_AllocateRendertarget, STATGROUP_TextureGraphEngine);
size_t Device_FX::s_maxRenderBatch = 16;
Device_FX::Device_FX() : Device(DeviceType::FX, new DeviceBuffer_FX(this, BufferDescriptor(), std::make_shared<CHash>(0xDeadBeef, true)))
{
ShouldPrintStats = false;
ExecThreadType = ENamedThreads::ActualRenderingThread;
RendererModule = FModuleManager::GetModulePtr<IRendererModule>("Renderer");
}
Device_FX::~Device_FX()
{
}
void Device_FX::Free()
{
/// clear the RT cache
FreeCacheInternal(RTCache);
FreeCacheInternal(RTArrayCache);
FreeRTList(RTUsed);
GCTextures();
Device::Free();
}
void Device_FX::AddReferencedObjects(FReferenceCollector& Collector)
{
for (auto Iter : RTCache)
{
RTList* List = Iter.second;
if (List)
{
for (auto& RT : *List)
{
Collector.AddReferencedObject(RT);
}
}
}
for (auto Iter : RTArrayCache)
{
RTList* List = Iter.second;
if (List)
{
for (auto& RT : *List)
{
Collector.AddReferencedObject(RT);
}
}
}
for (auto& RT : RTUsed)
{
Collector.AddReferencedObject(RT);
}
}
FString Device_FX::GetReferencerName() const
{
return Name();
}
void Device_FX::FreeCacheInternal(RenderTargetCache& TargetRTCache)
{
for (auto Iter : TargetRTCache)
{
RTList* List = Iter.second;
if (List)
{
for (UTextureRenderTarget2D* RT : *List)
{
check(RT);
}
List->clear();
delete List;
}
}
TargetRTCache.clear();
}
void Device_FX::FreeRTList(RTList& RTList)
{
//for (UTextureRenderTarget2D* RT : RTList)
//{
// check(RT);
//}
RTList.clear();
}
cti::continuable<int32> Device_FX::Use() const
{
if (IsInRenderingThread())
{
const_cast<Device_FX*>(this)->CallDeviceUpdate();
return cti::make_ready_continuable(0);
}
return cti::make_continuable<int32>([this](auto&& Promise) mutable
{
ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)([this, Promise = std::forward<decltype(Promise)>(Promise)](FRHICommandListImmediate& DevRHI) mutable
{
const_cast<Device_FX*>(this)->CallDeviceUpdate(); // OK as long as the type object isn't const
Promise.set_value(0);
});
});
}
void Device_FX::GetStatArray(TArray<FString>& ResourceArrayTiled,
TArray<FString>& ResourceArrayUnTiled,
TArray<FString>& TooltipListTiled,
TArray<FString>& TooltipListUnTiled,
FString& TotalStats)
{
size_t RTMemUnused = 0;
size_t RTCountUnused = 0;
size_t RTMemUsed = 0;
for (auto Iter = RTCache.begin(); Iter != RTCache.end(); Iter++)
{
RTList* RenderTargets = Iter->second;
if (RenderTargets)
{
for (UTextureRenderTarget2D* RT : *RenderTargets)
{
auto RTFormat = RT->RenderTargetFormat;
auto PixelFormat = TextureHelper::GetPixelFormatFromRenderTargetFormat(RTFormat);
size_t Size = RT->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal);
FString Info(FString::Printf(TEXT("[Unused] Name : %s "), *RT->GetName()));
FString Tooltip(FString::Printf(TEXT("Ptr : %x , Hash : %llu, Memory Size : %0.2fKB"), RT, Iter->first, (float)Size / (1024.0f)));
ResourceArrayUnTiled.Add(Info);
TooltipListUnTiled.Add(Tooltip);
RTMemUnused += Size;
RTCountUnused++;
}
}
}
for (auto& RT : RTUsed)
{
auto RTFormat = RT->RenderTargetFormat;
auto PixelFormat = TextureHelper::GetPixelFormatFromRenderTargetFormat(RTFormat);
size_t Size = RT->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal);
FString Info(FString::Printf(TEXT("[Used] Name : %s"), *RT->GetName()));
FString Tooltip(FString::Printf(TEXT("Ptr : %x , Memory Size : %0.2fKB"), RT.Get(), (float)Size / (1024.0f)));
ResourceArrayUnTiled.Add(Info);
TooltipListUnTiled.Add(Tooltip);
RTMemUsed += Size;
}
float usedRTMemory = (float)RTMemUsed / (1024.0f * 1024.0f);
float unusedRTMemory = (float)RTMemUnused / (1024.0f * 1024.0f);
TotalStats = FString::Printf(TEXT("Total Resources [UNUSED] : %llu, Total Memory [UNUSED] : %0.2fMB, Total Resources [USED] : %llu, Total Memory [USED] : %0.2f MB"), RTCountUnused, unusedRTMemory, RTUsed.size(), usedRTMemory);
}
AsyncDeviceBufferRef Device_FX::CombineFromTiles(const CombineSplitArgs& CombineArgs)
{
check(IsInGameThread());
SCOPE_CYCLE_COUNTER(STAT_Device_FX_CombineFromTiles);
const T_Tiles<DeviceBufferRef>& Tiles = CombineArgs.Tiles;
auto Buffer = CombineArgs.Buffer;
auto IsArray = CombineArgs.IsArray;
CHashPtr BufferHash = CombineArgs.BufferHash;
check(Tiles.Rows() > 1 && Tiles.Cols() > 1);
bool bIsCompatible = true;
BufferDescriptor Desc = Tiles[0][0]->Descriptor();
Desc.Width = Desc.Width * Tiles.Cols();
Desc.Height = Desc.Height * Tiles.Rows();
/// If any of the buffers is not compatible, then we fallback to the default implementation
if (!bIsCompatible)
return Device::CombineFromTiles(CombineArgs);
if (!Buffer)
Buffer = Create(Desc, BufferHash);
DeviceBuffer_FX* FXBuffer = static_cast<DeviceBuffer_FX*>(Buffer.get());
AsyncBufferResultPtr Promise = cti::make_ready_continuable(std::make_shared<BufferResult>());
if (!IsArray)
Promise = FXBuffer->AllocateRenderTarget();
return (std::move(Promise)).then([this, Tiles, Buffer, IsArray]()
{
check(IsInGameThread());
if (IsArray)
return FillTextureArray_Deferred(Buffer, Tiles);
return DrawTilesToBuffer_Deferred(Buffer, Tiles);
});
}
AsyncDeviceBufferRef Device_FX::FillTextureArray_Deferred(DeviceBufferRef Buffer, const T_Tiles<DeviceBufferRef>& Tiles)
{
DeviceNativeTaskPtr Task = DeviceNativeTask_Lambda::Create(this, (int32)E_Priority::kSystem, TEXT("FillTextureArray"), [this, Buffer, Tiles]() mutable -> int32
{
SCOPE_CYCLE_COUNTER(STAT_Device_FX_FillTextureArray);
check(IsInRenderingThread());
DeviceBuffer_FX* FXBuffer = static_cast<DeviceBuffer_FX*>(Buffer.get());
/// This texture must have been created beforehand AND it must be a render target
TexPtr DstTex = FXBuffer->GetTexture();
check(DstTex);
UTextureRenderTarget2D* RTDest = (UTextureRenderTarget2D*)DstTex->GetTexture();
FTextureRHIRef RTResDest = ((FTextureRenderTarget2DResource*)RTDest->GetResource())->GetTextureRHI();
FRHICommandListImmediate& DevRHI = RHI();
int32 Index = 0;
for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
{
for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++, Index++)
{
DeviceBuffer_FX* TileBuffer = static_cast<DeviceBuffer_FX*>(Tiles[RowId][ColId].get());
TexPtr TileTex = TileBuffer->GetTexture();
check(TileTex);
int32 TileWidth = TileTex->GetWidth();
int32 TileHeight = TileTex->GetHeight();
UTextureRenderTarget2D* TileRT = TileTex->GetRenderTarget();
FRHITexture* TileResource = TileTex->GetRHITexture();
FRHICopyTextureInfo CopyInfo;
CopyInfo.Size = TileResource->GetSizeXYZ();
CopyInfo.DestSliceIndex = Index;
DevRHI.CopyTexture(TileResource, RTResDest, CopyInfo);
}
}
return 0;
});
return cti::make_ready_continuable(Buffer);
}
AsyncDeviceBufferRef Device_FX::DrawTilesToBuffer_Deferred(DeviceBufferRef Buffer, const T_Tiles<DeviceBufferRef>& Tiles, const DrawTilesSettings* DrawSettings, FLinearColor* ClearColor)
{
DeviceNativeTaskPtr Task = DeviceNativeTask_Lambda::Create(this, (int32)E_Priority::kSystem, TEXT("DrawTilesToBuffer"), [this, Buffer, Tiles, DrawSettings, ClearColor]() mutable -> int32
{
SCOPE_CYCLE_COUNTER(STAT_Device_FX_DrawTilesToBuffer);
check(IsInRenderingThread());
DeviceBuffer_FX* FXBuffer = static_cast<DeviceBuffer_FX*>(Buffer.get());
/// This texture must have been created beforehand AND it must be a render target
TexPtr DstTex = FXBuffer->GetTexture();
check(DstTex);
UTextureRenderTarget2D* RTDest = DstTex->GetRenderTarget();
FTextureRHIRef RTResDest = ((FTextureRenderTarget2DResource*)RTDest->GetResource())->GetTextureRHI();
FRHICommandListImmediate& DevRHI = RHI();
const uint32 ViewportWidth = DstTex->GetWidth();
const uint32 ViewportHeight = DstTex->GetHeight();
const FIntPoint TargetSize(ViewportWidth, ViewportHeight);
//Render target is about to be renderered into, transition to RTV
DevRHI.Transition(FRHITransitionInfo(RTResDest, ERHIAccess::Unknown, ERHIAccess::RTV));
FRHIRenderPassInfo RenderPassInfo(RTResDest, ERenderTargetActions::Load_Store);
DevRHI.BeginRenderPass(RenderPassInfo, TEXT("CopyTexture"));
if (ClearColor)
DrawClearQuad(DevRHI, *ClearColor);
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
DevRHI.ApplyCachedRenderTargets(GraphicsPSOInit);
const auto FeatureLevel = GMaxRHIFeatureLevel;
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
TShaderMapRef<FScreenPS> PixelShader(ShaderMap);
FxMaterial::InitPSO_Default(GraphicsPSOInit, VertexShader.GetVertexShader(), PixelShader.GetPixelShader());
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
FRHISamplerState* PixelSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
bool bUseInputPositions = DrawSettings != nullptr ? true : false;
/// Verify that there are either no DrawSettings or they are setup correctly to match the input array dimensions
check(!DrawSettings || (DrawSettings->Position.Rows() == Tiles.Rows() && DrawSettings->Position.Cols() == Tiles.Cols()));
check(!DrawSettings || (DrawSettings->Size.Rows() == Tiles.Rows() && DrawSettings->Size.Cols() == Tiles.Cols()));
SetGraphicsPipelineState(DevRHI, GraphicsPSOInit, 0);
for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
{
uint32 XOffset = 0;
for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++)
{
DeviceBuffer_FX* TileBuffer = static_cast<DeviceBuffer_FX*>(Tiles(RowId, ColId).get());
TexPtr TileTex = TileBuffer->GetTexture();
UE_LOG(LogDevice, Verbose, TEXT("Combine tiles: %s [%d, %d] => %llu [%s]"), *FXBuffer->GetName(), RowId, ColId, TileBuffer->Hash(false)->Value(), *TileBuffer->GetName());
check(TileTex);
UTextureRenderTarget2D* TileRT = TileTex->GetRenderTarget();
FRHITexture* TileResource = TileTex->GetRHITexture();
float SrcTextureWidth = TileTex->GetWidth();
float SrcTextureHeight = TileTex->GetHeight();
uint32 X1 = XOffset; ///ColId * SrcTextureWidth;
uint32 Y1 = 0; ///RowId * SrcTextureHeight;
if (!bUseInputPositions)
{
for (int32 TAIndex = 0; TAIndex < RowId; TAIndex++)
{
DeviceBuffer_FX* TileAbove = static_cast<DeviceBuffer_FX*>(Tiles(TAIndex, ColId).get());
Y1 += TileAbove->Descriptor().Height;
}
}
else
{
const FIntPoint& TilePos = DrawSettings->Position(RowId, ColId);
X1 = (uint32)TilePos.X;
Y1 = (uint32)TilePos.Y;
const FIntPoint& TileSize = DrawSettings->Size(RowId, ColId);
SrcTextureWidth = TileSize.X;
SrcTextureHeight = TileSize.Y;
}
DevRHI.SetViewport(X1, Y1, 0, X1 + ViewportWidth, Y1 + ViewportHeight, 0);
SetShaderParametersLegacyPS(DevRHI, PixelShader, PixelSampler, TileResource);
RendererModule->DrawRectangle(
DevRHI,
0, 0,
SrcTextureWidth, SrcTextureHeight,
0.0f, 0.0f,
1, 1,
TargetSize,
FIntPoint(1, 1),
VertexShader,
EDRF_Default);
XOffset += SrcTextureWidth;
}
}
}
DevRHI.EndRenderPass();
//Render target is done rendering transition to Read
DevRHI.Transition(FRHITransitionInfo(RTResDest, ERHIAccess::RTV, ERHIAccess::SRVMask));
return 0;
});
return cti::make_ready_continuable(Buffer);
}
AsyncDeviceBufferRef Device_FX::SplitToTiles_Internal(const CombineSplitArgs& SplitArgs)
{
const T_Tiles<DeviceBufferRef>& Tiles = SplitArgs.Tiles;
auto Buffer = SplitArgs.Buffer;
auto IsArray = SplitArgs.IsArray;
DeviceNativeTask_Lambda::Create(this, (int32)E_Priority::kSystem, TEXT("SplitToTiles_Internal"), [this, Buffer, Tiles, IsArray]() -> int32
{
DeviceBuffer_FX* FXBuffer = static_cast<DeviceBuffer_FX*>(Buffer.get());
/// This texture must have been created beforehand AND it must be a render target
TexPtr SrcTex = FXBuffer->GetTexture();
FTextureRHIRef SourceRes = ((UTextureRenderTarget2D*)SrcTex->GetTexture())->GetRenderTargetResource()->GetRenderTargetTexture();
for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
{
for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++)
{
/// Using RHI to split Tiles to Buffer
DeviceBuffer_FX* TileBuffer = static_cast<DeviceBuffer_FX*>(Tiles[RowId][ColId].get());
TexPtr TileTex = TileBuffer->GetTexture();
check(TileTex);
check(TileTex->GetRenderTarget());
FTextureRHIRef Texture2DRes = ((FTextureRenderTarget2DResource*)TileTex->GetRenderTarget()->GetResource())->GetTextureRHI();
FRHICopyTextureInfo CopyInfo;
CopyInfo.SourcePosition = FIntVector(RowId * TileTex->GetHeight(), ColId * TileTex->GetWidth(), 0);
CopyInfo.DestPosition = FIntVector(0, 0, 0);
CopyInfo.Size = FIntVector(TileTex->GetWidth(), TileTex->GetHeight(), 0);
FRHICommandListImmediate& DevRHI = GRHICommandList.GetImmediateCommandList();
DevRHI.CopyTexture(SourceRes, Texture2DRes, CopyInfo);
}
}
return 0;
});
return cti::make_ready_continuable(Buffer);
}
AsyncDeviceBufferRef Device_FX::SplitToTiles(const CombineSplitArgs& SplitArgs)
{
SCOPE_CYCLE_COUNTER(STAT_Device_FX_SplitToTiles)
//check(IsInRenderingThread());
#define NATIVE_SPLIT_TILES
const T_Tiles<DeviceBufferRef>& Tiles = SplitArgs.Tiles;
auto Buffer = SplitArgs.Buffer;
auto IsArray = SplitArgs.IsArray;
const BufferDescriptor& Desc = Buffer->Descriptor();
BufferDescriptor TileDesc = Desc;
TileDesc.Width = Desc.Width / Tiles.Rows();
TileDesc.Height = Desc.Height / Tiles.Cols();
std::vector<AsyncBufferResultPtr> promises;
/// first of all we need to ensure that all the Tiles are ready
for (int32 RowId = 0; RowId < Tiles.Rows(); RowId++)
{
for (int32 ColId = 0; ColId < Tiles.Cols(); ColId++)
{
if (!Tiles[RowId][ColId])
{
DeviceBufferRef Tile = Tiles[RowId][ColId];
TileDesc.Name = FString::Format(TEXT("{0}-{1}.{2}"), { Buffer->Descriptor().Name, RowId, ColId });
DeviceBuffer_FX* FXBuffer = new DeviceBuffer_FX(this, TileDesc, nullptr);
DeviceBufferRef FXBufferRef = AddNewRef_Internal(FXBuffer);
#ifdef NATIVE_SPLIT_TILES
AsyncBufferResultPtr Promise = FXBuffer->AllocateRenderTarget();
promises.push_back(std::move(Promise));
#endif /// NATIVE_SPLIT_TILES
Tile = FXBufferRef;
}
}
}
#ifdef NATIVE_SPLIT_TILES
/// If there are no pending promises, then we can be ready immediately
if (!promises.size())
return SplitToTiles_Internal(SplitArgs);
/// Otherwise, we wait ...
return cti::when_all(promises.begin(), promises.end()).then([this, SplitArgs]()
{
return cti::make_continuable<DeviceBufferRef>([this, SplitArgs](auto&& Promise)
{
//Util::OnRenderingThread([this, Buffer, &Tiles, Promise = std::forward<decltype(Promise)>(Promise)](FRHICommandListImmediate& DevRHI) mutable
//{
/// And then, once we're ready, we split
SplitToTiles_Internal(SplitArgs).then([this, Promise = std::forward<decltype(Promise)>(Promise)](DeviceBufferRef Result) mutable
{
Promise.set_value(Result);
});
//});
});
});
#else
return cti::make_continuable<DeviceBufferRef>([this, Buffer, &Tiles](auto&& Promise)
{
Device::SplitToTiles_Generic(Buffer, Tiles).then([this, Promise = std::forward<decltype(Promise)>(Promise)](DeviceBufferRef Result) mutable
{
Promise.set_value(Result);
});
});
#endif /// NATIVE_SPLIT_TILES
}
UTextureRenderTarget2D* Device_FX::CreateRenderTarget(const BufferDescriptor& Desc)
{
check(Desc.Width && Desc.Height);
FName Name = *FString::Printf(TEXT("%s [RT]"), *Desc.Name);
auto Package = Util::GetRenderTargetPackage();
UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(Package, Name, Tex::Flags);
check(RT);
RT->ClearColor = FLinearColor::Black;
RT->bForceLinearGamma = true;
RT->RenderTargetFormat = TextureHelper::GetRenderTargetFormatFromPixelFormat(Desc.PixelFormat());
RT->SizeX = Desc.Width;
RT->SizeY = Desc.Height;
//UE_LOG(LogTexture, Log, TEXT("Mips number: %d"), _rt->GetNumMips());
RT->LODBias = 0;
//_rt->bAutoGenerateMips = false;
//_rt->InitCustomFormat(_desc.width, _desc.height, _desc.format, forceLinearGamma);
//UE_LOG(LogTexture, Log, TEXT("Creating render target: %s"), *_desc.Name);
RT->UpdateResource();
return RT;
}
void Device_FX::FreeRenderTarget(HashType HashValue, UTextureRenderTarget2D* RT)
{
FScopeLock lock(&GC_RTCache);
RTUsed.remove(RT);
UE_LOG(LogDevice, VeryVerbose, TEXT("Reclaimed RT: Name : %s : %llu [Ptr: 0x%llu, Size: %dx%d]"), *RT->GetName(), HashValue, RT, RT->SizeX, RT->SizeY);
/// We don't wanna be keeping these around in test mode.
auto Iter = RTCache.find(HashValue);
if (Iter == RTCache.end())
{
/// If the List wasnt previously found, that means RT was not created through AllocateRenderTarget
/// Hence there is no need to save this RT , so we simply release the resources
/// This is possible since FreeRenderTarget is called on deallocation, and deallocation can occur on shared ptr operator overloading as well
RT->ReleaseResource();
return;
}
RTList* List = Iter->second;
List->push_back(RT);
}
TexPtr Device_FX::AllocateRenderTarget(const BufferDescriptor& Desc)
{
const uint16 Depth = 1;
const uint16 ArraySize = 1;
uint8 NumMips = 1;
uint8 NumSamples = 1;
uint32 ExtData = 0;
FRHITextureCreateInfo rhiDesc = FRHITextureDesc(ETextureDimension::Texture2D, ETextureCreateFlags::RenderTargetable, Desc.PixelFormat(),
FClearValueBinding(Desc.DefaultValue), FIntPoint(Desc.Width, Desc.Height), Depth, ArraySize, NumMips, NumSamples, ExtData);
check(IsInGameThread());
SCOPE_CYCLE_COUNTER(STAT_Device_FX_AllocateRendertarget)
RTList* List = nullptr;
HashType Hash = Desc.FormatHashValue();
{
FScopeLock lock(&GC_RTCache);
auto Iter = RTCache.find(Hash);
if (Iter == RTCache.end())
{
List = new RTList();
RTCache[Hash] = List;
}
else
List = Iter->second;
}
if (!List->empty())
{
/// otherwise, we can just pull it from the List
UTextureRenderTarget2D* RT = List->back();
List->pop_back();
FString existingName = RT->GetName();
//UE_LOG(LogDevice, Verbose, TEXT("Rename: %s => %s"), *existingName, *Desc.Name);
//RT->Rename(*Desc.Name);
/// The RT resource is gone! then we need to create a new one
if (RT->GetResource())
{
check(IsInGameThread());
if (TextureGraphEngine::GetBlobber()->IsCacheEnabled())
RTUsed.push_back(RT);
TexPtr TextureObj = std::make_shared<Tex>(RT);
return TextureObj;
}
}
check(IsInGameThread());
TexDescriptor TextureDesc(Desc);
TexPtr TextureObj = std::make_shared<Tex>(TextureDesc);
TextureObj->InitRT();
check(TextureObj->GetRenderTarget());
if (TextureGraphEngine::GetBlobber()->IsCacheEnabled())
RTUsed.push_back(TextureObj->GetRenderTarget());
DeviceNativeTask_Lambda_Func allocateFunction = [this, TextureObj]() { return AllocateRTResource(TextureObj); };
auto Task = DeviceNativeTask_Lambda::Create(this, (int32)E_Priority::kSystem, TEXT("AllocateRenderTarget"), allocateFunction);
return TextureObj;
}
TexPtr Device_FX::AllocateRenderTargetArray(const BufferDescriptor& Desc, int32 NumTilesX, int32 NumTilesY)
{
check(IsInGameThread());
SCOPE_CYCLE_COUNTER(STAT_Device_FX_AllocateRendertarget)
RTList* List = nullptr;
HashType Hash = Desc.FormatHashValue();
{
FScopeLock lock(&GC_RTCache);
auto Iter = RTArrayCache.find(Hash);
if (Iter == RTArrayCache.end())
{
List = new RTList();
RTArrayCache[Hash] = List;
}
else
List = Iter->second;
}
check(IsInGameThread());
TexDescriptor TextureDesc(Desc);
TexArrayPtr TexArrayObj = std::make_shared<TexArray>(TextureDesc, NumTilesX, NumTilesY);
/// check if rendering target resources have been created correctly
/// This has to be done in GameThread since UE5
UTextureRenderTarget2DArray* RTArray = TexArrayObj->GetRenderTargetArray();
RTArray->SetResource(RTArray->CreateResource());
/// The rest of it can stay in RenderingThread
DeviceNativeTask_Lambda_Func AllocateFunction = [this, TexArrayObj]() { return AllocateRTArrayResource(TexArrayObj); };
auto Task = DeviceNativeTask_Lambda::Create(this, (int32)E_Priority::kSystem, TEXT("AllocateRenderTargetArray"), AllocateFunction);
List->push_back((UTextureRenderTarget2D*)TexArrayObj->GetRenderTargetArray());
return TexArrayObj;
}
int32 Device_FX::InitRTResource(TexPtr TextureObj, UTextureRenderTarget* RT)
{
check(IsInRenderingThread());
check(RT->GetResource());
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RT->GetResource()->InitResource(RHICmdList);
FDeferredUpdateResource::UpdateResources(RHICmdList);
FTextureRenderTarget2DResource* RTRes = (FTextureRenderTarget2DResource*)RT->GetRenderTargetResource();
check(RTRes);
UE_LOG(LogDevice, VeryVerbose, TEXT("New RT Array allocation: %s %llu [Ptr: 0x%x, Size: %dx%d]"), *TextureObj->GetDescriptor().Name,
TextureObj->GetDescriptor().Format_HashValue(), RT, RTRes->GetSizeX(), RTRes->GetSizeY());
FTextureRHIRef RHITexture = RTRes->GetTextureRHI();
check(RHITexture);
RHICmdList.BindDebugLabelName(RHITexture, *TextureObj->GetDescriptor().Name);
return 0;
}
int32 Device_FX::AllocateRTResource(TexPtr TextureObj)
{
return InitRTResource(TextureObj, TextureObj->GetRenderTarget());
}
int32 Device_FX::AllocateRTArrayResource(TexArrayPtr TexArrayObj)
{
return InitRTResource(std::static_pointer_cast<Tex>(TexArrayObj), TexArrayObj->GetRenderTargetArray());
}
void Device_FX::MarkForCollection(TexPtr TextureObj)
{
FScopeLock lock(&GCLock);
#if 0
if (TextureObj->IsRenderTarget())
{
UTextureRenderTarget2D* RT = TextureObj->RenderTarget();
/// Add to RT Display List
HashType Hash = TextureObj->Descriptor().ToBufferDescriptor().Format_HashValue();
FreeRenderTarget(Hash, RT);
TextureObj->ReleaseRT();
}
#endif
UE_LOG(LogDevice, VeryVerbose, TEXT("[Device_FX] Marking Tex for collection: %s"), *TextureObj->GetDescriptor().Name);
GCTargetTextures.push_back(TextureObj);
}
DeviceBufferRef Device_FX::CreateFromTex(TexPtr TextureObj, bool bInitRaw)
{
BufferDescriptor Desc = TextureObj->GetDescriptor().ToBufferDescriptor();
HashType Hash = (HashType)TextureObj->GetTexture(); ///Raw->Hash();
/// TODO: Put this in the mem device
//DeviceBufferRef memBuffer = Device_Mem::Get()->Create(Raw);
DeviceBuffer_FX* FXBuffer;
if (bInitRaw)
{
RawBufferPtr Raw = TextureObj->Raw();
FXBuffer = new DeviceBuffer_FX(this, TextureObj, Raw);
}
else
FXBuffer = new DeviceBuffer_FX(this, TextureObj, Desc, nullptr);
return AddNewRef_Internal(FXBuffer);
}
DeviceBufferRef Device_FX::CreateFromTexAndRaw(TexPtr TextureObj, RawBufferPtr Raw)
{
CHashPtr Hash = Raw->Hash();
/// We need to lock this entire scope for thread safety reasons, so we just
/// use this mutex as a way to achieve that
FScopeLock lock(&GCLock);
/// Find whether we already have an object like this
DeviceBufferRef RefBuffer = Find(*Hash, true);
if (RefBuffer && !RefBuffer.IsValid())
{
/// We already have something
GCTargetTextures.push_back(TextureObj);
UE_LOG(LogDevice, VeryVerbose, TEXT("[Device_FX] HASH ALREADY FOUND TextureObj: %s Hash:%llu Previously:%s"), *TextureObj->GetDescriptor().Name, Hash->Value(), *RefBuffer->Descriptor().Name);
return RefBuffer;
}
/// Otherwise we create a new one
/// TODO: Put this in the mem device
//DeviceBufferRef memBuffer = Device_Mem::Get()->Create(Raw);
UE_LOG(LogDevice, VeryVerbose, TEXT("[Device_FX] TextureObj: %s Hash:%llu"), *TextureObj->GetDescriptor().Name, Hash->Value());
/// Now that we have the in-memory Buffer. We can use re-use the render target
/// for our device buffer
DeviceBuffer_FX* FXBuffer = new DeviceBuffer_FX(this, TextureObj, Raw);
return AddNewRef_Internal(FXBuffer);
}
DeviceBufferRef Device_FX::CreateFromTexture(UTexture2D* texture, const BufferDescriptor& Desc)
{
check(IsInRenderingThread());
/// Get the Raw data from render target
RawBufferPtr Raw = TextureHelper::RawFromTexture(texture, Desc);
return CreateFromTexAndRaw(std::make_shared<Tex>(texture), Raw);
}
DeviceBufferRef Device_FX::CreateFromRT(UTextureRenderTarget2D* RT, const BufferDescriptor& Desc)
{
check(IsInRenderingThread());
/// Get the Raw data from render target
RawBufferPtr Raw = TextureHelper::RawFromRT(RT, Desc);
return CreateFromTexAndRaw(std::make_shared<Tex>(RT), Raw);
}
void Device_FX::ClearCache()
{
Device::ClearCache();
GCTextures();
}
void Device_FX::GCTextures()
{
check(IsInGameThread());
TextureNodeList Tmp;
{
FScopeLock lock(&GCLock);
Tmp = GCTargetTextures;
GCTargetTextures.clear();
}
for (TexPtr t : Tmp)
check(t.use_count());
Tmp.clear();
}
void Device_FX::AddNativeTask(DeviceNativeTaskPtr Task)
{
/// All Device_FX tasks must run in rendering thread, otherwise these
/// need to be Device_Mem tasks
//check(!Task->IsAsync());
Device::AddNativeTask(Task);
}
void Device_FX::Update(float Delta)
{
check(IsInGameThread());
GCTextures();
Device::Update(Delta);
}
void Device_FX::PrintStats()
{
size_t RTMemUnused = 0;
size_t RTCountUnused = 0;
for (auto Iter = RTCache.begin(); Iter != RTCache.end(); Iter++)
{
RTList* RenderTargets = Iter->second;
if (RenderTargets != nullptr)
{
for (UTextureRenderTarget2D* RT : *RenderTargets)
{
auto RTFormat = RT->RenderTargetFormat;
auto PixelFormat = TextureHelper::GetPixelFormatFromRenderTargetFormat(RTFormat);
size_t Size = RT->SizeX * RT->SizeY * TextureHelper::GetBppFromPixelFormat(PixelFormat) / 8;
RTMemUnused += Size;
RTCountUnused++;
}
}
}
size_t RTMemUsed = 0;
for (auto RT : RTUsed)
{
auto RTFormat = RT->RenderTargetFormat;
auto PixelFormat = TextureHelper::GetPixelFormatFromRenderTargetFormat(RTFormat);
size_t Size = RT->SizeX * RT->SizeY * TextureHelper::GetBppFromPixelFormat(PixelFormat) / 8;
RTMemUsed += Size;
}
UE_LOG(LogDevice, Log, TEXT("===== BEGIN Device: FX STATS (Native) ====="));
UE_LOG(LogDevice, Log, TEXT("[USED] RT Count : %llu"), RTUsed.size());
UE_LOG(LogDevice, Log, TEXT("[USED] RT Mem : %0.2f MB"), (float)RTMemUsed / (1024.0f * 1024.0f));
UE_LOG(LogDevice, Log, TEXT("[Unused] RT Count : %llu"), RTCountUnused);
UE_LOG(LogDevice, Log, TEXT("[Unused] RT Mem : %0.2f MB"), (float)RTMemUnused / (1024.0f * 1024.0f));
UE_LOG(LogDevice, Log, TEXT("===== END Device : FX STATS (Native) ====="));
Device::PrintStats();
}
//////////////////////////////////////////////////////////////////////////
Device_FX* Device_FX::Get()
{
if (TextureGraphEngine::IsDestroying())
throw std::runtime_error("Engine is shutting down!");
Device* Dev = TextureGraphEngine::GetDeviceManager()->GetDevice(DeviceType::FX);
check(Dev);
return static_cast<Device_FX*>(Dev);
}
void Device_FX::InitTiles_Texture(T_Tiles<BlobPtr>* Tiles, const BufferDescriptor& InTileDesc, bool bInitRaw)
{
size_t NumRows = Tiles->Rows();
size_t NumCols = Tiles->Cols();
BufferDescriptor TileDesc = InTileDesc;
TileDesc.Format = TextureHelper::FindOptimalSupportedFormat(InTileDesc.Format);
for (int32 RowId = 0; RowId < NumRows; RowId++)
{
for (int32 ColId = 0; ColId < NumCols; ColId++)
{
TileDesc.Name = TextureHelper::CreateTileName(InTileDesc.Name, RowId, ColId);
TexDescriptor TextureTileDesc(TileDesc);
TexPtr TileTex = std::make_shared<Tex>(TextureTileDesc);
TileTex->InitTexture(nullptr, 0);
/// prepare over here
//DeviceBuffer_FX* FXBuffer = new DeviceBuffer_FX(Device_FX::Get(), TileTex, TileDesc.ToBufferDescriptor(), (CHashPtr)nullptr);
//DeviceBuffer_FXPtr FXBuffer = std::make_shared<DeviceBuffer_FX>;
BlobPtr TileBlob = std::make_shared<Blob>(Device_FX::Get()->CreateFromTex(TileTex, bInitRaw));
(*Tiles)[RowId][ColId] = TileBlob;
}
}
}
void Device_FX::InitTiles_RenderTargets(BlobUPtr* Tiles, size_t NumRows, size_t NumCols, const BufferDescriptor& InTileDesc, bool bInitRaw)
{
for (int32 RowId = 0; RowId < NumRows; RowId++)
{
for (int32 ColId = 0; ColId < NumCols; ColId++)
{
TexDescriptor TileDesc(InTileDesc);
TileDesc.Name = TextureHelper::CreateTileName(InTileDesc.Name, RowId, ColId);
TexPtr TileTex = std::make_shared<Tex>(TileDesc);
TileTex->InitRT();
/// prepare over here
//DeviceBuffer_FXPtr FXBuffer = std::make_shared<DeviceBuffer_FX>(Device_FX::Get(), TileTex, TileDesc.ToBufferDescriptor(), (CHashPtr)nullptr);
BlobUPtr TileBlob = std::make_unique<Blob>(Device_FX::Get()->CreateFromTex(TileTex, bInitRaw));
Tiles[RowId * NumCols + ColId] = std::move(TileBlob);
}
}
}