Files
UnrealEngineUWP/Engine/Source/Runtime/RenderCore/Public/RenderTargetPool.h
zach bethel 8c1793ad5a Integration of RHI transient allocator into RDG.
- Replaced legacy transient support from RDG and replaced with new API.
 - Reworked acquire / discard operations a bit and added RHI validation to track correctness.
 - Reworked RDG barrier batching to include acquire / discard operations.
 - Hardened render pass merging logic and expanded to support lifetime extension of transient resources.
 - Added transient tag to RDG insights to track which resources are transient.

#rb luke.thatcher, kenzo.terelst

[CL 15726534 by zach bethel in ue5-main branch]
2021-03-17 12:44:59 -04:00

371 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
RenderTargetPool.h: Scene render target pool manager.
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "RHI.h"
#include "RenderResource.h"
#include "RendererInterface.h"
#include "RenderGraphResources.h"
#define LOG_MAX_RENDER_TARGET_POOL_USAGE 0
/** The reference to a pooled render target, use like this: TRefCountPtr<IPooledRenderTarget> */
struct RENDERCORE_API FPooledRenderTarget final : public IPooledRenderTarget
{
FPooledRenderTarget(const FPooledRenderTargetDesc& InDesc, class FRenderTargetPool* InRenderTargetPool)
: RenderTargetPool(InRenderTargetPool)
, Desc(InDesc)
{}
~FPooledRenderTarget()
{
check(!NumRefs);
RenderTargetItem.SafeRelease();
}
uint32 GetUnusedForNFrames() const
{
return UnusedForNFrames;
}
bool HasRDG() const
{
return TargetableTexture.IsValid() || ShaderResourceTexture.IsValid();
}
FRDGPooledTexture* GetRDG(ERenderTargetTexture Texture)
{
return Texture == ERenderTargetTexture::Targetable ? TargetableTexture : ShaderResourceTexture;
}
const FRDGPooledTexture* GetRDG(ERenderTargetTexture Texture) const
{
return Texture == ERenderTargetTexture::Targetable ? TargetableTexture : ShaderResourceTexture;
}
void InitRDG();
// interface IPooledRenderTarget --------------
virtual uint32 AddRef() const override final;
virtual uint32 Release() override final;
virtual uint32 GetRefCount() const override final;
virtual bool IsFree() const override final;
virtual uint32 HasBeenDiscardedThisFrame() const
{
return GFrameNumberRenderThread == FrameNumberLastDiscard;
}
bool IsTransient() const
{
return !!(Desc.Flags & TexCreate_Transient);
}
bool IsTracked() const override
{
return RenderTargetPool != nullptr;
}
bool IsCompatibleWithRDG() const override
{
return true;
}
virtual void SetDebugName(const TCHAR *InName);
virtual const FPooledRenderTargetDesc& GetDesc() const;
virtual uint32 ComputeMemorySize() const;
FVRamAllocation VRamAllocation;
private:
/** Pointer back to the pool for render targets which are actually pooled, otherwise NULL. */
FRenderTargetPool* RenderTargetPool;
/** All necessary data to create the render target */
FPooledRenderTargetDesc Desc;
/** For pool management (only if NumRef == 0 the element can be reused) */
mutable int32 NumRefs = 0;
/** Allows to defer the release to save performance on some hardware (DirectX) */
uint32 UnusedForNFrames = 0;
/** Keeps track of the last frame we unmapped physical memory for this resource. We can't map again in the same frame if we did that */
uint32 FrameNumberLastDiscard = -1;
/** Pooled textures for use with RDG. */
TRefCountPtr<FRDGPooledTexture> TargetableTexture;
TRefCountPtr<FRDGPooledTexture> ShaderResourceTexture;
/** @return true:release this one, false otherwise */
bool OnFrameStart();
friend class FRDGTexture;
friend class FRDGBuilder;
friend class FRenderTargetPool;
};
enum ERenderTargetPoolEventType
{
ERTPE_Alloc,
ERTPE_Dealloc,
ERTPE_Phase
};
struct RENDERCORE_API FRenderTargetPoolEvent
{
// constructor for ERTPE_Alloc
FRenderTargetPoolEvent(uint32 InPoolEntryId, uint32 InTimeStep, FPooledRenderTarget* InPointer)
: PoolEntryId(InPoolEntryId)
, TimeStep(InTimeStep)
, Pointer(InPointer)
, EventType(ERTPE_Alloc)
, ColumnIndex(-1)
, ColumnX(0)
, ColumnSize(0)
{
Desc = Pointer->GetDesc();
SizeInBytes = Pointer->ComputeMemorySize();
VRamAllocation = Pointer->VRamAllocation;
}
// constructor for ERTPE_Alloc
FRenderTargetPoolEvent(uint32 InPoolEntryId, uint32 InTimeStep)
: PoolEntryId(InPoolEntryId)
, TimeStep(InTimeStep)
, Pointer(0)
, SizeInBytes(0)
, EventType(ERTPE_Dealloc)
, ColumnIndex(-1)
, ColumnX(0)
, ColumnSize(0)
{
}
// constructor for ERTPE_Alloc
// @param PhaseName must not be 0
FRenderTargetPoolEvent(const FString& InPhaseName, uint32 InTimeStep)
: PoolEntryId(-1)
, TimeStep(InTimeStep)
, Pointer(0)
, PhaseName(InPhaseName)
, SizeInBytes(0)
, EventType(ERTPE_Phase)
, ColumnIndex(-1)
, ColumnX(0)
, ColumnSize(0)
{
}
// @return Pointer if the object is still in the pool
IPooledRenderTarget* GetValidatedPointer() const;
ERenderTargetPoolEventType GetEventType() const { return EventType; }
const int32 GetPoolEntryId() const { check(EventType == ERTPE_Alloc || EventType == ERTPE_Dealloc); return PoolEntryId; }
const FString& GetPhaseName() const { check(EventType == ERTPE_Phase); return PhaseName; }
const FPooledRenderTargetDesc& GetDesc() const { check(EventType == ERTPE_Alloc || EventType == ERTPE_Dealloc); return Desc; }
const uint32 GetTimeStep() const { return TimeStep; }
const uint64 GetSizeInBytes() const { check(EventType == ERTPE_Alloc); return SizeInBytes; }
const void SetPoolEntryId(uint32 InPoolEntryId) { PoolEntryId = InPoolEntryId; }
const void SetColumn(uint32 InColumnIndex, uint32 InColumnX, uint32 InColumnSize) { check(EventType == ERTPE_Alloc || EventType == ERTPE_Dealloc); ColumnIndex = InColumnIndex; ColumnX = InColumnX; ColumnSize = InColumnSize; }
const uint32 GetColumnX() const { check(EventType == ERTPE_Alloc || EventType == ERTPE_Dealloc); return ColumnX; }
const uint32 GetColumnSize() const { check(EventType == ERTPE_Alloc || EventType == ERTPE_Dealloc); return ColumnSize; }
bool IsVisible() const { return EventType == ERTPE_Phase || ColumnSize > 0; }
void SetDesc(const FPooledRenderTargetDesc &InDesc) { Desc = InDesc; }
bool NeedsDeallocEvent();
private:
// valid if EventType==ERTPE_Alloc || EventType==ERTPE_Dealloc, -1 if not set, was index into PooledRenderTargets[]
uint32 PoolEntryId;
//
uint32 TimeStep;
// valid EventType==ERTPE_Alloc, 0 if not set
FPooledRenderTarget* Pointer;
//
FVRamAllocation VRamAllocation;
// valid if EventType==ERTPE_Phase TEXT("") if not set
FString PhaseName;
// valid if EventType==ERTPE_Alloc || EventType==ERTPE_Dealloc
FPooledRenderTargetDesc Desc;
// valid if EventType==ERTPE_Alloc 0 if unknown
uint64 SizeInBytes;
// e.g. ERTPE_Alloc
ERenderTargetPoolEventType EventType;
// for display, computed by ComputeView()
// valid if EventType==ERTPE_Alloc || EventType==ERTPE_Dealloc, -1 if not defined yet
uint32 ColumnIndex;
//
uint32 ColumnX;
//
uint32 ColumnSize;
};
enum class ERenderTargetTransience : uint8
{
NonTransient,
Transient,
};
/**
* Encapsulates the render targets pools that allows easy sharing (mostly used on the render thread side)
*/
class RENDERCORE_API FRenderTargetPool : public FRenderResource
{
public:
FRenderTargetPool();
/**
* @param DebugName must not be 0, we only store the pointer
* @param Out is not the return argument to avoid double allocation because of wrong reference counting
* call from RenderThread only
* @return true if the old element was still valid, false if a new one was assigned
*/
bool FindFreeElement(
FRHICommandList& RHICmdList,
const FPooledRenderTargetDesc& Desc,
TRefCountPtr<IPooledRenderTarget>& Out,
const TCHAR* InDebugName,
ERenderTargetTransience TransienceHint = ERenderTargetTransience::Transient);
void CreateUntrackedElement(const FPooledRenderTargetDesc& Desc, TRefCountPtr<IPooledRenderTarget>& Out, const FSceneRenderTargetItem& Item);
/** Only to get statistics on usage and free elements. Normally only called in renderthread or if FlushRenderingCommands was called() */
void GetStats(uint32& OutWholeCount, uint32& OutWholePoolInKB, uint32& OutUsedInKB) const;
/**
* Can release RT, should be called once per frame.
* call from RenderThread only
*/
void TickPoolElements();
/** Free renderer resources */
void ReleaseDynamicRHI();
/** Allows to remove a resource so it cannot be shared and gets released immediately instead a/some frame[s] later. */
void FreeUnusedResource(TRefCountPtr<IPooledRenderTarget>& In);
/** Good to call between levels or before memory intense operations. */
void FreeUnusedResources();
// for debugging purpose, assumes you call FlushRenderingCommands() be
// @return can be 0, that doesn't mean iteration is done
FPooledRenderTarget* GetElementById(uint32 Id) const;
uint32 GetElementCount() const { return PooledRenderTargets.Num(); }
// @return -1 if not found
int32 FindIndex(IPooledRenderTarget* In) const;
void SetObserveTarget(const FString& InObservedDebugName, uint32 InObservedDebugNameReusedGoal = 0xffffffff);
// Logs out usage information.
void DumpMemoryUsage(FOutputDevice& OutputDevice);
// to not have event recording for some time during rendering (e.g. thumbnail rendering)
void SetEventRecordingActive(bool bValue) { bEventRecordingActive = bValue; }
//
void DisableEventDisplay() { RenderTargetPoolEvents.Empty(); bEventRecordingStarted = false; }
//
bool IsEventRecordingEnabled() const;
void AddPhaseEvent(const TCHAR* InPhaseName);
private:
TRefCountPtr<FPooledRenderTarget> FindFreeElementForRDG(FRHICommandList& RHICmdList, const FRDGTextureDesc& Desc, const TCHAR* Name);
TRefCountPtr<FPooledRenderTarget> FindFreeElementInternal(
FRHICommandList& RHICmdList,
const FPooledRenderTargetDesc& InputDesc,
const TCHAR* InDebugName);
static bool DoesTargetNeedTransienceOverride(ETextureCreateFlags Flags, ERenderTargetTransience TransienceHint);
friend void RenderTargetPoolEvents(const TArray<FString>& Args);
void FreeElementAtIndex(int32 Index);
/** Elements can be 0, we compact the buffer later. */
TArray<uint64> PooledRenderTargetHashes;
TArray< TRefCountPtr<FPooledRenderTarget> > PooledRenderTargets;
TArray< TRefCountPtr<FPooledRenderTarget> > DeferredDeleteArray;
// redundant, can always be computed with GetStats(), to debug "out of memory" situations and used for r.RenderTargetPoolMin
uint32 AllocationLevelInKB;
FGraphEventRef TransitionFence;
// to avoid log spam
bool bCurrentlyOverBudget;
// could be done on the fly but that makes the RenderTargetPoolEvents harder to read
void CompactPool();
// the following is used for Event recording --------------------------------
struct SMemoryStats
{
SMemoryStats()
: DisplayedUsageInBytes(0)
, TotalUsageInBytes(0)
{
}
// for statistics
uint64 DisplayedUsageInBytes;
// for statistics
uint64 TotalUsageInBytes;
// for display purposes, to normalize the view width (Initialize to 1 to avoid a division by zero when compiled out)
uint64 TotalColumnSize = 1;
};
// if next frame we want to run with bEventRecording=true
bool bStartEventRecordingNextTick;
// in KB, e.g. 1MB = 1024, 0 to display all
uint32 EventRecordingSizeThreshold;
// true if active, to not have the event recording for some time during rendering (e.g. thumbnail rendering)
bool bEventRecordingActive;
// true meaning someone used r.RenderTargetPool.Events to start it
bool bEventRecordingStarted;
// only used if bEventRecording
TArray<FRenderTargetPoolEvent> RenderTargetPoolEvents;
//
uint32 CurrentEventRecordingTime;
#if LOG_MAX_RENDER_TARGET_POOL_USAGE
uint32 MaxUsedRenderTargetInKB;
#endif
//
void AddDeallocEvents();
// @param In must no be 0
void AddAllocEvent(uint32 InPoolEntryId, FPooledRenderTarget* In);
//
void AddAllocEventsFromCurrentState();
// @return 0 if none was found
const FString* GetLastEventPhaseName();
// sorted by size
SMemoryStats ComputeView();
// can be optimized to not be generated each time
void GenerateVRamAllocationUsage(TArray<FVRamAllocation>& Out);
friend struct FPooledRenderTarget;
friend class FVisualizeTexture;
friend class FVisualizeTexturePresent;
friend class FRDGBuilder;
};
/** The global render targets for easy shading. */
extern RENDERCORE_API TGlobalResource<FRenderTargetPool> GRenderTargetPool;