// Copyright Epic Games, Inc. All Rights Reserved. #include "RenderTargetPool.h" #include "RHIStaticStates.h" #include "Misc/OutputDeviceRedirector.h" #include "Hash/CityHash.h" /** The global render targets pool. */ TGlobalResource GRenderTargetPool; DEFINE_LOG_CATEGORY_STATIC(LogRenderTargetPool, Warning, All); CSV_DEFINE_CATEGORY(RenderTargetPool, !UE_SERVER); TRefCountPtr CreateRenderTarget(FRHITexture* Texture, const TCHAR* Name) { check(Texture); const FIntVector Size = Texture->GetSizeXYZ(); FPooledRenderTargetDesc Desc; Desc.Extent = FIntPoint(Size.X, Size.Y); Desc.ClearValue = Texture->GetClearBinding(); Desc.Format = Texture->GetFormat(); Desc.NumMips = Texture->GetNumMips(); Desc.NumSamples = Texture->GetNumSamples(); Desc.Flags = Desc.TargetableFlags = Texture->GetFlags(); Desc.bForceSharedTargetAndShaderResource = true; Desc.AutoWritable = false; Desc.DebugName = Name; if (FRHITextureCube* TextureCube = Texture->GetTextureCube()) { Desc.bIsCubemap = true; } else if (FRHITexture3D* Texture3D = Texture->GetTexture3D()) { Desc.Depth = Size.Z; } else if (FRHITexture2DArray* TextureArray = Texture->GetTexture2DArray()) { Desc.bIsArray = true; Desc.ArraySize = Size.Z; } FSceneRenderTargetItem Item; Item.TargetableTexture = Texture; Item.ShaderResourceTexture = Texture; TRefCountPtr PooledRenderTarget; GRenderTargetPool.CreateUntrackedElement(Desc, PooledRenderTarget, Item); return MoveTemp(PooledRenderTarget); } bool CacheRenderTarget(FRHITexture* Texture, const TCHAR* Name, TRefCountPtr& OutPooledRenderTarget) { if (!OutPooledRenderTarget || OutPooledRenderTarget->GetShaderResourceRHI() != Texture) { OutPooledRenderTarget = CreateRenderTarget(Texture, Name); return true; } return false; } static uint64 GetTypeHash(FClearValueBinding Binding) { uint64 Hash = 0; switch (Binding.ColorBinding) { case EClearBinding::EColorBound: Hash = CityHash64((const char*)Binding.Value.Color, sizeof(Binding.Value.Color)); break; case EClearBinding::EDepthStencilBound: Hash = uint64(GetTypeHash(Binding.Value.DSValue.Depth)) << 32 | uint64(Binding.Value.DSValue.Stencil); break; } return Hash ^ uint64(Binding.ColorBinding); } static uint64 GetTypeHash(FPooledRenderTargetDesc Desc) { constexpr uint32 HashOffset = STRUCT_OFFSET(FPooledRenderTargetDesc, Flags); constexpr uint32 HashSize = STRUCT_OFFSET(FPooledRenderTargetDesc, PackedBits) + sizeof(FPooledRenderTargetDesc::PackedBits) - HashOffset; static_assert( HashSize == sizeof(FPooledRenderTargetDesc::Flags) + sizeof(FPooledRenderTargetDesc::TargetableFlags) + sizeof(FPooledRenderTargetDesc::Format) + sizeof(FPooledRenderTargetDesc::UAVFormat) + sizeof(FPooledRenderTargetDesc::Extent) + sizeof(FPooledRenderTargetDesc::Depth) + sizeof(FPooledRenderTargetDesc::ArraySize) + sizeof(FPooledRenderTargetDesc::NumMips) + sizeof(FPooledRenderTargetDesc::NumSamples) + sizeof(FPooledRenderTargetDesc::PackedBits), "FPooledRenderTarget has padding that will break the hash."); Desc.Flags &= (~TexCreate_FastVRAM); return CityHash64WithSeed((const char*)&Desc.Flags, HashSize, GetTypeHash(Desc.ClearValue)); } RENDERCORE_API void DumpRenderTargetPoolMemory(FOutputDevice& OutputDevice) { GRenderTargetPool.DumpMemoryUsage(OutputDevice); } static FAutoConsoleCommandWithOutputDevice GDumpRenderTargetPoolMemoryCmd( TEXT("r.DumpRenderTargetPoolMemory"), TEXT("Dump allocation information for the render target pool."), FConsoleCommandWithOutputDeviceDelegate::CreateStatic(DumpRenderTargetPoolMemory) ); static uint32 ComputeSizeInKB(FPooledRenderTarget& Element) { return (Element.ComputeMemorySize() + 1023) / 1024; } TRefCountPtr FRenderTargetPool::FindFreeElementForRDG( FRHICommandList& RHICmdList, const FRDGTextureDesc& Desc, const TCHAR* Name) { return FindFreeElementInternal(RHICmdList, Translate(Desc), Name); } TRefCountPtr FRenderTargetPool::FindFreeElementInternal( FRHICommandList& RHICmdList, const FPooledRenderTargetDesc& Desc, const TCHAR* InDebugName) { FPooledRenderTarget* Found = 0; uint32 FoundIndex = -1; const uint64 DescHash = GetTypeHash(Desc); for (uint32 Index = 0, Num = (uint32)PooledRenderTargets.Num(); Index < Num; ++Index) { if (PooledRenderTargetHashes[Index] == DescHash) { FPooledRenderTarget* Element = PooledRenderTargets[Index]; checkf(Element, TEXT("Hash was not cleared from the list.")); checkf(Element->GetDesc().Compare(Desc, false), TEXT("Invalid hash or collision when attempting to allocate %s"), Element->GetDesc().DebugName); if (!Element->IsFree()) { continue; } const FPooledRenderTargetDesc& ElementDesc = Element->GetDesc(); if (ElementDesc.Flags != Desc.Flags) { continue; } Found = Element; FoundIndex = Index; break; } } if (!Found) { TRACE_CPUPROFILER_EVENT_SCOPE(FRenderTargetPool::CreateTexture); UE_LOG(LogRenderTargetPool, Display, TEXT("%d MB, NewRT %s %s"), (AllocationLevelInKB + 1023) / 1024, *Desc.GenerateInfoString(), InDebugName); // not found in the pool, create a new element Found = new FPooledRenderTarget(Desc, this); PooledRenderTargets.Add(Found); PooledRenderTargetHashes.Add(DescHash); // TexCreate_UAV should be used on Desc.TargetableFlags check(!EnumHasAnyFlags(Desc.Flags, TexCreate_UAV)); FRHIResourceCreateInfo CreateInfo(InDebugName, Desc.ClearValue); if (EnumHasAnyFlags(Desc.TargetableFlags, TexCreate_RenderTargetable | TexCreate_DepthStencilTargetable | TexCreate_UAV)) { // Only create resources if we're not asked to defer creation. if (Desc.Is2DTexture()) { if (!Desc.IsArray()) { RHICreateTargetableShaderResource2D( Desc.Extent.X, Desc.Extent.Y, (uint8)Desc.Format, Desc.NumMips, Desc.Flags, Desc.TargetableFlags, Desc.bForceSeparateTargetAndShaderResource, Desc.bForceSharedTargetAndShaderResource, CreateInfo, (FTexture2DRHIRef&)Found->RenderTargetItem.TargetableTexture, (FTexture2DRHIRef&)Found->RenderTargetItem.ShaderResourceTexture, Desc.NumSamples ); } else { RHICreateTargetableShaderResource2DArray( Desc.Extent.X, Desc.Extent.Y, Desc.ArraySize, (uint8)Desc.Format, Desc.NumMips, Desc.Flags, Desc.TargetableFlags, Desc.bForceSeparateTargetAndShaderResource, Desc.bForceSharedTargetAndShaderResource, CreateInfo, (FTexture2DArrayRHIRef&)Found->RenderTargetItem.TargetableTexture, (FTexture2DArrayRHIRef&)Found->RenderTargetItem.ShaderResourceTexture, Desc.NumSamples ); } } else if (Desc.Is3DTexture()) { Found->RenderTargetItem.ShaderResourceTexture = RHICreateTexture3D( Desc.Extent.X, Desc.Extent.Y, Desc.Depth, (uint8)Desc.Format, Desc.NumMips, Desc.Flags | Desc.TargetableFlags, CreateInfo); // similar to RHICreateTargetableShaderResource2D Found->RenderTargetItem.TargetableTexture = Found->RenderTargetItem.ShaderResourceTexture; } else { check(Desc.IsCubemap()); if (Desc.IsArray()) { RHICreateTargetableShaderResourceCubeArray( Desc.Extent.X, Desc.ArraySize, (uint8)Desc.Format, Desc.NumMips, Desc.Flags, Desc.TargetableFlags, false, CreateInfo, (FTextureCubeRHIRef&)Found->RenderTargetItem.TargetableTexture, (FTextureCubeRHIRef&)Found->RenderTargetItem.ShaderResourceTexture ); } else { RHICreateTargetableShaderResourceCube( Desc.Extent.X, (uint8)Desc.Format, Desc.NumMips, Desc.Flags, Desc.TargetableFlags, false, CreateInfo, (FTextureCubeRHIRef&)Found->RenderTargetItem.TargetableTexture, (FTextureCubeRHIRef&)Found->RenderTargetItem.ShaderResourceTexture ); } } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) RHIBindDebugLabelName(Found->RenderTargetItem.TargetableTexture, InDebugName); #endif } else { if (Desc.Is2DTexture()) { // this is useful to get a CPU lockable texture through the same interface if (!Desc.IsArray()) { Found->RenderTargetItem.ShaderResourceTexture = RHICreateTexture2D( Desc.Extent.X, Desc.Extent.Y, (uint8)Desc.Format, Desc.NumMips, Desc.NumSamples, Desc.Flags, CreateInfo); } else { Found->RenderTargetItem.ShaderResourceTexture = RHICreateTexture2DArray( Desc.Extent.X, Desc.Extent.Y, Desc.ArraySize, (uint8)Desc.Format, Desc.NumMips, Desc.NumSamples, Desc.Flags, CreateInfo); } } else if (Desc.Is3DTexture()) { Found->RenderTargetItem.ShaderResourceTexture = RHICreateTexture3D( Desc.Extent.X, Desc.Extent.Y, Desc.Depth, (uint8)Desc.Format, Desc.NumMips, Desc.Flags, CreateInfo); } else { check(Desc.IsCubemap()); if (Desc.IsArray()) { FTextureCubeRHIRef CubeTexture = RHICreateTextureCubeArray(Desc.Extent.X, Desc.ArraySize, (uint8)Desc.Format, Desc.NumMips, Desc.Flags | Desc.TargetableFlags | TexCreate_ShaderResource, CreateInfo); Found->RenderTargetItem.TargetableTexture = Found->RenderTargetItem.ShaderResourceTexture = CubeTexture; } else { FTextureCubeRHIRef CubeTexture = RHICreateTextureCube(Desc.Extent.X, (uint8)Desc.Format, Desc.NumMips, Desc.Flags | Desc.TargetableFlags | TexCreate_ShaderResource, CreateInfo); Found->RenderTargetItem.TargetableTexture = Found->RenderTargetItem.ShaderResourceTexture = CubeTexture; } } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) RHIBindDebugLabelName(Found->RenderTargetItem.ShaderResourceTexture, InDebugName); #endif } if (EnumHasAnyFlags(Desc.TargetableFlags, TexCreate_UAV)) { // The render target desc is invalid if a UAV is requested with an RHI that doesn't support the high-end feature level. check(GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5 || GMaxRHIFeatureLevel == ERHIFeatureLevel::ES3_1); if (GRHISupportsUAVFormatAliasing) { EPixelFormat AliasFormat = Desc.UAVFormat != PF_Unknown ? Desc.UAVFormat : Desc.Format; Found->RenderTargetItem.UAV = RHICreateUnorderedAccessView(Found->RenderTargetItem.TargetableTexture, 0, (uint8)AliasFormat, 0, 0); } else { checkf(Desc.UAVFormat == PF_Unknown || Desc.UAVFormat == Desc.Format, TEXT("UAV aliasing is not supported by the current RHI.")); Found->RenderTargetItem.UAV = RHICreateUnorderedAccessView(Found->RenderTargetItem.TargetableTexture, 0); } } AllocationLevelInKB += ComputeSizeInKB(*Found); FoundIndex = PooledRenderTargets.Num() - 1; Found->Desc.DebugName = InDebugName; } Found->Desc.DebugName = InDebugName; Found->UnusedForNFrames = 0; // assign to the reference counted variable TRefCountPtr Result = Found; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (Found->GetRenderTargetItem().TargetableTexture) { RHIBindDebugLabelName(Found->GetRenderTargetItem().TargetableTexture, InDebugName); } #endif return MoveTemp(Result); } bool FRenderTargetPool::FindFreeElement( FRHICommandList& RHICmdList, const FPooledRenderTargetDesc& Desc, TRefCountPtr& Out, const TCHAR* InDebugName) { check(IsInRenderingThread()); if (!Desc.IsValid()) { // no need to do anything return true; } // Querying a render target that have no mip levels makes no sens. check(Desc.NumMips > 0); // Make sure if requesting a depth format that the clear value is correct ensure(!(Desc.Flags & TexCreate_DepthStencilTargetable) || (Desc.ClearValue.ColorBinding == EClearBinding::ENoneBound || Desc.ClearValue.ColorBinding == EClearBinding::EDepthStencilBound)); // TexCreate_FastVRAM should be used on Desc.Flags ensure(!EnumHasAnyFlags(Desc.TargetableFlags, TexCreate_FastVRAM)); // if we can keep the current one, do that if (Out) { FPooledRenderTarget* Current = (FPooledRenderTarget*)Out.GetReference(); const bool bExactMatch = true; if (Out->GetDesc().Compare(Desc, bExactMatch)) { // we can reuse the same, but the debug name might have changed Current->Desc.DebugName = InDebugName; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (Current->GetRenderTargetItem().TargetableTexture) { RHIBindDebugLabelName(Current->GetRenderTargetItem().TargetableTexture, InDebugName); } #endif check(!Out->IsFree()); return true; } else { // release old reference, it might free a RT we can use Out = 0; if (Current->IsFree()) { AllocationLevelInKB -= ComputeSizeInKB(*Current); int32 Index = FindIndex(Current); check(Index >= 0); FreeElementAtIndex(Index); } } } Out = FindFreeElementInternal(RHICmdList, Desc, InDebugName); return false; } void FRenderTargetPool::CreateUntrackedElement(const FPooledRenderTargetDesc& Desc, TRefCountPtr& Out, const FSceneRenderTargetItem& Item) { check(IsInRenderingThread()); Out = 0; // not found in the pool, create a new element FPooledRenderTarget* Found = new FPooledRenderTarget(Desc, NULL); Found->RenderTargetItem = Item; // assign to the reference counted variable Out = Found; } void FRenderTargetPool::GetStats(uint32& OutWholeCount, uint32& OutWholePoolInKB, uint32& OutUsedInKB) const { OutWholeCount = (uint32)PooledRenderTargets.Num(); OutUsedInKB = 0; OutWholePoolInKB = 0; for (uint32 i = 0; i < (uint32)PooledRenderTargets.Num(); ++i) { FPooledRenderTarget* Element = PooledRenderTargets[i]; if (Element) { uint32 SizeInKB = ComputeSizeInKB(*Element); OutWholePoolInKB += SizeInKB; if (!Element->IsFree()) { OutUsedInKB += SizeInKB; } } } // if this triggers uncomment the code in VerifyAllocationLevel() and debug the issue, we might leak memory or not release when we could ensure(AllocationLevelInKB == OutWholePoolInKB); } void FRenderTargetPool::TickPoolElements() { uint32 DeferredAllocationLevelInKB = 0; for (FPooledRenderTarget* Element : DeferredDeleteArray) { DeferredAllocationLevelInKB += ComputeSizeInKB(*Element); } check(IsInRenderingThread()); DeferredDeleteArray.Reset(); uint32 MinimumPoolSizeInKB; { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RenderTargetPoolMin")); MinimumPoolSizeInKB = FMath::Clamp(CVar->GetValueOnRenderThread(), 0, 2000) * 1024; } CompactPool(); uint32 UnusedAllocationLevelInKB = 0; for (uint32 i = 0; i < (uint32)PooledRenderTargets.Num(); ++i) { FPooledRenderTarget* Element = PooledRenderTargets[i]; if (Element) { Element->OnFrameStart(); if (Element->UnusedForNFrames > 2) { UnusedAllocationLevelInKB += ComputeSizeInKB(*Element); } } } uint32 TotalFrameUsageInKb = AllocationLevelInKB + DeferredAllocationLevelInKB ; CSV_CUSTOM_STAT(RenderTargetPool, UnusedMB, UnusedAllocationLevelInKB / 1024.0f, ECsvCustomStatOp::Set); CSV_CUSTOM_STAT(RenderTargetPool, PeakUsedMB, (TotalFrameUsageInKb - UnusedAllocationLevelInKB) / 1024.f, ECsvCustomStatOp::Set); // we need to release something, take the oldest ones first while (AllocationLevelInKB > MinimumPoolSizeInKB) { // -1: not set int32 OldestElementIndex = -1; // find oldest element we can remove for (uint32 i = 0, Num = (uint32)PooledRenderTargets.Num(); i < Num; ++i) { FPooledRenderTarget* Element = PooledRenderTargets[i]; if (Element && Element->UnusedForNFrames > 2) { if (OldestElementIndex != -1) { if (PooledRenderTargets[OldestElementIndex]->UnusedForNFrames < Element->UnusedForNFrames) { OldestElementIndex = i; } } else { OldestElementIndex = i; } } } if (OldestElementIndex != -1) { AllocationLevelInKB -= ComputeSizeInKB(*PooledRenderTargets[OldestElementIndex]); // we assume because of reference counting the resource gets released when not needed any more // we don't use Remove() to not shuffle around the elements for better transparency on RenderTargetPoolEvents FreeElementAtIndex(OldestElementIndex); } else { // There is no element we can remove but we are over budget, better we log that. // Options: // * Increase the pool // * Reduce rendering features or resolution // * Investigate allocations, order or reusing other render targets can help // * Ignore (editor case, might start using slow memory which can be ok) if (!bCurrentlyOverBudget) { UE_CLOG(IsRunningClientOnly() && MinimumPoolSizeInKB != 0, LogRenderTargetPool, Warning, TEXT("r.RenderTargetPoolMin exceeded %d/%d MB (ok in editor, bad on fixed memory platform)"), (AllocationLevelInKB + 1023) / 1024, MinimumPoolSizeInKB / 1024); bCurrentlyOverBudget = true; } // at this point we need to give up break; } } if (AllocationLevelInKB <= MinimumPoolSizeInKB) { if (bCurrentlyOverBudget) { UE_CLOG(MinimumPoolSizeInKB != 0, LogRenderTargetPool, Display, TEXT("r.RenderTargetPoolMin resolved %d/%d MB"), (AllocationLevelInKB + 1023) / 1024, MinimumPoolSizeInKB / 1024); bCurrentlyOverBudget = false; } } #if STATS uint32 Count, SizeKB, UsedKB; GetStats(Count, SizeKB, UsedKB); CSV_CUSTOM_STAT_GLOBAL(RenderTargetPoolSize, float(SizeKB) / 1024.0f, ECsvCustomStatOp::Set); CSV_CUSTOM_STAT_GLOBAL(RenderTargetPoolUsed, float(UsedKB) / 1024.0f, ECsvCustomStatOp::Set); CSV_CUSTOM_STAT_GLOBAL(RenderTargetPoolCount, int32(Count), ECsvCustomStatOp::Set); SET_MEMORY_STAT(STAT_RenderTargetPoolSize, int64(SizeKB) * 1024ll); SET_MEMORY_STAT(STAT_RenderTargetPoolUsed, int64(UsedKB) * 1024ll); SET_DWORD_STAT(STAT_RenderTargetPoolCount, Count); #endif // STATS } int32 FRenderTargetPool::FindIndex(IPooledRenderTarget* In) const { check(IsInRenderingThread()); if (In) { for (uint32 i = 0, Num = (uint32)PooledRenderTargets.Num(); i < Num; ++i) { const FPooledRenderTarget* Element = PooledRenderTargets[i]; if (Element == In) { return i; } } } // not found return -1; } void FRenderTargetPool::FreeElementAtIndex(int32 Index) { // we don't use Remove() to not shuffle around the elements for better transparency on RenderTargetPoolEvents PooledRenderTargets[Index] = 0; PooledRenderTargetHashes[Index] = 0; } void FRenderTargetPool::FreeUnusedResource(TRefCountPtr& In) { check(IsInRenderingThread()); int32 Index = FindIndex(In); if (Index != -1) { FPooledRenderTarget* Element = PooledRenderTargets[Index]; // Ref count will always be at least 2 ensure(Element->GetRefCount() >= 2); In = nullptr; if (Element->IsFree()) { AllocationLevelInKB -= ComputeSizeInKB(*Element); // we assume because of reference counting the resource gets released when not needed any more DeferredDeleteArray.Add(PooledRenderTargets[Index]); FreeElementAtIndex(Index); } } } void FRenderTargetPool::FreeUnusedResources() { check(IsInRenderingThread()); for (uint32 i = 0, Num = (uint32)PooledRenderTargets.Num(); i < Num; ++i) { FPooledRenderTarget* Element = PooledRenderTargets[i]; if (Element && Element->IsFree()) { AllocationLevelInKB -= ComputeSizeInKB(*Element); // we assume because of reference counting the resource gets released when not needed any more // we don't use Remove() to not shuffle around the elements for better transparency on RenderTargetPoolEvents DeferredDeleteArray.Add(PooledRenderTargets[i]); FreeElementAtIndex(i); } } } void FRenderTargetPool::DumpMemoryUsage(FOutputDevice& OutputDevice) { uint32 UnusedAllocationInKB = 0; OutputDevice.Logf(TEXT("Pooled Render Targets:")); for (int32 i = 0; i < PooledRenderTargets.Num(); ++i) { FPooledRenderTarget* Element = PooledRenderTargets[i]; if (Element) { uint32 ElementAllocationInKB = ComputeSizeInKB(*Element); if (Element->UnusedForNFrames > 2) { UnusedAllocationInKB += ElementAllocationInKB; } OutputDevice.Logf( TEXT(" %6.3fMB %4dx%4d%s%s %2dmip(s) %s (%s) Unused frames: %d"), ElementAllocationInKB / 1024.0f, Element->Desc.Extent.X, Element->Desc.Extent.Y, Element->Desc.Depth > 1 ? *FString::Printf(TEXT("x%3d"), Element->Desc.Depth) : (Element->Desc.IsCubemap() ? TEXT("cube") : TEXT(" ")), Element->Desc.bIsArray ? *FString::Printf(TEXT("[%3d]"), Element->Desc.ArraySize) : TEXT(" "), Element->Desc.NumMips, Element->Desc.DebugName, GPixelFormats[Element->Desc.Format].Name, Element->UnusedForNFrames ); } } uint32 NumTargets = 0; uint32 UsedKB = 0; uint32 PoolKB = 0; GetStats(NumTargets, PoolKB, UsedKB); OutputDevice.Logf(TEXT("%.3fMB total, %.3fMB used, %.3fMB unused, %d render targets"), PoolKB / 1024.f, UsedKB / 1024.f, UnusedAllocationInKB / 1024.f, NumTargets); uint32 DeferredTotal = 0; OutputDevice.Logf(TEXT("Deferred Render Targets:")); for (int32 i = 0; i < DeferredDeleteArray.Num(); ++i) { FPooledRenderTarget* Element = DeferredDeleteArray[i]; if (Element) { OutputDevice.Logf( TEXT(" %6.3fMB %4dx%4d%s%s %2dmip(s) %s (%s)"), ComputeSizeInKB(*Element) / 1024.0f, Element->Desc.Extent.X, Element->Desc.Extent.Y, Element->Desc.Depth > 1 ? *FString::Printf(TEXT("x%3d"), Element->Desc.Depth) : (Element->Desc.IsCubemap() ? TEXT("cube") : TEXT(" ")), Element->Desc.bIsArray ? *FString::Printf(TEXT("[%3d]"), Element->Desc.ArraySize) : TEXT(" "), Element->Desc.NumMips, Element->Desc.DebugName, GPixelFormats[Element->Desc.Format].Name ); uint32 SizeInKB = ComputeSizeInKB(*Element); DeferredTotal += SizeInKB; } } OutputDevice.Logf(TEXT("%.3fMB Deferred total"), DeferredTotal / 1024.f); } void FPooledRenderTarget::InitRDG() { check(RenderTargetItem.ShaderResourceTexture); if (RenderTargetItem.TargetableTexture) { TargetableTexture = new FRDGPooledTexture(RenderTargetItem.TargetableTexture, Translate(Desc, ERenderTargetTexture::Targetable)); } if (RenderTargetItem.ShaderResourceTexture != RenderTargetItem.TargetableTexture) { ShaderResourceTexture = new FRDGPooledTexture(RenderTargetItem.ShaderResourceTexture, Translate(Desc, ERenderTargetTexture::ShaderResource)); } else { ShaderResourceTexture = TargetableTexture; } } uint32 FPooledRenderTarget::AddRef() const { return uint32(FPlatformAtomics::InterlockedIncrement(&NumRefs)); } uint32 FPooledRenderTarget::Release() { const int32 Refs = FPlatformAtomics::InterlockedDecrement(&NumRefs); if (Refs == 0) { delete this; } return uint32(Refs); } uint32 FPooledRenderTarget::GetRefCount() const { return uint32(NumRefs); } void FPooledRenderTarget::SetDebugName(const TCHAR* InName) { check(InName); Desc.DebugName = InName; } const FPooledRenderTargetDesc& FPooledRenderTarget::GetDesc() const { return Desc; } void FRenderTargetPool::ReleaseDynamicRHI() { check(IsInRenderingThread()); DeferredDeleteArray.Empty(); PooledRenderTargets.Empty(); } // for debugging purpose FPooledRenderTarget* FRenderTargetPool::GetElementById(uint32 Id) const { // is used in game and render thread if (Id >= (uint32)PooledRenderTargets.Num()) { return 0; } return PooledRenderTargets[Id]; } void FRenderTargetPool::CompactPool() { for (uint32 i = 0, Num = (uint32)PooledRenderTargets.Num(); i < Num; ) { FPooledRenderTarget* Element = PooledRenderTargets[i]; if (!Element) { PooledRenderTargets.RemoveAtSwap(i); PooledRenderTargetHashes.RemoveAtSwap(i); --Num; } else { ++i; } } } bool FPooledRenderTarget::OnFrameStart() { check(IsInRenderingThread()); // If there are any references to the pooled render target other than the pool itself, then it may not be freed. if (!IsFree()) { check(!UnusedForNFrames); return false; } ++UnusedForNFrames; // this logic can be improved if (UnusedForNFrames > 10) { // release return true; } return false; } uint32 FPooledRenderTarget::ComputeMemorySize() const { uint32 Size = 0; if (Desc.Is2DTexture()) { Size += RHIComputeMemorySize(RenderTargetItem.TargetableTexture); if (RenderTargetItem.ShaderResourceTexture != RenderTargetItem.TargetableTexture) { Size += RHIComputeMemorySize(RenderTargetItem.ShaderResourceTexture); } } else if (Desc.Is3DTexture()) { Size += RHIComputeMemorySize(RenderTargetItem.TargetableTexture); if (RenderTargetItem.ShaderResourceTexture != RenderTargetItem.TargetableTexture) { Size += RHIComputeMemorySize(RenderTargetItem.ShaderResourceTexture); } } else { Size += RHIComputeMemorySize(RenderTargetItem.TargetableTexture); if (RenderTargetItem.ShaderResourceTexture != RenderTargetItem.TargetableTexture) { Size += RHIComputeMemorySize(RenderTargetItem.ShaderResourceTexture); } } return Size; } bool FPooledRenderTarget::IsFree() const { uint32 RefCount = GetRefCount(); check(RefCount >= 1); // If the only reference to the pooled render target is from the pool, then it's unused. return RefCount == 1; }