You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- New Drain() method on FRDGBuilder; will flush all pending work. - Drained passes are not culled; resource lifetimes are extended; async compute fences are optimized as best as possible but fence joining may occur after the drain. - Batch up and pre-build all resource transitions. This is a prerequisite for parallel command lists. - Removed ServiceLocalQueue passes with built-in RDG AddDispatchHint(). #jira UE-114622 [CL 16393495 by zach bethel in ue5-main branch]
2998 lines
92 KiB
C++
2998 lines
92 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RenderGraphBuilder.h"
|
|
#include "RenderGraphPrivate.h"
|
|
#include "RenderGraphTrace.h"
|
|
#include "RenderTargetPool.h"
|
|
#include "RenderGraphResourcePool.h"
|
|
#include "VisualizeTexture.h"
|
|
#include "ProfilingDebugging/CsvProfiler.h"
|
|
|
|
#if ENABLE_RHI_VALIDATION
|
|
|
|
inline void GatherPassUAVsForOverlapValidation(const FRDGPass* Pass, TArray<FRHIUnorderedAccessView*, TInlineAllocator<MaxSimultaneousUAVs, FRDGArrayAllocator>>& OutUAVs)
|
|
{
|
|
// RHI validation tracking of Begin/EndUAVOverlaps happens on the underlying resource, so we need to be careful about not
|
|
// passing multiple UAVs that refer to the same resource, otherwise we get double-Begin and double-End validation errors.
|
|
// Filter UAVs to only those with unique parent resources.
|
|
TArray<FRDGParentResource*, TInlineAllocator<MaxSimultaneousUAVs, FRDGArrayAllocator>> UniqueParents;
|
|
Pass->GetParameters().Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
if (Parameter.IsUAV())
|
|
{
|
|
if (FRDGUnorderedAccessViewRef UAV = Parameter.GetAsUAV())
|
|
{
|
|
FRDGParentResource* Parent = UAV->GetParent();
|
|
|
|
// Check if we've already seen this parent.
|
|
bool bFound = false;
|
|
for (int32 Index = 0; !bFound && Index < UniqueParents.Num(); ++Index)
|
|
{
|
|
bFound = UniqueParents[Index] == Parent;
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
UniqueParents.Add(Parent);
|
|
OutUAVs.Add(UAV->GetRHI());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#endif
|
|
|
|
inline void BeginUAVOverlap(const FRDGPass* Pass, FRHIComputeCommandList& RHICmdList)
|
|
{
|
|
#if ENABLE_RHI_VALIDATION
|
|
TArray<FRHIUnorderedAccessView*, TInlineAllocator<MaxSimultaneousUAVs, FRDGArrayAllocator>> UAVs;
|
|
GatherPassUAVsForOverlapValidation(Pass, UAVs);
|
|
|
|
if (UAVs.Num())
|
|
{
|
|
RHICmdList.BeginUAVOverlap(UAVs);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
inline void EndUAVOverlap(const FRDGPass* Pass, FRHIComputeCommandList& RHICmdList)
|
|
{
|
|
#if ENABLE_RHI_VALIDATION
|
|
TArray<FRHIUnorderedAccessView*, TInlineAllocator<MaxSimultaneousUAVs, FRDGArrayAllocator>> UAVs;
|
|
GatherPassUAVsForOverlapValidation(Pass, UAVs);
|
|
|
|
if (UAVs.Num())
|
|
{
|
|
RHICmdList.EndUAVOverlap(UAVs);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
inline ERHIAccess MakeValidAccess(ERHIAccess Access)
|
|
{
|
|
// If we find any write states in the access mask, remove all read-only states. This mainly exists
|
|
// to allow RDG uniform buffers to contain read-only parameters which are also bound for write on the
|
|
// pass. Often times these uniform buffers are created and only relevant things are accessed. If an
|
|
// invalid access does occur, the RHI validation layer will catch it.
|
|
return IsWritableAccess(Access) ? (Access & ~ERHIAccess::ReadOnlyExclusiveMask) : Access;
|
|
}
|
|
|
|
inline void GetPassAccess(ERDGPassFlags PassFlags, ERHIAccess& SRVAccess, ERHIAccess& UAVAccess)
|
|
{
|
|
SRVAccess = ERHIAccess::Unknown;
|
|
UAVAccess = ERHIAccess::Unknown;
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster))
|
|
{
|
|
SRVAccess |= ERHIAccess::SRVGraphics;
|
|
UAVAccess |= ERHIAccess::UAVGraphics;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::AsyncCompute | ERDGPassFlags::Compute))
|
|
{
|
|
SRVAccess |= ERHIAccess::SRVCompute;
|
|
UAVAccess |= ERHIAccess::UAVCompute;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Copy))
|
|
{
|
|
SRVAccess |= ERHIAccess::CopySrc;
|
|
}
|
|
}
|
|
|
|
enum class ERDGTextureAccessFlags
|
|
{
|
|
None = 0,
|
|
|
|
// Access is within the fixed-function render pass.
|
|
RenderTarget = 1 << 0
|
|
};
|
|
ENUM_CLASS_FLAGS(ERDGTextureAccessFlags);
|
|
|
|
/** Enumerates all texture accesses and provides the access and subresource range info. This results in
|
|
* multiple invocations of the same resource, but with different access / subresource range.
|
|
*/
|
|
template <typename TAccessFunction>
|
|
void EnumerateTextureAccess(FRDGParameterStruct PassParameters, ERDGPassFlags PassFlags, TAccessFunction AccessFunction)
|
|
{
|
|
const ERDGTextureAccessFlags NoneFlags = ERDGTextureAccessFlags::None;
|
|
|
|
ERHIAccess SRVAccess, UAVAccess;
|
|
GetPassAccess(PassFlags, SRVAccess, UAVAccess);
|
|
|
|
PassParameters.EnumerateTextures([&](FRDGParameter Parameter)
|
|
{
|
|
switch (Parameter.GetType())
|
|
{
|
|
case UBMT_RDG_TEXTURE:
|
|
if (FRDGTextureRef Texture = Parameter.GetAsTexture())
|
|
{
|
|
AccessFunction(nullptr, Texture, SRVAccess, NoneFlags, Texture->GetSubresourceRangeSRV());
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS:
|
|
{
|
|
if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess())
|
|
{
|
|
AccessFunction(nullptr, TextureAccess.GetTexture(), TextureAccess.GetAccess(), NoneFlags, TextureAccess->GetSubresourceRange());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS_ARRAY:
|
|
{
|
|
const FRDGTextureAccessArray& TextureAccessArray = Parameter.GetAsTextureAccessArray();
|
|
|
|
for (FRDGTextureAccess TextureAccess : TextureAccessArray)
|
|
{
|
|
AccessFunction(nullptr, TextureAccess.GetTexture(), TextureAccess.GetAccess(), NoneFlags, TextureAccess->GetSubresourceRange());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_SRV:
|
|
if (FRDGTextureSRVRef SRV = Parameter.GetAsTextureSRV())
|
|
{
|
|
AccessFunction(SRV, SRV->GetParent(), SRVAccess, NoneFlags, SRV->GetSubresourceRange());
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_UAV:
|
|
if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV())
|
|
{
|
|
AccessFunction(UAV, UAV->GetParent(), UAVAccess, NoneFlags, UAV->GetSubresourceRange());
|
|
}
|
|
break;
|
|
case UBMT_RENDER_TARGET_BINDING_SLOTS:
|
|
{
|
|
const ERDGTextureAccessFlags RenderTargetAccess = ERDGTextureAccessFlags::RenderTarget;
|
|
|
|
const ERHIAccess RTVAccess = ERHIAccess::RTV;
|
|
|
|
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
|
|
|
|
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
|
|
{
|
|
FRDGTextureRef Texture = RenderTarget.GetTexture();
|
|
FRDGTextureRef ResolveTexture = RenderTarget.GetResolveTexture();
|
|
|
|
FRDGTextureSubresourceRange Range(Texture->GetSubresourceRange());
|
|
Range.MipIndex = RenderTarget.GetMipIndex();
|
|
Range.NumMips = 1;
|
|
|
|
if (RenderTarget.GetArraySlice() != -1)
|
|
{
|
|
Range.ArraySlice = RenderTarget.GetArraySlice();
|
|
Range.NumArraySlices = 1;
|
|
}
|
|
|
|
AccessFunction(nullptr, Texture, RTVAccess, RenderTargetAccess, Range);
|
|
|
|
if (ResolveTexture && ResolveTexture != Texture)
|
|
{
|
|
// Resolve targets must use the RTV|ResolveDst flag combination when the resolve is performed through the render
|
|
// pass. The ResolveDst flag must be used alone only when the resolve is performed using RHICopyToResolveTarget.
|
|
AccessFunction(nullptr, ResolveTexture, ERHIAccess::RTV | ERHIAccess::ResolveDst, RenderTargetAccess, Range);
|
|
}
|
|
});
|
|
|
|
const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil;
|
|
|
|
if (FRDGTextureRef Texture = DepthStencil.GetTexture())
|
|
{
|
|
DepthStencil.GetDepthStencilAccess().EnumerateSubresources([&](ERHIAccess NewAccess, uint32 PlaneSlice)
|
|
{
|
|
FRDGTextureSubresourceRange Range = Texture->GetSubresourceRange();
|
|
|
|
// Adjust the range to use a single plane slice if not using of them all.
|
|
if (PlaneSlice != FRHITransitionInfo::kAllSubresources)
|
|
{
|
|
Range.PlaneSlice = PlaneSlice;
|
|
Range.NumPlaneSlices = 1;
|
|
}
|
|
|
|
AccessFunction(nullptr, Texture, NewAccess, RenderTargetAccess, Range);
|
|
});
|
|
}
|
|
|
|
if (FRDGTextureRef Texture = RenderTargets.ShadingRateTexture)
|
|
{
|
|
AccessFunction(nullptr, Texture, ERHIAccess::ShadingRateSource, RenderTargetAccess, Texture->GetSubresourceRangeSRV());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Enumerates all buffer accesses and provides the access info. */
|
|
template <typename TAccessFunction>
|
|
void EnumerateBufferAccess(FRDGParameterStruct PassParameters, ERDGPassFlags PassFlags, TAccessFunction AccessFunction)
|
|
{
|
|
ERHIAccess SRVAccess, UAVAccess;
|
|
GetPassAccess(PassFlags, SRVAccess, UAVAccess);
|
|
|
|
PassParameters.EnumerateBuffers([&](FRDGParameter Parameter)
|
|
{
|
|
switch (Parameter.GetType())
|
|
{
|
|
case UBMT_RDG_BUFFER_ACCESS:
|
|
if (FRDGBufferAccess BufferAccess = Parameter.GetAsBufferAccess())
|
|
{
|
|
AccessFunction(nullptr, BufferAccess.GetBuffer(), BufferAccess.GetAccess());
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_ACCESS_ARRAY:
|
|
{
|
|
const FRDGBufferAccessArray& BufferAccessArray = Parameter.GetAsBufferAccessArray();
|
|
|
|
for (FRDGBufferAccess BufferAccess : BufferAccessArray)
|
|
{
|
|
AccessFunction(nullptr, BufferAccess.GetBuffer(), BufferAccess.GetAccess());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_SRV:
|
|
if (FRDGBufferSRVRef SRV = Parameter.GetAsBufferSRV())
|
|
{
|
|
FRDGBufferRef Buffer = SRV->GetParent();
|
|
ERHIAccess BufferAccess = SRVAccess;
|
|
|
|
if (EnumHasAnyFlags(Buffer->Desc.Usage, BUF_AccelerationStructure))
|
|
{
|
|
BufferAccess = ERHIAccess::BVHRead;
|
|
}
|
|
|
|
AccessFunction(SRV, Buffer, BufferAccess);
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_UAV:
|
|
if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV())
|
|
{
|
|
AccessFunction(UAV, UAV->GetParent(), UAVAccess);
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
inline FRDGViewHandle GetHandleIfNoUAVBarrier(FRDGViewRef Resource)
|
|
{
|
|
if (Resource && (Resource->Type == ERDGViewType::BufferUAV || Resource->Type == ERDGViewType::TextureUAV))
|
|
{
|
|
if (EnumHasAnyFlags(static_cast<FRDGUnorderedAccessViewRef>(Resource)->Flags, ERDGUnorderedAccessViewFlags::SkipBarrier))
|
|
{
|
|
return Resource->GetHandle();
|
|
}
|
|
}
|
|
return FRDGViewHandle::Null;
|
|
}
|
|
|
|
inline EResourceTransitionFlags GetTextureViewTransitionFlags(FRDGViewRef Resource, FRDGTextureRef Texture)
|
|
{
|
|
if (Resource)
|
|
{
|
|
switch (Resource->Type)
|
|
{
|
|
case ERDGViewType::TextureUAV:
|
|
{
|
|
FRDGTextureUAVRef UAV = static_cast<FRDGTextureUAVRef>(Resource);
|
|
if (UAV->Desc.MetaData != ERDGTextureMetaDataAccess::None)
|
|
{
|
|
return EResourceTransitionFlags::MaintainCompression;
|
|
}
|
|
}
|
|
break;
|
|
case ERDGViewType::TextureSRV:
|
|
{
|
|
FRDGTextureSRVRef SRV = static_cast<FRDGTextureSRVRef>(Resource);
|
|
if (SRV->Desc.MetaData != ERDGTextureMetaDataAccess::None)
|
|
{
|
|
return EResourceTransitionFlags::MaintainCompression;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (EnumHasAnyFlags(Texture->Flags, ERDGTextureFlags::MaintainCompression))
|
|
{
|
|
return EResourceTransitionFlags::MaintainCompression;
|
|
}
|
|
}
|
|
return EResourceTransitionFlags::None;
|
|
}
|
|
|
|
void FRDGBuilder::TickPoolElements()
|
|
{
|
|
GRenderGraphResourcePool.TickPoolElements();
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
if (GRDGDumpGraph)
|
|
{
|
|
--GRDGDumpGraph;
|
|
}
|
|
if (GRDGTransitionLog > 0)
|
|
{
|
|
--GRDGTransitionLog;
|
|
}
|
|
GRDGDumpGraphUnknownCount = 0;
|
|
#endif
|
|
|
|
#if STATS
|
|
SET_DWORD_STAT(STAT_RDG_PassCount, GRDGStatPassCount);
|
|
SET_DWORD_STAT(STAT_RDG_PassWithParameterCount, GRDGStatPassWithParameterCount);
|
|
SET_DWORD_STAT(STAT_RDG_PassCullCount, GRDGStatPassCullCount);
|
|
SET_DWORD_STAT(STAT_RDG_RenderPassMergeCount, GRDGStatRenderPassMergeCount);
|
|
SET_DWORD_STAT(STAT_RDG_PassDependencyCount, GRDGStatPassDependencyCount);
|
|
SET_DWORD_STAT(STAT_RDG_TextureCount, GRDGStatTextureCount);
|
|
SET_DWORD_STAT(STAT_RDG_TextureReferenceCount, GRDGStatTextureReferenceCount);
|
|
SET_FLOAT_STAT(STAT_RDG_TextureReferenceAverage, (float)(GRDGStatTextureReferenceCount / FMath::Max<float>(GRDGStatTextureCount, 1.0f)));
|
|
SET_DWORD_STAT(STAT_RDG_BufferCount, GRDGStatBufferCount);
|
|
SET_DWORD_STAT(STAT_RDG_BufferReferenceCount, GRDGStatBufferReferenceCount);
|
|
SET_FLOAT_STAT(STAT_RDG_BufferReferenceAverage, (float)(GRDGStatBufferReferenceCount / FMath::Max<float>(GRDGStatBufferCount, 1.0f)));
|
|
SET_DWORD_STAT(STAT_RDG_ViewCount, GRDGStatViewCount);
|
|
SET_DWORD_STAT(STAT_RDG_TransientTextureCount, GRDGStatTransientTextureCount);
|
|
SET_DWORD_STAT(STAT_RDG_TransientBufferCount, GRDGStatTransientBufferCount);
|
|
SET_DWORD_STAT(STAT_RDG_TransitionCount, GRDGStatTransitionCount);
|
|
SET_DWORD_STAT(STAT_RDG_AliasingCount, GRDGStatAliasingCount);
|
|
SET_DWORD_STAT(STAT_RDG_TransitionBatchCount, GRDGStatTransitionBatchCount);
|
|
SET_MEMORY_STAT(STAT_RDG_MemoryWatermark, int64(GRDGStatMemoryWatermark));
|
|
GRDGStatPassCount = 0;
|
|
GRDGStatPassWithParameterCount = 0;
|
|
GRDGStatPassCullCount = 0;
|
|
GRDGStatRenderPassMergeCount = 0;
|
|
GRDGStatPassDependencyCount = 0;
|
|
GRDGStatTextureCount = 0;
|
|
GRDGStatTextureReferenceCount = 0;
|
|
GRDGStatBufferCount = 0;
|
|
GRDGStatBufferReferenceCount = 0;
|
|
GRDGStatViewCount = 0;
|
|
GRDGStatTransientTextureCount = 0;
|
|
GRDGStatTransientBufferCount = 0;
|
|
GRDGStatTransitionCount = 0;
|
|
GRDGStatAliasingCount = 0;
|
|
GRDGStatTransitionBatchCount = 0;
|
|
GRDGStatMemoryWatermark = 0;
|
|
#endif
|
|
}
|
|
|
|
ERDGPassFlags FRDGBuilder::OverridePassFlags(const TCHAR* PassName, ERDGPassFlags PassFlags, bool bAsyncComputeSupported)
|
|
{
|
|
const bool bDebugAllowedForPass =
|
|
#if RDG_ENABLE_DEBUG
|
|
IsDebugAllowedForPass(PassName);
|
|
#else
|
|
true;
|
|
#endif
|
|
|
|
const bool bGlobalForceAsyncCompute = (GRDGAsyncCompute == RDG_ASYNC_COMPUTE_FORCE_ENABLED && !IsImmediateMode() && bDebugAllowedForPass);
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Compute) && (bGlobalForceAsyncCompute))
|
|
{
|
|
PassFlags &= ~ERDGPassFlags::Compute;
|
|
PassFlags |= ERDGPassFlags::AsyncCompute;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::AsyncCompute) && (GRDGAsyncCompute == RDG_ASYNC_COMPUTE_DISABLED || IsImmediateMode() || !bAsyncComputeSupported))
|
|
{
|
|
PassFlags &= ~ERDGPassFlags::AsyncCompute;
|
|
PassFlags |= ERDGPassFlags::Compute;
|
|
}
|
|
|
|
return PassFlags;
|
|
}
|
|
|
|
bool FRDGBuilder::IsTransient(FRDGBufferRef Buffer) const
|
|
{
|
|
if (!IsTransientInternal(Buffer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return EnumHasAnyFlags(Buffer->Desc.Usage, BUF_UnorderedAccess);
|
|
}
|
|
|
|
bool FRDGBuilder::IsTransient(FRDGTextureRef Texture) const
|
|
{
|
|
return IsTransientInternal(Texture);
|
|
}
|
|
|
|
bool FRDGBuilder::IsTransientInternal(FRDGParentResourceRef Resource) const
|
|
{
|
|
// Immediate mode can't use the transient allocator because we don't know if the user will extract the resource.
|
|
if (!GRDGTransientAllocator || IsImmediateMode())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Transient resources must stay within the graph.
|
|
if (Resource->bExternal || Resource->bExtracted || Resource->bUserSetNonTransient)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
if (GRDGDebugDisableTransientResources != 0 && IsDebugAllowedForResource(Resource->Name))
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
FRDGBuilder::FTransientResourceAllocator::~FTransientResourceAllocator()
|
|
{
|
|
if (Allocator)
|
|
{
|
|
Allocator->Release(RHICmdList);
|
|
}
|
|
}
|
|
|
|
IRHITransientResourceAllocator* FRDGBuilder::FTransientResourceAllocator::GetOrCreate()
|
|
{
|
|
check(!IsImmediateMode());
|
|
if (!Allocator && !bCreateAttempted)
|
|
{
|
|
Allocator = RHICreateTransientResourceAllocator();
|
|
bCreateAttempted = true;
|
|
}
|
|
return Allocator;
|
|
}
|
|
|
|
FRDGBuilder::FRDGBuilder(FRHICommandListImmediate& InRHICmdList, FRDGEventName InName)
|
|
: RHICmdList(InRHICmdList)
|
|
, Blackboard(Allocator)
|
|
, RHICmdListAsyncCompute(FRHICommandListExecutor::GetImmediateAsyncComputeCommandList())
|
|
, BuilderName(InName)
|
|
, ProloguePasses(InPlace, nullptr)
|
|
#if RDG_CPU_SCOPES
|
|
, CPUScopeStacks(RHICmdList, Allocator)
|
|
#endif
|
|
#if RDG_GPU_SCOPES
|
|
, GPUScopeStacks(RHICmdList, RHICmdListAsyncCompute, Allocator)
|
|
#endif
|
|
#if RDG_ENABLE_DEBUG
|
|
, UserValidation(Allocator)
|
|
, BarrierValidation(&Passes, BuilderName)
|
|
, LogFile(Passes)
|
|
#endif
|
|
, TransientResourceAllocator(RHICmdList)
|
|
{
|
|
AddProloguePass();
|
|
|
|
#if RDG_EVENTS != RDG_EVENTS_NONE
|
|
// This is polled once as a workaround for a race condition since the underlying global is not always changed on the render thread.
|
|
GRDGEmitEvents = GetEmitDrawEvents();
|
|
#endif
|
|
|
|
IF_RDG_ENABLE_DEBUG(LogFile.Begin(BuilderName));
|
|
}
|
|
|
|
FRDGBuilder::~FRDGBuilder() {}
|
|
|
|
const TRefCountPtr<FRDGPooledBuffer>& FRDGBuilder::ConvertToExternalBuffer(FRDGBufferRef Buffer)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateConvertToExternalResource(Buffer));
|
|
if (!Buffer->bExternal)
|
|
{
|
|
Buffer->bExternal = 1;
|
|
Buffer->AccessFinal = kDefaultAccessFinal;
|
|
BeginResourceRHI(GetProloguePassHandle(), Buffer);
|
|
ExternalBuffers.Add(Buffer->PooledBuffer, Buffer);
|
|
}
|
|
return GetPooledBuffer(Buffer);
|
|
}
|
|
|
|
const TRefCountPtr<IPooledRenderTarget>& FRDGBuilder::ConvertToExternalTexture(FRDGTextureRef Texture)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateConvertToExternalResource(Texture));
|
|
if (!Texture->bExternal)
|
|
{
|
|
Texture->bExternal = 1;
|
|
Texture->AccessFinal = kDefaultAccessFinal;
|
|
BeginResourceRHI(GetProloguePassHandle(), Texture);
|
|
ExternalTextures.Add(Texture->GetRHIUnchecked(), Texture);
|
|
}
|
|
return GetPooledTexture(Texture);
|
|
}
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FFinalizePassParameters, )
|
|
RDG_TEXTURE_ACCESS_ARRAY(Textures)
|
|
RDG_BUFFER_ACCESS_ARRAY(Buffers)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
void FRDGBuilder::FinalizeResourceAccess(FRDGTextureAccessArray&& InTextures, FRDGBufferAccessArray&& InBuffers)
|
|
{
|
|
auto* PassParameters = AllocParameters<FFinalizePassParameters>();
|
|
PassParameters->Textures = Forward<FRDGTextureAccessArray&&>(InTextures);
|
|
PassParameters->Buffers = Forward<FRDGBufferAccessArray&&>(InBuffers);
|
|
|
|
// Take reference to pass parameters version since we've moved the memory.
|
|
const auto& LocalTextures = PassParameters->Textures;
|
|
const auto& LocalBuffers = PassParameters->Buffers;
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
{
|
|
const FRDGPassHandle FinalizePassHandle(Passes.Num());
|
|
|
|
for (FRDGTextureAccess TextureAccess : LocalTextures)
|
|
{
|
|
UserValidation.ValidateFinalize(TextureAccess.GetTexture(), TextureAccess.GetAccess(), FinalizePassHandle);
|
|
}
|
|
|
|
for (FRDGBufferAccess BufferAccess : LocalBuffers)
|
|
{
|
|
UserValidation.ValidateFinalize(BufferAccess.GetBuffer(), BufferAccess.GetAccess(), FinalizePassHandle);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
AddPass(
|
|
RDG_EVENT_NAME("FinalizeResourceAccess(Textures: %d, Buffers: %d)", LocalTextures.Num(), LocalBuffers.Num()),
|
|
PassParameters,
|
|
// Use all of the work flags so that any access is valid.
|
|
ERDGPassFlags::Copy | ERDGPassFlags::Compute | ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass |
|
|
// We're not writing to anything, so we have to tell the pass not to cull.
|
|
ERDGPassFlags::NeverCull,
|
|
[](FRHICommandList&) {});
|
|
|
|
// bFinalized must be set after adding the finalize pass, as future declarations of the resource will be ignored.
|
|
|
|
for (FRDGTextureAccess TextureAccess : LocalTextures)
|
|
{
|
|
TextureAccess->bFinalizedAccess = 1;
|
|
}
|
|
|
|
for (FRDGBufferAccess BufferAccess : LocalBuffers)
|
|
{
|
|
BufferAccess->bFinalizedAccess = 1;
|
|
}
|
|
}
|
|
|
|
FRDGTextureRef FRDGBuilder::RegisterExternalTexture(
|
|
const TRefCountPtr<IPooledRenderTarget>& ExternalPooledTexture,
|
|
ERenderTargetTexture RenderTargetTexture,
|
|
ERDGTextureFlags Flags)
|
|
{
|
|
#if RDG_ENABLE_DEBUG
|
|
checkf(ExternalPooledTexture.IsValid(), TEXT("Attempted to register NULL external texture."));
|
|
#endif
|
|
|
|
const TCHAR* Name = ExternalPooledTexture->GetDesc().DebugName;
|
|
if (!Name)
|
|
{
|
|
Name = TEXT("External");
|
|
}
|
|
return RegisterExternalTexture(ExternalPooledTexture, Name, RenderTargetTexture, Flags);
|
|
}
|
|
|
|
FRDGTextureRef FRDGBuilder::RegisterExternalTexture(
|
|
const TRefCountPtr<IPooledRenderTarget>& ExternalPooledTexture,
|
|
const TCHAR* Name,
|
|
ERenderTargetTexture RenderTargetTexture,
|
|
ERDGTextureFlags Flags)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalTexture(ExternalPooledTexture, Name, RenderTargetTexture, Flags));
|
|
FRHITexture* ExternalTextureRHI = ExternalPooledTexture->GetRenderTargetItem().GetRHI(RenderTargetTexture);
|
|
IF_RDG_ENABLE_DEBUG(checkf(ExternalTextureRHI, TEXT("Attempted to register texture %s, but its RHI texture is null."), Name));
|
|
|
|
if (FRDGTextureRef FoundTexture = FindExternalTexture(ExternalTextureRHI))
|
|
{
|
|
return FoundTexture;
|
|
}
|
|
|
|
FRDGTextureRef Texture = Textures.Allocate(Allocator, Name, Translate(ExternalPooledTexture->GetDesc(), RenderTargetTexture), Flags, RenderTargetTexture);
|
|
Texture->SetRHI(static_cast<FPooledRenderTarget*>(ExternalPooledTexture.GetReference()));
|
|
|
|
const ERHIAccess AccessInitial = kDefaultAccessInitial;
|
|
|
|
Texture->bExternal = true;
|
|
Texture->AccessInitial = AccessInitial;
|
|
Texture->AccessFinal = EnumHasAnyFlags(ExternalTextureRHI->GetFlags(), TexCreate_Foveation)? ERHIAccess::ShadingRateSource : kDefaultAccessFinal;
|
|
Texture->FirstPass = GetProloguePassHandle();
|
|
|
|
FRDGTextureSubresourceState& TextureState = Texture->GetState();
|
|
|
|
checkf(IsWholeResource(TextureState) && GetWholeResource(TextureState).Access == ERHIAccess::Unknown,
|
|
TEXT("Externally registered texture '%s' has known RDG state. This means the graph did not sanitize it correctly, or ")
|
|
TEXT("an IPooledRenderTarget reference was improperly held within a pass."), Texture->Name);
|
|
|
|
{
|
|
FRDGSubresourceState SubresourceState;
|
|
SubresourceState.Access = AccessInitial;
|
|
InitAsWholeResource(TextureState, SubresourceState);
|
|
}
|
|
|
|
ExternalTextures.Add(Texture->GetRHIUnchecked(), Texture);
|
|
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalTexture(Texture));
|
|
IF_RDG_ENABLE_TRACE(Trace.AddResource(Texture));
|
|
return Texture;
|
|
}
|
|
|
|
FRDGBufferRef FRDGBuilder::RegisterExternalBuffer(const TRefCountPtr<FRDGPooledBuffer>& ExternalPooledBuffer, ERDGBufferFlags Flags)
|
|
{
|
|
#if RDG_ENABLE_DEBUG
|
|
checkf(ExternalPooledBuffer.IsValid(), TEXT("Attempted to register NULL external buffer."));
|
|
#endif
|
|
|
|
const TCHAR* Name = ExternalPooledBuffer->Name;
|
|
if (!Name)
|
|
{
|
|
Name = TEXT("External");
|
|
}
|
|
return RegisterExternalBuffer(ExternalPooledBuffer, Name, Flags);
|
|
}
|
|
|
|
FRDGBufferRef FRDGBuilder::RegisterExternalBuffer(
|
|
const TRefCountPtr<FRDGPooledBuffer>& ExternalPooledBuffer,
|
|
const TCHAR* Name,
|
|
ERDGBufferFlags Flags)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalBuffer(ExternalPooledBuffer, Name, Flags));
|
|
|
|
if (FRDGBufferRef* FoundBufferPtr = ExternalBuffers.Find(ExternalPooledBuffer.GetReference()))
|
|
{
|
|
return *FoundBufferPtr;
|
|
}
|
|
|
|
FRDGBufferRef Buffer = Buffers.Allocate(Allocator, Name, ExternalPooledBuffer->Desc, Flags);
|
|
Buffer->SetRHI(ExternalPooledBuffer);
|
|
|
|
const ERHIAccess AccessInitial = kDefaultAccessInitial;
|
|
|
|
Buffer->bExternal = true;
|
|
Buffer->AccessInitial = AccessInitial;
|
|
Buffer->AccessFinal = kDefaultAccessFinal;
|
|
Buffer->FirstPass = GetProloguePassHandle();
|
|
|
|
FRDGSubresourceState& BufferState = Buffer->GetState();
|
|
checkf(BufferState.Access == ERHIAccess::Unknown,
|
|
TEXT("Externally registered buffer '%s' has known RDG state. This means the graph did not sanitize it correctly, or ")
|
|
TEXT("an FRDGPooledBuffer reference was improperly held within a pass."), Buffer->Name);
|
|
|
|
BufferState.Access = AccessInitial;
|
|
|
|
ExternalBuffers.Add(ExternalPooledBuffer, Buffer);
|
|
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalBuffer(Buffer));
|
|
IF_RDG_ENABLE_TRACE(Trace.AddResource(Buffer));
|
|
return Buffer;
|
|
}
|
|
|
|
void FRDGBuilder::AddPassDependency(FRDGPassHandle ProducerHandle, FRDGPassHandle ConsumerHandle)
|
|
{
|
|
checkf(ProducerHandle.IsValid(), TEXT("AddPassDependency called with null producer."));
|
|
checkf(ConsumerHandle.IsValid(), TEXT("AddPassDependency called with null consumer."));
|
|
FRDGPass* Consumer = Passes[ConsumerHandle];
|
|
|
|
auto& Producers = Consumer->Producers;
|
|
if (Producers.Find(ProducerHandle) == INDEX_NONE)
|
|
{
|
|
Producers.Add(ProducerHandle);
|
|
|
|
#if STATS
|
|
GRDGStatPassDependencyCount++;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
void FRDGBuilder::Compile(EExecuteMode ExecuteMode)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RDG_CompileTime);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDG_Compile, GRDGVerboseCSVStats != 0);
|
|
SCOPED_NAMED_EVENT(FRDGBuilder_Compile, FColor::Emerald);
|
|
|
|
ExtendPassBitArray(PassesOnAsyncCompute, false);
|
|
|
|
const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle();
|
|
const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();
|
|
|
|
// This will differ from Passes.Num() when drains are performed.
|
|
const uint32 CompilePassCount = EpiloguePassHandle.GetIndex() - ProloguePassHandle.GetIndex() + 1;
|
|
|
|
// Culling is only performed in the final execution mode.
|
|
const bool bCullPasses = GRDGCullPasses && ExecuteMode == EExecuteMode::Final;
|
|
|
|
TArray<FRDGPassHandle, FRDGArrayAllocator> PassStack;
|
|
|
|
if (bCullPasses)
|
|
{
|
|
PassStack.Reserve(CompilePassCount);
|
|
}
|
|
|
|
TransitionCreateQueue.Reserve(CompilePassCount);
|
|
|
|
// Build producer / consumer dependencies across the graph and construct packed bit-arrays of metadata
|
|
// for better cache coherency when searching for passes meeting specific criteria. Search roots are also
|
|
// identified for culling. Passes with untracked RHI output (e.g. SHADER_PARAMETER_{BUFFER, TEXTURE}_UAV)
|
|
// cannot be culled, nor can any pass which writes to an external resource. Resource extractions extend the
|
|
// lifetime to the epilogue pass which is always a root of the graph. The prologue and epilogue are helper
|
|
// passes and therefore never culled.
|
|
|
|
if (bCullPasses || AsyncComputePassCount > 0)
|
|
{
|
|
SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Culling_Dependencies, FColor::Emerald);
|
|
|
|
const auto AddCullingDependency = [&](FRDGProducerStatesByPipeline& LastProducers, const FRDGProducerState& NextState, ERHIPipeline NextPipeline)
|
|
{
|
|
for (ERHIPipeline LastPipeline : GetRHIPipelines())
|
|
{
|
|
FRDGProducerState& LastProducer = LastProducers[LastPipeline];
|
|
|
|
if (LastProducer.Access == ERHIAccess::Unknown)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (FRDGProducerState::IsDependencyRequired(LastProducer, LastPipeline, NextState, NextPipeline))
|
|
{
|
|
AddPassDependency(LastProducer.PassHandle, NextState.PassHandle);
|
|
}
|
|
}
|
|
|
|
if (IsWritableAccess(NextState.Access))
|
|
{
|
|
LastProducers[NextPipeline] = NextState;
|
|
}
|
|
};
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle + 1; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
const ERHIPipeline PassPipeline = Pass->Pipeline;
|
|
|
|
bool bUntrackedOutputs = Pass->bHasExternalOutputs;
|
|
|
|
for (auto& PassState : Pass->TextureStates)
|
|
{
|
|
FRDGTextureRef Texture = PassState.Texture;
|
|
auto& LastProducers = Texture->LastProducers;
|
|
|
|
for (uint32 Index = 0, Count = LastProducers.Num(); Index < Count; ++Index)
|
|
{
|
|
const auto& SubresourceState = PassState.State[Index];
|
|
|
|
if (SubresourceState.Access == ERHIAccess::Unknown)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FRDGProducerState ProducerState;
|
|
ProducerState.Access = SubresourceState.Access;
|
|
ProducerState.PassHandle = PassHandle;
|
|
ProducerState.NoUAVBarrierHandle = SubresourceState.NoUAVBarrierFilter.GetUniqueHandle();
|
|
|
|
AddCullingDependency(LastProducers[Index], ProducerState, PassPipeline);
|
|
}
|
|
|
|
bUntrackedOutputs |= Texture->bExternal;
|
|
}
|
|
|
|
for (auto& PassState : Pass->BufferStates)
|
|
{
|
|
FRDGBufferRef Buffer = PassState.Buffer;
|
|
const auto& SubresourceState = PassState.State;
|
|
|
|
FRDGProducerState ProducerState;
|
|
ProducerState.Access = SubresourceState.Access;
|
|
ProducerState.PassHandle = PassHandle;
|
|
ProducerState.NoUAVBarrierHandle = SubresourceState.NoUAVBarrierFilter.GetUniqueHandle();
|
|
|
|
AddCullingDependency(Buffer->LastProducer, ProducerState, PassPipeline);
|
|
bUntrackedOutputs |= Buffer->bExternal;
|
|
}
|
|
|
|
PassesOnAsyncCompute[PassHandle] = EnumHasAnyFlags(Pass->Flags, ERDGPassFlags::AsyncCompute);
|
|
Pass->bCulled = bCullPasses;
|
|
|
|
if (bCullPasses && (bUntrackedOutputs || EnumHasAnyFlags(Pass->Flags, ERDGPassFlags::NeverCull)))
|
|
{
|
|
PassStack.Emplace(PassHandle);
|
|
}
|
|
}
|
|
|
|
if (ExecuteMode == EExecuteMode::Final)
|
|
{
|
|
for (const auto& Query : ExtractedTextures)
|
|
{
|
|
FRDGTextureRef Texture = Query.Key;
|
|
for (auto& LastProducer : Texture->LastProducers)
|
|
{
|
|
FRDGProducerState StateFinal;
|
|
StateFinal.Access = Texture->AccessFinal;
|
|
StateFinal.PassHandle = EpiloguePassHandle;
|
|
|
|
AddCullingDependency(LastProducer, StateFinal, ERHIPipeline::Graphics);
|
|
}
|
|
}
|
|
|
|
for (const auto& Query : ExtractedBuffers)
|
|
{
|
|
FRDGBufferRef Buffer = Query.Key;
|
|
|
|
FRDGProducerState StateFinal;
|
|
StateFinal.Access = Buffer->AccessFinal;
|
|
StateFinal.PassHandle = EpiloguePassHandle;
|
|
|
|
AddCullingDependency(Buffer->LastProducer, StateFinal, ERHIPipeline::Graphics);
|
|
}
|
|
}
|
|
}
|
|
|
|
// All dependencies in the raw graph have been specified; if enabled, all passes are marked as culled and a
|
|
// depth first search is employed to find reachable regions of the graph. Roots of the search are those passes
|
|
// with outputs leaving the graph or those marked to never cull.
|
|
|
|
if (bCullPasses)
|
|
{
|
|
SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Cull_Passes, FColor::Emerald);
|
|
|
|
PassStack.Emplace(EpiloguePassHandle);
|
|
|
|
// Manually mark the prologue passes as not culled.
|
|
ProloguePasses[ERHIPipeline::Graphics]->bCulled = 0;
|
|
|
|
if (FRDGPass* Pass = ProloguePasses[ERHIPipeline::AsyncCompute])
|
|
{
|
|
Pass->bCulled = 0;
|
|
}
|
|
|
|
while (PassStack.Num())
|
|
{
|
|
FRDGPass* Pass = Passes[PassStack.Pop()];
|
|
|
|
if (Pass->bCulled)
|
|
{
|
|
Pass->bCulled = 0;
|
|
PassStack.Append(Pass->Producers);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Walk the culled graph and compile barriers for each subresource. Certain transitions are redundant; read-to-read, for example.
|
|
// We can avoid them by traversing and merging compatible states together. The merging states removes a transition, but the merging
|
|
// heuristic is conservative and choosing not to merge doesn't necessarily mean a transition is performed. They are two distinct steps.
|
|
// Merged states track the first and last pass interval. Pass references are also accumulated onto each resource. This must happen
|
|
// after culling since culled passes can't contribute references.
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Barriers, FColor::Emerald);
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle + 1; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (Pass->bCulled || Pass->bEmptyParameters)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bAsyncComputePass = PassesOnAsyncCompute[PassHandle];
|
|
|
|
const ERHIPipeline PassPipeline = Pass->Pipeline;
|
|
|
|
const auto MergeSubresourceStates = [&](ERDGParentResourceType ResourceType, FRDGSubresourceState*& PassMergeState, FRDGSubresourceState*& ResourceMergeState, const FRDGSubresourceState& PassState)
|
|
{
|
|
if (!ResourceMergeState || !FRDGSubresourceState::IsMergeAllowed(ResourceType, *ResourceMergeState, PassState))
|
|
{
|
|
// Cross-pipeline, non-mergable state changes require a new pass dependency for fencing purposes.
|
|
if (ResourceMergeState)
|
|
{
|
|
for (ERHIPipeline Pipeline : GetRHIPipelines())
|
|
{
|
|
if (Pipeline != PassPipeline && ResourceMergeState->LastPass[Pipeline].IsValid())
|
|
{
|
|
// Add a dependency from the other pipe to this pass to join back.
|
|
AddPassDependency(ResourceMergeState->LastPass[Pipeline], PassHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allocate a new pending merge state and assign it to the pass state.
|
|
ResourceMergeState = AllocSubresource(PassState);
|
|
}
|
|
else
|
|
{
|
|
// Merge the pass state into the merged state.
|
|
ResourceMergeState->Access |= PassState.Access;
|
|
|
|
FRDGPassHandle& FirstPassHandle = ResourceMergeState->FirstPass[PassPipeline];
|
|
|
|
if (FirstPassHandle.IsNull())
|
|
{
|
|
FirstPassHandle = PassHandle;
|
|
}
|
|
|
|
ResourceMergeState->LastPass[PassPipeline] = PassHandle;
|
|
}
|
|
|
|
PassMergeState = ResourceMergeState;
|
|
};
|
|
|
|
for (auto& PassState : Pass->TextureStates)
|
|
{
|
|
FRDGTextureRef Texture = PassState.Texture;
|
|
Texture->ReferenceCount += PassState.ReferenceCount;
|
|
Texture->bUsedByAsyncComputePass |= bAsyncComputePass;
|
|
|
|
#if STATS
|
|
GRDGStatTextureReferenceCount += PassState.ReferenceCount;
|
|
#endif
|
|
|
|
for (int32 Index = 0; Index < PassState.State.Num(); ++Index)
|
|
{
|
|
if (PassState.State[Index].Access == ERHIAccess::Unknown)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
MergeSubresourceStates(ERDGParentResourceType::Texture, PassState.MergeState[Index], Texture->MergeState[Index], PassState.State[Index]);
|
|
}
|
|
}
|
|
|
|
for (auto& PassState : Pass->BufferStates)
|
|
{
|
|
FRDGBufferRef Buffer = PassState.Buffer;
|
|
Buffer->ReferenceCount += PassState.ReferenceCount;
|
|
Buffer->bUsedByAsyncComputePass |= bAsyncComputePass;
|
|
|
|
#if STATS
|
|
GRDGStatBufferReferenceCount += PassState.ReferenceCount;
|
|
#endif
|
|
|
|
MergeSubresourceStates(ERDGParentResourceType::Buffer, PassState.MergeState, Buffer->MergeState, PassState.State);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Traverses passes on the graphics pipe and merges raster passes with the same render targets into a single RHI render pass.
|
|
if (IsRenderPassMergeEnabled() && RasterPassCount > 0)
|
|
{
|
|
SCOPED_NAMED_EVENT(FRDGBuilder_Compile_RenderPassMerge, FColor::Emerald);
|
|
|
|
TArray<FRDGPassHandle, TInlineAllocator<32, FRDGArrayAllocator>> PassesToMerge;
|
|
FRDGPass* PrevPass = nullptr;
|
|
const FRenderTargetBindingSlots* PrevRenderTargets = nullptr;
|
|
|
|
const auto CommitMerge = [&]
|
|
{
|
|
if (PassesToMerge.Num())
|
|
{
|
|
const auto SetEpilogueBarrierPass = [&](FRDGPass* Pass, FRDGPassHandle EpilogueBarrierPassHandle)
|
|
{
|
|
Pass->EpilogueBarrierPass = EpilogueBarrierPassHandle;
|
|
Pass->ResourcesToEnd.Reset();
|
|
Passes[EpilogueBarrierPassHandle]->ResourcesToEnd.Add(Pass);
|
|
};
|
|
|
|
const auto SetPrologueBarrierPass = [&](FRDGPass* Pass, FRDGPassHandle PrologueBarrierPassHandle)
|
|
{
|
|
Pass->PrologueBarrierPass = PrologueBarrierPassHandle;
|
|
Pass->ResourcesToBegin.Reset();
|
|
Passes[PrologueBarrierPassHandle]->ResourcesToBegin.Add(Pass);
|
|
};
|
|
|
|
const FRDGPassHandle FirstPassHandle = PassesToMerge[0];
|
|
const FRDGPassHandle LastPassHandle = PassesToMerge.Last();
|
|
Passes[FirstPassHandle]->ResourcesToBegin.Reserve(PassesToMerge.Num());
|
|
Passes[LastPassHandle]->ResourcesToEnd.Reserve(PassesToMerge.Num());
|
|
|
|
// Given an interval of passes to merge into a single render pass: [B, X, X, X, X, E]
|
|
//
|
|
// The begin pass (B) and end (E) passes will call {Begin, End}RenderPass, respectively. Also,
|
|
// begin will handle all prologue barriers for the entire merged interval, and end will handle all
|
|
// epilogue barriers. This avoids transitioning of resources within the render pass and batches the
|
|
// transitions more efficiently. This assumes we have filtered out dependencies between passes from
|
|
// the merge set, which is done during traversal.
|
|
|
|
// (B) First pass in the merge sequence.
|
|
{
|
|
FRDGPass* Pass = Passes[FirstPassHandle];
|
|
Pass->bSkipRenderPassEnd = 1;
|
|
SetEpilogueBarrierPass(Pass, LastPassHandle);
|
|
}
|
|
|
|
// (X) Intermediate passes.
|
|
for (int32 PassIndex = 1, PassCount = PassesToMerge.Num() - 1; PassIndex < PassCount; ++PassIndex)
|
|
{
|
|
const FRDGPassHandle PassHandle = PassesToMerge[PassIndex];
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
Pass->bSkipRenderPassBegin = 1;
|
|
Pass->bSkipRenderPassEnd = 1;
|
|
SetPrologueBarrierPass(Pass, FirstPassHandle);
|
|
SetEpilogueBarrierPass(Pass, LastPassHandle);
|
|
}
|
|
|
|
// (E) Last pass in the merge sequence.
|
|
{
|
|
FRDGPass* Pass = Passes[LastPassHandle];
|
|
Pass->bSkipRenderPassBegin = 1;
|
|
SetPrologueBarrierPass(Pass, FirstPassHandle);
|
|
}
|
|
|
|
#if STATS
|
|
GRDGStatRenderPassMergeCount += PassesToMerge.Num();
|
|
#endif
|
|
}
|
|
PassesToMerge.Reset();
|
|
PrevPass = nullptr;
|
|
PrevRenderTargets = nullptr;
|
|
};
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle + 1; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* NextPass = Passes[PassHandle];
|
|
|
|
if (NextPass->bCulled || NextPass->bEmptyParameters)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(NextPass->Flags, ERDGPassFlags::Raster))
|
|
{
|
|
// A pass where the user controls the render pass or it is forced to skip pass merging can't merge with other passes
|
|
if (EnumHasAnyFlags(NextPass->Flags, ERDGPassFlags::SkipRenderPass | ERDGPassFlags::NeverMerge))
|
|
{
|
|
CommitMerge();
|
|
continue;
|
|
}
|
|
|
|
// A pass which writes to resources outside of the render pass introduces new dependencies which break merging.
|
|
if (!NextPass->bRenderPassOnlyWrites)
|
|
{
|
|
CommitMerge();
|
|
continue;
|
|
}
|
|
|
|
const FRenderTargetBindingSlots& RenderTargets = NextPass->GetParameters().GetRenderTargets();
|
|
|
|
if (PrevPass)
|
|
{
|
|
check(PrevRenderTargets);
|
|
|
|
if (PrevRenderTargets->CanMergeBefore(RenderTargets)
|
|
#if WITH_MGPU
|
|
&& PrevPass->GPUMask == NextPass->GPUMask
|
|
#endif
|
|
)
|
|
{
|
|
if (!PassesToMerge.Num())
|
|
{
|
|
PassesToMerge.Add(PrevPass->GetHandle());
|
|
}
|
|
PassesToMerge.Add(PassHandle);
|
|
}
|
|
else
|
|
{
|
|
CommitMerge();
|
|
}
|
|
}
|
|
|
|
PrevPass = NextPass;
|
|
PrevRenderTargets = &RenderTargets;
|
|
}
|
|
else if (!EnumHasAnyFlags(NextPass->Flags, ERDGPassFlags::AsyncCompute))
|
|
{
|
|
// A non-raster pass on the graphics pipe will invalidate the render target merge.
|
|
CommitMerge();
|
|
}
|
|
}
|
|
|
|
CommitMerge();
|
|
}
|
|
|
|
if (AsyncComputePassCount > 0)
|
|
{
|
|
SCOPED_NAMED_EVENT(FRDGBuilder_Compile_AsyncCompute, FColor::Emerald);
|
|
|
|
// Traverse the active passes in execution order to find latest cross-pipeline producer and the earliest
|
|
// cross-pipeline consumer for each pass. This helps narrow the search space later when building async
|
|
// compute overlap regions.
|
|
|
|
const auto IsCrossPipeline = [&](FRDGPassHandle A, FRDGPassHandle B)
|
|
{
|
|
return PassesOnAsyncCompute[A] != PassesOnAsyncCompute[B];
|
|
};
|
|
|
|
FRDGPassBitArray PassesWithCrossPipelineProducer(false, Passes.Num());
|
|
FRDGPassBitArray PassesWithCrossPipelineConsumer(false, Passes.Num());
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle + 1; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (Pass->bCulled || Pass->bEmptyParameters)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (FRDGPassHandle ProducerHandle : Pass->GetProducers())
|
|
{
|
|
const FRDGPassHandle ConsumerHandle = PassHandle;
|
|
|
|
if (!IsCrossPipeline(ProducerHandle, ConsumerHandle))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FRDGPass* Consumer = Pass;
|
|
FRDGPass* Producer = Passes[ProducerHandle];
|
|
|
|
// Finds the earliest consumer on the other pipeline for the producer.
|
|
if (Producer->CrossPipelineConsumer.IsNull() || ConsumerHandle < Producer->CrossPipelineConsumer)
|
|
{
|
|
Producer->CrossPipelineConsumer = PassHandle;
|
|
PassesWithCrossPipelineConsumer[ProducerHandle] = true;
|
|
}
|
|
|
|
// Finds the latest producer on the other pipeline for the consumer.
|
|
if (Consumer->CrossPipelineProducer.IsNull() || ProducerHandle > Consumer->CrossPipelineProducer)
|
|
{
|
|
Consumer->CrossPipelineProducer = ProducerHandle;
|
|
PassesWithCrossPipelineProducer[ConsumerHandle] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Establishes fork / join overlap regions for async compute. This is used for fencing as well as resource
|
|
// allocation / deallocation. Async compute passes can't allocate / release their resource references until
|
|
// the fork / join is complete, since the two pipes run in parallel. Therefore, all resource lifetimes on
|
|
// async compute are extended to cover the full async region.
|
|
|
|
const auto IsCrossPipelineProducer = [&](FRDGPassHandle A)
|
|
{
|
|
return PassesWithCrossPipelineConsumer[A];
|
|
};
|
|
|
|
const auto IsCrossPipelineConsumer = [&](FRDGPassHandle A)
|
|
{
|
|
return PassesWithCrossPipelineProducer[A];
|
|
};
|
|
|
|
const auto FindCrossPipelineProducer = [&](FRDGPassHandle PassHandle)
|
|
{
|
|
FRDGPassHandle LatestProducerHandle = ProloguePassHandle;
|
|
FRDGPassHandle ConsumerHandle = PassHandle;
|
|
|
|
// We want to find the latest producer on the other pipeline in order to establish a fork point.
|
|
// Since we could be consuming N resources with N producer passes, we only care about the last one.
|
|
while (ConsumerHandle != ProloguePassHandle)
|
|
{
|
|
if (IsCrossPipelineConsumer(ConsumerHandle) && !IsCrossPipeline(ConsumerHandle, PassHandle))
|
|
{
|
|
const FRDGPass* Consumer = Passes[ConsumerHandle];
|
|
|
|
if (Consumer->CrossPipelineProducer > LatestProducerHandle && !Consumer->bCulled)
|
|
{
|
|
LatestProducerHandle = Consumer->CrossPipelineProducer;
|
|
}
|
|
}
|
|
--ConsumerHandle;
|
|
}
|
|
|
|
return LatestProducerHandle;
|
|
};
|
|
|
|
const auto FindCrossPipelineConsumer = [&](FRDGPassHandle PassHandle)
|
|
{
|
|
FRDGPassHandle EarliestConsumerHandle = EpiloguePassHandle;
|
|
FRDGPassHandle ProducerHandle = PassHandle;
|
|
|
|
// We want to find the earliest consumer on the other pipeline, as this establishes a join point
|
|
// between the pipes. Since we could be producing for N consumers on the other pipeline, we only
|
|
// care about the first one to execute.
|
|
while (ProducerHandle != EpiloguePassHandle)
|
|
{
|
|
if (IsCrossPipelineProducer(ProducerHandle) && !IsCrossPipeline(ProducerHandle, PassHandle))
|
|
{
|
|
const FRDGPass* Producer = Passes[ProducerHandle];
|
|
|
|
if (Producer->CrossPipelineConsumer < EarliestConsumerHandle && !Producer->bCulled)
|
|
{
|
|
EarliestConsumerHandle = Producer->CrossPipelineConsumer;
|
|
}
|
|
}
|
|
++ProducerHandle;
|
|
}
|
|
|
|
return EarliestConsumerHandle;
|
|
};
|
|
|
|
const auto InsertGraphicsToAsyncComputeFork = [&](FRDGPass* GraphicsPass, FRDGPass* AsyncComputePass)
|
|
{
|
|
FRDGBarrierBatchBegin& EpilogueBarriersToBeginForAsyncCompute = GraphicsPass->GetEpilogueBarriersToBeginForAsyncCompute(Allocator, TransitionCreateQueue);
|
|
|
|
GraphicsPass->bGraphicsFork = 1;
|
|
EpilogueBarriersToBeginForAsyncCompute.SetUseCrossPipelineFence();
|
|
|
|
AsyncComputePass->bAsyncComputeBegin = 1;
|
|
AsyncComputePass->GetPrologueBarriersToEnd(Allocator).AddDependency(&EpilogueBarriersToBeginForAsyncCompute);
|
|
};
|
|
|
|
const auto InsertAsyncComputeToGraphicsJoin = [&](FRDGPass* AsyncComputePass, FRDGPass* GraphicsPass)
|
|
{
|
|
FRDGBarrierBatchBegin& EpilogueBarriersToBeginForGraphics = AsyncComputePass->GetEpilogueBarriersToBeginForGraphics(Allocator, TransitionCreateQueue);
|
|
|
|
AsyncComputePass->bAsyncComputeEnd = 1;
|
|
EpilogueBarriersToBeginForGraphics.SetUseCrossPipelineFence();
|
|
|
|
GraphicsPass->bGraphicsJoin = 1;
|
|
GraphicsPass->GetPrologueBarriersToEnd(Allocator).AddDependency(&EpilogueBarriersToBeginForGraphics);
|
|
};
|
|
|
|
FRDGPassHandle CurrentGraphicsForkPassHandle;
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle + 1; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
if (!PassesOnAsyncCompute[PassHandle])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FRDGPass* AsyncComputePass = Passes[PassHandle];
|
|
|
|
if (AsyncComputePass->bCulled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FRDGPassHandle GraphicsForkPassHandle = FindCrossPipelineProducer(PassHandle);
|
|
|
|
FRDGPass* GraphicsForkPass = Passes[GraphicsForkPassHandle];
|
|
|
|
AsyncComputePass->GraphicsForkPass = GraphicsForkPassHandle;
|
|
GraphicsForkPass->ResourcesToBegin.Add(AsyncComputePass);
|
|
|
|
if (CurrentGraphicsForkPassHandle != GraphicsForkPassHandle)
|
|
{
|
|
CurrentGraphicsForkPassHandle = GraphicsForkPassHandle;
|
|
InsertGraphicsToAsyncComputeFork(GraphicsForkPass, AsyncComputePass);
|
|
}
|
|
}
|
|
|
|
for (FRDGPassHandle PassHandle : PassesOnAsyncComputeToJoin)
|
|
{
|
|
const FRDGPassHandle GraphicsJoinPassHandle = FindCrossPipelineConsumer(PassHandle);
|
|
|
|
FRDGPass* AsyncComputePass = Passes[PassHandle];
|
|
FRDGPass* GraphicsJoinPass = Passes[GraphicsJoinPassHandle];
|
|
FRDGPass* AsyncComputeProloguePass = ProloguePasses[ERHIPipeline::AsyncCompute];
|
|
|
|
AsyncComputePass->GraphicsJoinPass = GraphicsJoinPassHandle;
|
|
GraphicsJoinPass->ResourcesToEnd.Add(AsyncComputePass);
|
|
|
|
if (AsyncComputeProloguePass->GraphicsJoinPass == AsyncComputeProloguePass->Handle)
|
|
{
|
|
AsyncComputeProloguePass->GraphicsJoinPass = GraphicsJoinPassHandle;
|
|
}
|
|
else
|
|
{
|
|
AsyncComputeProloguePass->GraphicsJoinPass = FMath::Min(AsyncComputeProloguePass->GraphicsJoinPass, GraphicsJoinPassHandle);
|
|
}
|
|
}
|
|
|
|
// Flush the prologue pass fence if it exists.
|
|
if (!PassesOnAsyncComputeToJoin.IsEmpty())
|
|
{
|
|
FRDGPass* AsyncComputePass = ProloguePasses[ERHIPipeline::AsyncCompute];
|
|
FRDGPass* GraphicsJoinPass = Passes[AsyncComputePass->GraphicsJoinPass];
|
|
|
|
check(AsyncComputePass && AsyncComputePass->GraphicsJoinPass && AsyncComputePass->Pipeline == ERHIPipeline::AsyncCompute);
|
|
InsertAsyncComputeToGraphicsJoin(AsyncComputePass, GraphicsJoinPass);
|
|
}
|
|
|
|
PassesOnAsyncComputeToJoin.Reset();
|
|
|
|
FRDGPassHandle CurrentGraphicsJoinPassHandle;
|
|
|
|
bool bFoundAsyncComputeEndExecutePass = false;
|
|
|
|
for (FRDGPassHandle PassHandle = EpiloguePassHandle - 1; PassHandle > ProloguePassHandle; --PassHandle)
|
|
{
|
|
if (!PassesOnAsyncCompute[PassHandle])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FRDGPass* AsyncComputePass = Passes[PassHandle];
|
|
|
|
if (AsyncComputePass->bCulled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FRDGPassHandle GraphicsJoinPassHandle = FindCrossPipelineConsumer(PassHandle);
|
|
|
|
// When draining and the epilogue pass is found, mark it for the next cycle and continue.
|
|
if (GraphicsJoinPassHandle == EpiloguePassHandle && ExecuteMode == EExecuteMode::Drain)
|
|
{
|
|
PassesOnAsyncComputeToJoin.Add(PassHandle);
|
|
continue;
|
|
}
|
|
|
|
FRDGPass* GraphicsJoinPass = Passes[GraphicsJoinPassHandle];
|
|
|
|
AsyncComputePass->GraphicsJoinPass = GraphicsJoinPassHandle;
|
|
GraphicsJoinPass->ResourcesToEnd.Add(AsyncComputePass);
|
|
|
|
if (CurrentGraphicsJoinPassHandle != GraphicsJoinPassHandle)
|
|
{
|
|
CurrentGraphicsJoinPassHandle = GraphicsJoinPassHandle;
|
|
InsertAsyncComputeToGraphicsJoin(AsyncComputePass, GraphicsJoinPass);
|
|
|
|
if (!bFoundAsyncComputeEndExecutePass)
|
|
{
|
|
bFoundAsyncComputeEndExecutePass = true;
|
|
|
|
AsyncComputePass->bAsyncComputeEndExecute = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFoundAsyncComputeEndExecutePass && ExecuteMode == EExecuteMode::Final)
|
|
{
|
|
ProloguePasses[ERHIPipeline::AsyncCompute]->bAsyncComputeEndExecute = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::Drain()
|
|
{
|
|
if (IsImmediateMode() || !GRDGDrain)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Execute(EExecuteMode::Drain);
|
|
}
|
|
|
|
void FRDGBuilder::Execute()
|
|
{
|
|
Execute(EExecuteMode::Final);
|
|
}
|
|
|
|
void FRDGBuilder::Execute(EExecuteMode ExecuteMode)
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RDG);
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::Execute);
|
|
|
|
// Create the epilogue pass at the end of the graph just prior to compilation.
|
|
SetupEmptyPass(EpiloguePass = Passes.Allocate<FRDGSentinelPass>(Allocator, RDG_EVENT_NAME("Graph Epilogue%s", (ExecuteMode == EExecuteMode::Drain) ? TEXT(" (Drain)") : TEXT(""))));
|
|
|
|
const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle();
|
|
const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();
|
|
|
|
const bool bFirstExecute = ExecuteCount == 0;
|
|
const bool bFinalExecute = ExecuteMode == EExecuteMode::Final;
|
|
|
|
if (bFirstExecute)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecuteBegin());
|
|
}
|
|
|
|
IF_RDG_ENABLE_DEBUG(GRDGAllowRHIAccess = true);
|
|
|
|
if (!IsImmediateMode())
|
|
{
|
|
Compile(ExecuteMode);
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RDG_CollectResourcesTime);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RDG_CollectResources);
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::CollectResources);
|
|
|
|
if (bFinalExecute)
|
|
{
|
|
EnumerateExtendedLifetimeResources(Textures, [](FRDGTexture* Texture)
|
|
{
|
|
++Texture->ReferenceCount;
|
|
});
|
|
|
|
EnumerateExtendedLifetimeResources(Buffers, [](FRDGBuffer* Buffer)
|
|
{
|
|
++Buffer->ReferenceCount;
|
|
});
|
|
|
|
for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle < ProloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (!Pass->bCulled)
|
|
{
|
|
EndResourcesRHI(Pass, ProloguePassHandle);
|
|
}
|
|
}
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle; PassHandle <= EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (!Pass->bCulled)
|
|
{
|
|
BeginResourcesRHI(Pass, PassHandle);
|
|
EndResourcesRHI(Pass, PassHandle);
|
|
}
|
|
}
|
|
|
|
for (const auto& Query : ExtractedTextures)
|
|
{
|
|
EndResourceRHI(EpiloguePassHandle, Query.Key, 1);
|
|
}
|
|
|
|
for (const auto& Query : ExtractedBuffers)
|
|
{
|
|
EndResourceRHI(EpiloguePassHandle, Query.Key, 1);
|
|
}
|
|
|
|
EnumerateExtendedLifetimeResources(Textures, [&](FRDGTextureRef Texture)
|
|
{
|
|
EndResourceRHI(EpiloguePassHandle, Texture, 1);
|
|
});
|
|
|
|
EnumerateExtendedLifetimeResources(Buffers, [&](FRDGBufferRef Buffer)
|
|
{
|
|
EndResourceRHI(EpiloguePassHandle, Buffer, 1);
|
|
});
|
|
|
|
if (TransientResourceAllocator)
|
|
{
|
|
TransientResourceAllocator->Freeze(RHICmdList);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
BeginResourcesRHI(Passes[PassHandle], PassHandle);
|
|
}
|
|
|
|
if (TransientResourceAllocator)
|
|
{
|
|
TransientResourceAllocator->Flush(RHICmdList);
|
|
}
|
|
}
|
|
}
|
|
|
|
CreateUniformBuffers();
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::CollectBarriers);
|
|
SCOPE_CYCLE_COUNTER(STAT_RDG_CollectBarriersTime);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDG_CollectBarriers, GRDGVerboseCSVStats != 0);
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle + 1; PassHandle < EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (!Pass->bCulled && !Pass->bEmptyParameters)
|
|
{
|
|
CollectPassBarriers(Pass, PassHandle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bFinalExecute)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::Finalize);
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
const auto LogResource = [&](auto* Resource, auto& Registry)
|
|
{
|
|
if (!Resource->bCulled)
|
|
{
|
|
if (!Resource->bLastOwner)
|
|
{
|
|
auto* NextOwner = Registry[Resource->NextOwner];
|
|
LogFile.AddAliasEdge(Resource, Resource->LastPass, NextOwner, NextOwner->FirstPass);
|
|
}
|
|
LogFile.AddFirstEdge(Resource, Resource->FirstPass);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
ActivePooledTextures.Reserve(Textures.Num());
|
|
Textures.Enumerate([&](FRDGTextureRef Texture)
|
|
{
|
|
if (Texture->HasRHI())
|
|
{
|
|
AddEpilogueTransition(Texture);
|
|
Texture->Finalize(ActivePooledTextures);
|
|
|
|
IF_RDG_ENABLE_DEBUG(LogResource(Texture, Textures));
|
|
}
|
|
});
|
|
|
|
ActivePooledBuffers.Reserve(Buffers.Num());
|
|
Buffers.Enumerate([&](FRDGBufferRef Buffer)
|
|
{
|
|
if (Buffer->HasRHI())
|
|
{
|
|
AddEpilogueTransition(Buffer);
|
|
Buffer->Finalize(ActivePooledBuffers);
|
|
|
|
IF_RDG_ENABLE_DEBUG(LogResource(Buffer, Buffers));
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::Finalize);
|
|
|
|
Textures.Enumerate([&](FRDGTextureRef Texture)
|
|
{
|
|
FMemory::Memzero(Texture->MergeState.GetData(), Texture->MergeState.Num() * sizeof(FRDGSubresourceState*));
|
|
});
|
|
|
|
Buffers.Enumerate([&](FRDGBufferRef Buffer)
|
|
{
|
|
Buffer->MergeState = nullptr;
|
|
});
|
|
}
|
|
|
|
IF_RDG_ENABLE_DEBUG(GRDGAllowRHIAccess = false);
|
|
|
|
CreatePassBarriers();
|
|
|
|
if (bFirstExecute)
|
|
{
|
|
IF_RDG_CPU_SCOPES(CPUScopeStacks.BeginExecute());
|
|
IF_RDG_GPU_SCOPES(GPUScopeStacks.BeginExecute());
|
|
IF_RDG_ENABLE_TRACE(Trace.OutputGraphBegin());
|
|
}
|
|
|
|
if (!IsImmediateMode())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_Execute_Passes);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderOther);
|
|
|
|
if (FRDGPass* AsyncComputeProloguePass = GetProloguePass(ERHIPipeline::AsyncCompute))
|
|
{
|
|
ExecutePass(AsyncComputeProloguePass);
|
|
}
|
|
|
|
for (FRDGPassHandle PassHandle = ProloguePassHandle; PassHandle <= EpiloguePassHandle; ++PassHandle)
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (!Pass->bCulled)
|
|
{
|
|
ExecutePass(Pass);
|
|
}
|
|
|
|
#if STATS
|
|
GRDGStatPassCullCount += Pass->bCulled;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExecutePass(EpiloguePass);
|
|
}
|
|
|
|
if (bFinalExecute)
|
|
{
|
|
RHICmdList.SetStaticUniformBuffers({});
|
|
|
|
#if WITH_MGPU
|
|
if (NameForTemporalEffect != NAME_None)
|
|
{
|
|
TArray<FRHITexture*> BroadcastTexturesForTemporalEffect;
|
|
for (const auto& Query : ExtractedTextures)
|
|
{
|
|
if (EnumHasAnyFlags(Query.Key->Flags, ERDGTextureFlags::MultiFrame))
|
|
{
|
|
BroadcastTexturesForTemporalEffect.Add(Query.Key->GetRHIUnchecked());
|
|
}
|
|
}
|
|
RHICmdList.BroadcastTemporalEffect(NameForTemporalEffect, BroadcastTexturesForTemporalEffect);
|
|
}
|
|
#endif
|
|
|
|
for (const auto& Query : ExtractedTextures)
|
|
{
|
|
*Query.Value = Query.Key->PooledRenderTarget;
|
|
}
|
|
|
|
for (const auto& Query : ExtractedBuffers)
|
|
{
|
|
*Query.Value = Query.Key->PooledBuffer;
|
|
}
|
|
|
|
IF_RDG_ENABLE_TRACE(Trace.OutputGraphEnd(*this));
|
|
IF_RDG_GPU_SCOPES(GPUScopeStacks.Graphics.EndExecute());
|
|
IF_RDG_CPU_SCOPES(CPUScopeStacks.EndExecute());
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecuteEnd());
|
|
IF_RDG_ENABLE_DEBUG(LogFile.End());
|
|
|
|
#if STATS
|
|
GRDGStatBufferCount += Buffers.Num();
|
|
GRDGStatTextureCount += Textures.Num();
|
|
GRDGStatViewCount += Views.Num();
|
|
GRDGStatMemoryWatermark = FMath::Max(GRDGStatMemoryWatermark, Allocator.GetByteCount());
|
|
#endif
|
|
|
|
Clear();
|
|
}
|
|
else
|
|
{
|
|
AddProloguePass();
|
|
}
|
|
|
|
ExecuteCount++;
|
|
}
|
|
|
|
void FRDGBuilder::Clear()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::Clear);
|
|
Passes.Clear();
|
|
UniformBuffers.Clear();
|
|
Blackboard.Clear();
|
|
ActivePooledTextures.Empty();
|
|
ActivePooledBuffers.Empty();
|
|
}
|
|
|
|
FRDGPass* FRDGBuilder::SetupPass(FRDGPass* Pass)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateAddPass(Pass, bInDebugPassScope));
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDGBuilder_SetupPass, GRDGVerboseCSVStats != 0);
|
|
|
|
const FRDGParameterStruct PassParameters = Pass->GetParameters();
|
|
const FRDGPassHandle PassHandle = Pass->Handle;
|
|
const ERDGPassFlags PassFlags = Pass->Flags;
|
|
const ERHIPipeline PassPipeline = Pass->Pipeline;
|
|
|
|
bool bRenderPassOnlyWrites = true;
|
|
|
|
const auto TryAddView = [&](FRDGViewRef View)
|
|
{
|
|
if (View && View->LastPass != PassHandle)
|
|
{
|
|
View->LastPass = PassHandle;
|
|
Pass->Views.Add(View->Handle);
|
|
}
|
|
};
|
|
|
|
Pass->Views.Reserve(PassParameters.GetBufferParameterCount() + PassParameters.GetTextureParameterCount());
|
|
Pass->TextureStates.Reserve(PassParameters.GetTextureParameterCount() + (PassParameters.HasRenderTargets() ? (MaxSimultaneousRenderTargets + 1) : 0));
|
|
EnumerateTextureAccess(PassParameters, PassFlags, [&](FRDGViewRef TextureView, FRDGTextureRef Texture, ERHIAccess Access, ERDGTextureAccessFlags AccessFlags, FRDGTextureSubresourceRange Range)
|
|
{
|
|
TryAddView(TextureView);
|
|
|
|
if (Texture->bFinalizedAccess)
|
|
{
|
|
// Finalized resources expected to remain in the same state, so are ignored by the graph.
|
|
// As only External | Extracted resources can be finalized by the user, the graph doesn't
|
|
// need to track them any more for culling / transition purposes. Validation checks that these
|
|
// invariants are true.
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateFinalizedAccess(Texture, Access, Pass));
|
|
return;
|
|
}
|
|
|
|
const FRDGViewHandle NoUAVBarrierHandle = GetHandleIfNoUAVBarrier(TextureView);
|
|
const EResourceTransitionFlags TransitionFlags = GetTextureViewTransitionFlags(TextureView, Texture);
|
|
|
|
FRDGPass::FTextureState* PassState;
|
|
|
|
if (Texture->LastPass != PassHandle)
|
|
{
|
|
Texture->LastPass = PassHandle;
|
|
Texture->PassStateIndex = Pass->TextureStates.Num();
|
|
|
|
PassState = &Pass->TextureStates.Emplace_GetRef(Texture);
|
|
}
|
|
else
|
|
{
|
|
PassState = &Pass->TextureStates[Texture->PassStateIndex];
|
|
}
|
|
|
|
PassState->ReferenceCount++;
|
|
|
|
const auto AddSubresourceAccess = [&](FRDGSubresourceState& State)
|
|
{
|
|
State.Access = MakeValidAccess(State.Access | Access);
|
|
State.Flags |= TransitionFlags;
|
|
State.NoUAVBarrierFilter.AddHandle(NoUAVBarrierHandle);
|
|
State.SetPass(PassPipeline, PassHandle);
|
|
};
|
|
|
|
if (IsWholeResource(PassState->State))
|
|
{
|
|
AddSubresourceAccess(GetWholeResource(PassState->State));
|
|
}
|
|
else
|
|
{
|
|
EnumerateSubresourceRange(PassState->State, Texture->Layout, Range, AddSubresourceAccess);
|
|
}
|
|
|
|
const bool bWritableAccess = IsWritableAccess(Access);
|
|
bRenderPassOnlyWrites &= (!bWritableAccess || EnumHasAnyFlags(AccessFlags, ERDGTextureAccessFlags::RenderTarget));
|
|
Texture->bProduced |= bWritableAccess;
|
|
});
|
|
|
|
Pass->BufferStates.Reserve(PassParameters.GetBufferParameterCount());
|
|
EnumerateBufferAccess(PassParameters, PassFlags, [&](FRDGViewRef BufferView, FRDGBufferRef Buffer, ERHIAccess Access)
|
|
{
|
|
TryAddView(BufferView);
|
|
|
|
if (Buffer->bFinalizedAccess)
|
|
{
|
|
// Finalized resources expected to remain in the same state, so are ignored by the graph.
|
|
// As only External | Extracted resources can be finalized by the user, the graph doesn't
|
|
// need to track them any more for culling / transition purposes. Validation checks that these
|
|
// invariants are true.
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateFinalizedAccess(Buffer, Access, Pass));
|
|
return;
|
|
}
|
|
|
|
const FRDGViewHandle NoUAVBarrierHandle = GetHandleIfNoUAVBarrier(BufferView);
|
|
|
|
FRDGPass::FBufferState* PassState;
|
|
|
|
if (Buffer->LastPass != PassHandle)
|
|
{
|
|
Buffer->LastPass = PassHandle;
|
|
Buffer->PassStateIndex = Pass->BufferStates.Num();
|
|
|
|
PassState = &Pass->BufferStates.Emplace_GetRef(Buffer);
|
|
}
|
|
else
|
|
{
|
|
PassState = &Pass->BufferStates[Buffer->PassStateIndex];
|
|
}
|
|
|
|
PassState->ReferenceCount++;
|
|
PassState->State.Access = MakeValidAccess(PassState->State.Access | Access);
|
|
PassState->State.NoUAVBarrierFilter.AddHandle(NoUAVBarrierHandle);
|
|
PassState->State.SetPass(PassPipeline, PassHandle);
|
|
|
|
const bool bWritableAccess = IsWritableAccess(Access);
|
|
bRenderPassOnlyWrites &= !bWritableAccess;
|
|
Buffer->bProduced |= bWritableAccess;
|
|
});
|
|
|
|
PassParameters.EnumerateUniformBuffers([&](FRDGUniformBufferBinding UniformBuffer)
|
|
{
|
|
if (!UniformBuffer->bQueuedForCreate)
|
|
{
|
|
UniformBuffersToCreate.Emplace(UniformBuffer->Handle);
|
|
UniformBuffer->bQueuedForCreate = true;
|
|
}
|
|
});
|
|
|
|
Pass->bRenderPassOnlyWrites = bRenderPassOnlyWrites;
|
|
Pass->bHasExternalOutputs = PassParameters.HasExternalOutputs();
|
|
|
|
const bool bEmptyParameters = !Pass->TextureStates.Num() && !Pass->BufferStates.Num();
|
|
SetupPassInternal(Pass, PassHandle, PassPipeline, bEmptyParameters);
|
|
return Pass;
|
|
}
|
|
|
|
FRDGPass* FRDGBuilder::SetupEmptyPass(FRDGPass* Pass)
|
|
{
|
|
const bool bEmptyParameters = true;
|
|
SetupPassInternal(Pass, Pass->Handle, Pass->Pipeline, bEmptyParameters);
|
|
return Pass;
|
|
}
|
|
|
|
void FRDGBuilder::SetupPassInternal(FRDGPass* Pass, FRDGPassHandle PassHandle, ERHIPipeline PassPipeline, bool bEmptyParameters)
|
|
{
|
|
check(Pass->Handle == PassHandle);
|
|
check(Pass->Pipeline == PassPipeline);
|
|
|
|
Pass->bEmptyParameters = bEmptyParameters;
|
|
Pass->bDispatchAfterExecute = bDispatchHint;
|
|
Pass->GraphicsForkPass = PassHandle;
|
|
Pass->GraphicsJoinPass = PassHandle;
|
|
Pass->PrologueBarrierPass = PassHandle;
|
|
Pass->EpilogueBarrierPass = PassHandle;
|
|
|
|
bDispatchHint = false;
|
|
|
|
if (!EnumHasAnyFlags(Pass->Flags, ERDGPassFlags::AsyncCompute))
|
|
{
|
|
Pass->ResourcesToBegin.Add(Pass);
|
|
Pass->ResourcesToEnd.Add(Pass);
|
|
}
|
|
|
|
AsyncComputePassCount += EnumHasAnyFlags(Pass->Flags, ERDGPassFlags::AsyncCompute) ? 1 : 0;
|
|
RasterPassCount += EnumHasAnyFlags(Pass->Flags, ERDGPassFlags::Raster) ? 1 : 0;
|
|
|
|
#if WITH_MGPU
|
|
Pass->GPUMask = RHICmdList.GetGPUMask();
|
|
#endif
|
|
|
|
#if STATS
|
|
Pass->CommandListStat = CommandListStatScope;
|
|
|
|
GRDGStatPassCount++;
|
|
GRDGStatPassWithParameterCount += !bEmptyParameters ? 1 : 0;
|
|
#endif
|
|
|
|
IF_RDG_CPU_SCOPES(Pass->CPUScopes = CPUScopeStacks.GetCurrentScopes());
|
|
IF_RDG_GPU_SCOPES(Pass->GPUScopes = GPUScopeStacks.GetCurrentScopes(PassPipeline));
|
|
|
|
#if RDG_GPU_SCOPES && RDG_ENABLE_TRACE
|
|
Pass->TraceEventScope = GPUScopeStacks.GetCurrentScopes(ERHIPipeline::Graphics).Event;
|
|
#endif
|
|
|
|
#if RDG_GPU_SCOPES && RDG_ENABLE_DEBUG
|
|
if (const FRDGEventScope* Scope = Pass->GPUScopes.Event)
|
|
{
|
|
Pass->FullPathIfDebug = Scope->GetPath(Pass->Name);
|
|
}
|
|
#endif
|
|
|
|
if (IsImmediateMode() && !Pass->bSentinel)
|
|
{
|
|
RDG_ALLOW_RHI_ACCESS_SCOPE();
|
|
|
|
// Trivially redirect the merge states to the pass states, since we won't be compiling the graph.
|
|
for (auto& PassState : Pass->TextureStates)
|
|
{
|
|
const uint32 SubresourceCount = PassState.State.Num();
|
|
PassState.MergeState.SetNum(SubresourceCount);
|
|
for (uint32 Index = 0; Index < SubresourceCount; ++Index)
|
|
{
|
|
if (PassState.State[Index].Access != ERHIAccess::Unknown)
|
|
{
|
|
PassState.MergeState[Index] = &PassState.State[Index];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& PassState : Pass->BufferStates)
|
|
{
|
|
PassState.MergeState = &PassState.State;
|
|
}
|
|
|
|
check(!EnumHasAnyFlags(PassPipeline, ERHIPipeline::AsyncCompute));
|
|
|
|
BeginResourcesRHI(Pass, PassHandle);
|
|
EndResourcesRHI(Pass, PassHandle);
|
|
CollectPassBarriers(Pass, PassHandle);
|
|
CreatePassBarriers();
|
|
CreateUniformBuffers();
|
|
ExecutePass(Pass);
|
|
}
|
|
|
|
IF_RDG_ENABLE_DEBUG(VisualizePassOutputs(Pass));
|
|
}
|
|
|
|
void FRDGBuilder::CreateUniformBuffers()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::CreateUniformBuffers);
|
|
|
|
for (FRDGUniformBufferHandle UniformBufferHandle : UniformBuffersToCreate)
|
|
{
|
|
UniformBuffers[UniformBufferHandle]->InitRHI();
|
|
}
|
|
UniformBuffersToCreate.Reset();
|
|
}
|
|
|
|
void FRDGBuilder::AddProloguePass()
|
|
{
|
|
if (!PassesOnAsyncComputeToJoin.IsEmpty())
|
|
{
|
|
FRDGPass* AsyncComputePass = SetupEmptyPass(Passes.Allocate<FRDGSentinelPass>(Allocator, RDG_EVENT_NAME("Graph Prologue (AsyncCompute)"), ERDGPassFlags::AsyncCompute));
|
|
ProloguePasses[ERHIPipeline::AsyncCompute] = AsyncComputePass;
|
|
ProloguePassHandles[ERHIPipeline::AsyncCompute] = AsyncComputePass->Handle;
|
|
}
|
|
|
|
FRDGPass* GraphicsPass = SetupEmptyPass(Passes.Allocate<FRDGSentinelPass>(Allocator, RDG_EVENT_NAME("Graph Prologue (Graphics)")));
|
|
ProloguePasses[ERHIPipeline::Graphics] = GraphicsPass;
|
|
ProloguePassHandles[ERHIPipeline::Graphics] = GraphicsPass->Handle;
|
|
}
|
|
|
|
void FRDGBuilder::ExecutePassPrologue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass)
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDGBuilder_ExecutePassPrologue, GRDGVerboseCSVStats != 0);
|
|
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecutePassBegin(Pass));
|
|
|
|
#if RDG_CMDLIST_STATS
|
|
if (!Pass->bSentinel && CommandListStatState != Pass->CommandListStat)
|
|
{
|
|
CommandListStatState = Pass->CommandListStat;
|
|
RHICmdList.SetCurrentStat(Pass->CommandListStat);
|
|
}
|
|
#endif
|
|
|
|
const ERDGPassFlags PassFlags = Pass->Flags;
|
|
const ERHIPipeline PassPipeline = Pass->Pipeline;
|
|
|
|
if (Pass->PrologueBarriersToBegin)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->PrologueBarriersToBegin));
|
|
Pass->PrologueBarriersToBegin->Submit(RHICmdListPass, PassPipeline);
|
|
}
|
|
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchEnd(Pass, Pass->PrologueBarriersToEnd));
|
|
Pass->PrologueBarriersToEnd.Submit(RHICmdListPass, PassPipeline);
|
|
|
|
if (PassPipeline == ERHIPipeline::AsyncCompute && !Pass->bSentinel && AsyncComputeBudgetState != Pass->AsyncComputeBudget)
|
|
{
|
|
AsyncComputeBudgetState = Pass->AsyncComputeBudget;
|
|
RHICmdListPass.SetAsyncComputeBudget(Pass->AsyncComputeBudget);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster))
|
|
{
|
|
if (!EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassBegin())
|
|
{
|
|
static_cast<FRHICommandList&>(RHICmdListPass).BeginRenderPass(Pass->GetParameters().GetRenderPassInfo(), Pass->GetName());
|
|
}
|
|
}
|
|
|
|
BeginUAVOverlap(Pass, RHICmdListPass);
|
|
}
|
|
|
|
void FRDGBuilder::ExecutePassEpilogue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass)
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDGBuilder_ExecutePassEpilogue, GRDGVerboseCSVStats != 0);
|
|
|
|
EndUAVOverlap(Pass, RHICmdListPass);
|
|
|
|
const ERDGPassFlags PassFlags = Pass->Flags;
|
|
const ERHIPipeline PassPipeline = Pass->Pipeline;
|
|
const FRDGParameterStruct PassParameters = Pass->GetParameters();
|
|
|
|
if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster) && !EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassEnd())
|
|
{
|
|
static_cast<FRHICommandList&>(RHICmdListPass).EndRenderPass();
|
|
}
|
|
|
|
FRDGTransitionQueue Transitions;
|
|
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, Pass->EpilogueBarriersToBeginForGraphics));
|
|
Pass->EpilogueBarriersToBeginForGraphics.Submit(RHICmdListPass, PassPipeline, Transitions);
|
|
|
|
if (Pass->EpilogueBarriersToBeginForAsyncCompute)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->EpilogueBarriersToBeginForAsyncCompute));
|
|
Pass->EpilogueBarriersToBeginForAsyncCompute->Submit(RHICmdListPass, PassPipeline, Transitions);
|
|
}
|
|
|
|
if (Pass->EpilogueBarriersToBeginForAll)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->EpilogueBarriersToBeginForAll));
|
|
Pass->EpilogueBarriersToBeginForAll->Submit(RHICmdListPass, PassPipeline, Transitions);
|
|
}
|
|
|
|
for (FRDGBarrierBatchBegin* BarriersToBegin : Pass->SharedEpilogueBarriersToBegin)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *BarriersToBegin));
|
|
BarriersToBegin->Submit(RHICmdListPass, PassPipeline, Transitions);
|
|
}
|
|
|
|
if (!Transitions.IsEmpty())
|
|
{
|
|
RHICmdListPass.BeginTransitions(Transitions);
|
|
}
|
|
|
|
if (Pass->EpilogueBarriersToEnd)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchEnd(Pass, *Pass->EpilogueBarriersToEnd));
|
|
Pass->EpilogueBarriersToEnd->Submit(RHICmdListPass, PassPipeline);
|
|
}
|
|
|
|
IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecutePassEnd(Pass));
|
|
}
|
|
|
|
void FRDGBuilder::ExecutePass(FRDGPass* Pass)
|
|
{
|
|
// Note that we must do this before doing anything with RHICmdList for the pass.
|
|
// For example, if this pass only executes on GPU 1 we want to avoid adding a
|
|
// 0-duration event for this pass on GPU 0's time line.
|
|
SCOPED_GPU_MASK(RHICmdList, Pass->GPUMask);
|
|
|
|
IF_RDG_CPU_SCOPES(CPUScopeStacks.BeginExecutePass(Pass));
|
|
|
|
IF_RDG_ENABLE_DEBUG(ConditionalDebugBreak(RDG_BREAKPOINT_PASS_EXECUTE, BuilderName.GetTCHAR(), Pass->GetName()));
|
|
|
|
#if WITH_MGPU
|
|
if (!bWaitedForTemporalEffect && NameForTemporalEffect != NAME_None)
|
|
{
|
|
RHICmdList.WaitForTemporalEffect(NameForTemporalEffect);
|
|
bWaitedForTemporalEffect = true;
|
|
}
|
|
#endif
|
|
|
|
// Execute the pass by invoking the prologue, then the pass body, then the epilogue.
|
|
// The entire pass is executed using the command list on the specified pipeline.
|
|
FRHIComputeCommandList& RHICmdListPass = (Pass->Pipeline == ERHIPipeline::AsyncCompute)
|
|
? static_cast<FRHIComputeCommandList&>(RHICmdListAsyncCompute)
|
|
: RHICmdList;
|
|
|
|
ExecutePassPrologue(RHICmdListPass, Pass);
|
|
|
|
#if RDG_GPU_SCOPES
|
|
if (!Pass->bSentinel)
|
|
{
|
|
GPUScopeStacks.BeginExecutePass(Pass);
|
|
}
|
|
#endif
|
|
|
|
Pass->Execute(RHICmdListPass);
|
|
|
|
#if RDG_GPU_SCOPES
|
|
if (!Pass->bSentinel)
|
|
{
|
|
GPUScopeStacks.EndExecutePass(Pass);
|
|
}
|
|
#endif
|
|
|
|
ExecutePassEpilogue(RHICmdListPass, Pass);
|
|
|
|
if (Pass->bAsyncComputeEnd)
|
|
{
|
|
if (Pass->bAsyncComputeEndExecute)
|
|
{
|
|
IF_RDG_GPU_SCOPES(GPUScopeStacks.AsyncCompute.EndExecute());
|
|
}
|
|
FRHIAsyncComputeCommandListImmediate::ImmediateDispatch(RHICmdListAsyncCompute);
|
|
}
|
|
|
|
if (GRDGDebugFlushGPU && !GRDGAsyncCompute)
|
|
{
|
|
RHICmdList.SubmitCommandsAndFlushGPU();
|
|
RHICmdList.BlockUntilGPUIdle();
|
|
}
|
|
|
|
if (Pass->bDispatchAfterExecute && IsRunningRHIInSeparateThread())
|
|
{
|
|
RHICmdList.SubmitCommandsHint();
|
|
FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GetRenderThread_Local());
|
|
RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourcesRHI(FRDGPass* ResourcePass, FRDGPassHandle ExecutePassHandle)
|
|
{
|
|
for (FRDGPass* PassToBegin : ResourcePass->ResourcesToBegin)
|
|
{
|
|
for (const auto& PassState : PassToBegin->TextureStates)
|
|
{
|
|
BeginResourceRHI(ExecutePassHandle, PassState.Texture);
|
|
}
|
|
|
|
for (const auto& PassState : PassToBegin->BufferStates)
|
|
{
|
|
BeginResourceRHI(ExecutePassHandle, PassState.Buffer);
|
|
}
|
|
|
|
for (FRDGViewHandle ViewHandle : PassToBegin->Views)
|
|
{
|
|
BeginResourceRHI(ExecutePassHandle, Views[ViewHandle]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::EndResourcesRHI(FRDGPass* ResourcePass, FRDGPassHandle ExecutePassHandle)
|
|
{
|
|
for (FRDGPass* PassToEnd : ResourcePass->ResourcesToEnd)
|
|
{
|
|
for (const auto& PassState : PassToEnd->TextureStates)
|
|
{
|
|
EndResourceRHI(ExecutePassHandle, PassState.Texture, PassState.ReferenceCount);
|
|
}
|
|
|
|
for (const auto& PassState : PassToEnd->BufferStates)
|
|
{
|
|
EndResourceRHI(ExecutePassHandle, PassState.Buffer, PassState.ReferenceCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::CollectPassBarriers(FRDGPass* Pass, FRDGPassHandle PassHandle)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(ConditionalDebugBreak(RDG_BREAKPOINT_PASS_COMPILE, BuilderName.GetTCHAR(), Pass->GetName()));
|
|
|
|
for (const auto& PassState : Pass->TextureStates)
|
|
{
|
|
FRDGTextureRef Texture = PassState.Texture;
|
|
AddTransition(PassHandle, Texture, PassState.MergeState);
|
|
Texture->bCulled = false;
|
|
|
|
IF_RDG_ENABLE_TRACE(Trace.AddTexturePassDependency(Texture, Pass));
|
|
}
|
|
|
|
for (const auto& PassState : Pass->BufferStates)
|
|
{
|
|
FRDGBufferRef Buffer = PassState.Buffer;
|
|
AddTransition(PassHandle, Buffer, *PassState.MergeState);
|
|
Buffer->bCulled = false;
|
|
|
|
IF_RDG_ENABLE_TRACE(Trace.AddBufferPassDependency(Buffer, Pass));
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::CreatePassBarriers()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FRDGBuilder::CreatePassBarriers);
|
|
|
|
for (FRDGBarrierBatchBegin* BarrierBatchBegin : TransitionCreateQueue)
|
|
{
|
|
BarrierBatchBegin->CreateTransition();
|
|
}
|
|
TransitionCreateQueue.Reset();
|
|
}
|
|
|
|
void FRDGBuilder::AddEpilogueTransition(FRDGTextureRef Texture)
|
|
{
|
|
if (!Texture->bLastOwner || Texture->bCulled || Texture->bFinalizedAccess)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();
|
|
|
|
FRDGSubresourceState ScratchSubresourceState;
|
|
EBarrierLocation BarrierLocation = EBarrierLocation::Prologue;
|
|
|
|
// Texture is using the RHI transient allocator. Transition it back to Discard in the final pass it is used.
|
|
if (Texture->bTransient)
|
|
{
|
|
ScratchSubresourceState.SetPass(ERHIPipeline::Graphics, ClampToPrologue(Texture->LastPass));
|
|
ScratchSubresourceState.Access = ERHIAccess::Discard;
|
|
InitAsWholeResource(ScratchTextureState, &ScratchSubresourceState);
|
|
|
|
BarrierLocation = EBarrierLocation::Epilogue;
|
|
}
|
|
// A known final state means extraction from the graph (or an external texture).
|
|
else if(Texture->AccessFinal != ERHIAccess::Unknown)
|
|
{
|
|
ScratchSubresourceState.SetPass(ERHIPipeline::Graphics, EpiloguePassHandle);
|
|
ScratchSubresourceState.Access = Texture->AccessFinal;
|
|
InitAsWholeResource(ScratchTextureState, &ScratchSubresourceState);
|
|
}
|
|
// Lifetime is within the graph, but a pass may have left the resource in an async compute state. We cannot
|
|
// release the pooled texture back to the pool until we transition back to the graphics pipe.
|
|
else if (Texture->bUsedByAsyncComputePass)
|
|
{
|
|
FRDGTextureSubresourceState& TextureState = Texture->GetState();
|
|
ScratchTextureState.SetNumUninitialized(TextureState.Num(), false);
|
|
|
|
for (uint32 Index = 0, Count = ScratchTextureState.Num(); Index < Count; ++Index)
|
|
{
|
|
FRDGSubresourceState SubresourceState = TextureState[Index];
|
|
|
|
// Transition async compute back to the graphics pipe.
|
|
if (SubresourceState.IsUsedBy(ERHIPipeline::AsyncCompute))
|
|
{
|
|
SubresourceState.SetPass(ERHIPipeline::Graphics, EpiloguePassHandle);
|
|
|
|
ScratchTextureState[Index] = AllocSubresource(SubresourceState);
|
|
}
|
|
else
|
|
{
|
|
ScratchTextureState[Index] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
// No need to transition; texture stayed on the graphics pipe and its lifetime stayed within the graph.
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
AddTransition(EpiloguePassHandle, Texture, ScratchTextureState, BarrierLocation);
|
|
ScratchTextureState.Reset();
|
|
}
|
|
|
|
void FRDGBuilder::AddEpilogueTransition(FRDGBufferRef Buffer)
|
|
{
|
|
if (!Buffer->bLastOwner || Buffer->bCulled || Buffer->bFinalizedAccess)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();
|
|
|
|
if (Buffer->bTransient)
|
|
{
|
|
FRDGSubresourceState StateFinal;
|
|
StateFinal.SetPass(ERHIPipeline::Graphics, ClampToPrologue(Buffer->LastPass));
|
|
StateFinal.Access = ERHIAccess::Discard;
|
|
AddTransition(Buffer->LastPass, Buffer, StateFinal, EBarrierLocation::Epilogue);
|
|
}
|
|
else
|
|
{
|
|
ERHIAccess AccessFinal = Buffer->AccessFinal;
|
|
|
|
// Transition async compute back to the graphics pipe.
|
|
if (AccessFinal == ERHIAccess::Unknown)
|
|
{
|
|
const FRDGSubresourceState State = Buffer->GetState();
|
|
|
|
if (State.IsUsedBy(ERHIPipeline::AsyncCompute))
|
|
{
|
|
AccessFinal = State.Access;
|
|
}
|
|
}
|
|
|
|
if (AccessFinal != ERHIAccess::Unknown)
|
|
{
|
|
FRDGSubresourceState StateFinal;
|
|
StateFinal.SetPass(ERHIPipeline::Graphics, EpiloguePassHandle);
|
|
StateFinal.Access = AccessFinal;
|
|
AddTransition(EpiloguePassHandle, Buffer, StateFinal);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::AddTransition(FRDGPassHandle PassHandle, FRDGTextureRef Texture, const FRDGTextureTransientSubresourceStateIndirect& StateAfter, EBarrierLocation BarrierLocation)
|
|
{
|
|
const FRDGTextureSubresourceRange WholeRange = Texture->GetSubresourceRange();
|
|
const FRDGTextureSubresourceLayout Layout = Texture->Layout;
|
|
FRDGTextureSubresourceState& StateBefore = Texture->GetState();
|
|
|
|
const auto AddSubresourceTransition = [&] (
|
|
const FRDGSubresourceState& SubresourceStateBefore,
|
|
const FRDGSubresourceState& SubresourceStateAfter,
|
|
FRDGTextureSubresource* Subresource)
|
|
{
|
|
check(SubresourceStateAfter.Access != ERHIAccess::Unknown);
|
|
|
|
if (FRDGSubresourceState::IsTransitionRequired(SubresourceStateBefore, SubresourceStateAfter))
|
|
{
|
|
FRHITransitionInfo Info;
|
|
Info.Texture = Texture->GetRHIUnchecked();
|
|
Info.Type = FRHITransitionInfo::EType::Texture;
|
|
Info.Flags = SubresourceStateAfter.Flags;
|
|
Info.AccessBefore = SubresourceStateBefore.Access;
|
|
Info.AccessAfter = SubresourceStateAfter.Access;
|
|
|
|
if (Info.AccessBefore == ERHIAccess::Discard)
|
|
{
|
|
Info.Flags |= EResourceTransitionFlags::Discard;
|
|
}
|
|
|
|
if (Subresource)
|
|
{
|
|
Info.MipIndex = Subresource->MipIndex;
|
|
Info.ArraySlice = Subresource->ArraySlice;
|
|
Info.PlaneSlice = Subresource->PlaneSlice;
|
|
}
|
|
|
|
AddTransition(Texture, SubresourceStateBefore, SubresourceStateAfter, Info, BarrierLocation);
|
|
}
|
|
|
|
if (Subresource)
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(LogFile.AddTransitionEdge(PassHandle, SubresourceStateBefore, SubresourceStateAfter, Texture, *Subresource));
|
|
}
|
|
else
|
|
{
|
|
IF_RDG_ENABLE_DEBUG(LogFile.AddTransitionEdge(PassHandle, SubresourceStateBefore, SubresourceStateAfter, Texture));
|
|
}
|
|
};
|
|
|
|
const auto MergeSubresourceState = [&] (FRDGSubresourceState& SubresourceStateBefore, const FRDGSubresourceState& SubresourceStateAfter)
|
|
{
|
|
SubresourceStateBefore = SubresourceStateAfter;
|
|
|
|
for (ERHIPipeline Pipeline : GetRHIPipelines())
|
|
{
|
|
if (SubresourceStateBefore.IsUsedBy(Pipeline))
|
|
{
|
|
SubresourceStateBefore.FirstPass[Pipeline] = PassHandle;
|
|
}
|
|
}
|
|
};
|
|
|
|
if (IsWholeResource(StateBefore))
|
|
{
|
|
// 1 -> 1
|
|
if (IsWholeResource(StateAfter))
|
|
{
|
|
if (const FRDGSubresourceState* SubresourceStateAfter = GetWholeResource(StateAfter))
|
|
{
|
|
FRDGSubresourceState& SubresourceStateBefore = GetWholeResource(StateBefore);
|
|
AddSubresourceTransition(SubresourceStateBefore, *SubresourceStateAfter, nullptr);
|
|
MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter);
|
|
}
|
|
}
|
|
// 1 -> N
|
|
else
|
|
{
|
|
const FRDGSubresourceState SubresourceStateBeforeWhole = GetWholeResource(StateBefore);
|
|
InitAsSubresources(StateBefore, Layout, SubresourceStateBeforeWhole);
|
|
WholeRange.EnumerateSubresources([&](FRDGTextureSubresource Subresource)
|
|
{
|
|
if (FRDGSubresourceState* SubresourceStateAfter = GetSubresource(StateAfter, Layout, Subresource))
|
|
{
|
|
AddSubresourceTransition(SubresourceStateBeforeWhole, *SubresourceStateAfter, &Subresource);
|
|
FRDGSubresourceState& SubresourceStateBefore = GetSubresource(StateBefore, Layout, Subresource);
|
|
MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// N -> 1
|
|
if (IsWholeResource(StateAfter))
|
|
{
|
|
if (const FRDGSubresourceState* SubresourceStateAfter = GetWholeResource(StateAfter))
|
|
{
|
|
WholeRange.EnumerateSubresources([&](FRDGTextureSubresource Subresource)
|
|
{
|
|
AddSubresourceTransition(GetSubresource(StateBefore, Layout, Subresource), *SubresourceStateAfter, &Subresource);
|
|
});
|
|
InitAsWholeResource(StateBefore);
|
|
FRDGSubresourceState& SubresourceStateBefore = GetWholeResource(StateBefore);
|
|
MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter);
|
|
}
|
|
}
|
|
// N -> N
|
|
else
|
|
{
|
|
WholeRange.EnumerateSubresources([&](FRDGTextureSubresource Subresource)
|
|
{
|
|
if (FRDGSubresourceState* SubresourceStateAfter = GetSubresource(StateAfter, Layout, Subresource))
|
|
{
|
|
FRDGSubresourceState& SubresourceStateBefore = GetSubresource(StateBefore, Layout, Subresource);
|
|
AddSubresourceTransition(SubresourceStateBefore, *SubresourceStateAfter, &Subresource);
|
|
MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::AddTransition(FRDGPassHandle PassHandle, FRDGBufferRef Buffer, FRDGSubresourceState StateAfter, EBarrierLocation BarrierLocation)
|
|
{
|
|
check(StateAfter.Access != ERHIAccess::Unknown);
|
|
|
|
FRDGSubresourceState& StateBefore = Buffer->GetState();
|
|
|
|
if (FRDGSubresourceState::IsTransitionRequired(StateBefore, StateAfter))
|
|
{
|
|
FRHITransitionInfo Info;
|
|
Info.Resource = Buffer->GetRHIUnchecked();
|
|
Info.Type = FRHITransitionInfo::EType::Buffer;
|
|
Info.Flags = StateAfter.Flags;
|
|
Info.AccessBefore = StateBefore.Access;
|
|
Info.AccessAfter = StateAfter.Access;
|
|
|
|
AddTransition(Buffer, StateBefore, StateAfter, Info, BarrierLocation);
|
|
}
|
|
|
|
IF_RDG_ENABLE_DEBUG(LogFile.AddTransitionEdge(PassHandle, StateBefore, StateAfter, Buffer));
|
|
StateBefore = StateAfter;
|
|
}
|
|
|
|
void FRDGBuilder::AddTransition(
|
|
FRDGParentResource* Resource,
|
|
FRDGSubresourceState StateBefore,
|
|
FRDGSubresourceState StateAfter,
|
|
const FRHITransitionInfo& TransitionInfo,
|
|
EBarrierLocation BarrierLocation)
|
|
{
|
|
const ERHIPipeline Graphics = ERHIPipeline::Graphics;
|
|
const ERHIPipeline AsyncCompute = ERHIPipeline::AsyncCompute;
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
StateBefore.Validate();
|
|
StateAfter.Validate();
|
|
#endif
|
|
|
|
if (IsImmediateMode())
|
|
{
|
|
// Immediate mode simply enqueues the barrier into the 'after' pass. Everything is on the graphics pipe.
|
|
AddToBarriers(StateAfter.FirstPass[Graphics], BarrierLocation, [&](FRDGBarrierBatchBegin& Barriers)
|
|
{
|
|
Barriers.AddTransition(Resource, TransitionInfo);
|
|
});
|
|
return;
|
|
}
|
|
|
|
StateBefore.LastPass = ClampToPrologue(StateBefore.LastPass);
|
|
|
|
ERHIPipeline PipelinesBefore = StateBefore.GetPipelines();
|
|
ERHIPipeline PipelinesAfter = StateAfter.GetPipelines();
|
|
|
|
// This may be the first use of the resource in the graph, so we assign the prologue as the previous pass.
|
|
if (PipelinesBefore == ERHIPipeline::None)
|
|
{
|
|
StateBefore.SetPass(Graphics, GetProloguePassHandle());
|
|
PipelinesBefore = Graphics;
|
|
}
|
|
|
|
check(PipelinesBefore != ERHIPipeline::None && PipelinesAfter != ERHIPipeline::None);
|
|
checkf(StateBefore.GetLastPass() <= StateAfter.GetFirstPass(), TEXT("Submitted a state for '%s' that begins before our previous state has ended."), Resource->Name);
|
|
check(!Resource->bTransient || StateBefore.GetLastPass() >= Resource->FirstPass);
|
|
|
|
const FRDGPassHandlesByPipeline& PassesBefore = StateBefore.LastPass;
|
|
const FRDGPassHandlesByPipeline& PassesAfter = StateAfter.FirstPass;
|
|
|
|
// 1-to-1 or 1-to-N pipe transition.
|
|
if (PipelinesBefore != ERHIPipeline::All)
|
|
{
|
|
const FRDGPassHandle BeginPassHandle = StateBefore.GetLastPass();
|
|
const FRDGPassHandle FirstEndPassHandle = StateAfter.GetFirstPass();
|
|
|
|
FRDGPass* BeginPass = nullptr;
|
|
FRDGBarrierBatchBegin* BarriersToBegin = nullptr;
|
|
|
|
// Issue the begin in the epilogue of the begin pass if the barrier is being split across multiple passes or the barrier end is in the epilogue.
|
|
if (BeginPassHandle < FirstEndPassHandle || BarrierLocation == EBarrierLocation::Epilogue)
|
|
{
|
|
BeginPass = GetEpilogueBarrierPass(BeginPassHandle);
|
|
BarriersToBegin = &BeginPass->GetEpilogueBarriersToBeginFor(Allocator, TransitionCreateQueue, PipelinesAfter);
|
|
}
|
|
// This is an immediate prologue transition in the same pass. Issue the begin in the prologue.
|
|
else
|
|
{
|
|
checkf(PipelinesAfter == ERHIPipeline::Graphics,
|
|
TEXT("Attempted to queue an immediate async pipe transition for %s. Pipelines: %s. Async transitions must be split."),
|
|
Resource->Name, *GetRHIPipelineName(PipelinesAfter));
|
|
|
|
BeginPass = GetPrologueBarrierPass(BeginPassHandle);
|
|
BarriersToBegin = &BeginPass->GetPrologueBarriersToBegin(Allocator, TransitionCreateQueue);
|
|
}
|
|
|
|
BarriersToBegin->AddTransition(Resource, TransitionInfo);
|
|
|
|
for (ERHIPipeline Pipeline : GetRHIPipelines())
|
|
{
|
|
/** If doing a 1-to-N transition and this is the same pipe as the begin, we end it immediately afterwards in the epilogue
|
|
* of the begin pass. This is because we can't guarantee that the other pipeline won't join back before the end. This can
|
|
* happen if the forking async compute pass joins back to graphics (via another independent transition) before the current
|
|
* graphics transition is ended.
|
|
*
|
|
* Async Compute Pipe: EndA BeginB
|
|
* / \
|
|
* Graphics Pipe: BeginA EndB EndA
|
|
*
|
|
* A is our 1-to-N transition and B is a future transition of the same resource that we haven't evaluated yet. Instead, the
|
|
* same pipe End is performed in the epilogue of the begin pass, which removes the spit barrier but simplifies the tracking:
|
|
*
|
|
* Async Compute Pipe: EndA BeginB
|
|
* / \
|
|
* Graphics Pipe: BeginA EndA EndB
|
|
*/
|
|
if ((PipelinesBefore == Pipeline && PipelinesAfter == ERHIPipeline::All))
|
|
{
|
|
AddToEpilogueBarriersToEnd(BeginPassHandle, *BarriersToBegin);
|
|
}
|
|
else if (EnumHasAnyFlags(PipelinesAfter, Pipeline))
|
|
{
|
|
if (BarrierLocation == EBarrierLocation::Prologue)
|
|
{
|
|
AddToPrologueBarriersToEnd(PassesAfter[Pipeline], *BarriersToBegin);
|
|
}
|
|
else
|
|
{
|
|
AddToEpilogueBarriersToEnd(PassesAfter[Pipeline], *BarriersToBegin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// N-to-1 or N-to-N transition.
|
|
else
|
|
{
|
|
checkf(StateBefore.GetLastPass() != StateAfter.GetFirstPass() || BarrierLocation == EBarrierLocation::Epilogue,
|
|
TEXT("Attempted to queue a transition for resource '%s' from '%s' to '%s', but previous and next passes are the same on one pipe."),
|
|
Resource->Name, *GetRHIPipelineName(PipelinesBefore), *GetRHIPipelineName(PipelinesAfter));
|
|
|
|
FRDGBarrierBatchBeginId Id;
|
|
Id.PipelinesAfter = PipelinesAfter;
|
|
for (ERHIPipeline Pipeline : GetRHIPipelines())
|
|
{
|
|
Id.Passes[Pipeline] = GetEpilogueBarrierPassHandle(PassesBefore[Pipeline]);
|
|
}
|
|
|
|
FRDGBarrierBatchBegin*& BarriersToBegin = BarrierBatchMap.FindOrAdd(Id);
|
|
|
|
if (!BarriersToBegin)
|
|
{
|
|
FRDGPassesByPipeline BarrierBatchPasses;
|
|
BarrierBatchPasses[Graphics] = Passes[Id.Passes[Graphics]];
|
|
BarrierBatchPasses[AsyncCompute] = Passes[Id.Passes[AsyncCompute]];
|
|
|
|
BarriersToBegin = Allocator.AllocNoDestruct<FRDGBarrierBatchBegin>(PipelinesBefore, PipelinesAfter, GetEpilogueBarriersToBeginDebugName(PipelinesAfter), BarrierBatchPasses);
|
|
TransitionCreateQueue.Emplace(BarriersToBegin);
|
|
|
|
for (FRDGPass* Pass : BarrierBatchPasses)
|
|
{
|
|
Pass->SharedEpilogueBarriersToBegin.Add(BarriersToBegin);
|
|
}
|
|
}
|
|
|
|
BarriersToBegin->AddTransition(Resource, TransitionInfo);
|
|
|
|
for (ERHIPipeline Pipeline : GetRHIPipelines())
|
|
{
|
|
if (EnumHasAnyFlags(PipelinesAfter, Pipeline))
|
|
{
|
|
if (BarrierLocation == EBarrierLocation::Prologue)
|
|
{
|
|
AddToPrologueBarriersToEnd(PassesAfter[Pipeline], *BarriersToBegin);
|
|
}
|
|
else
|
|
{
|
|
AddToEpilogueBarriersToEnd(PassesAfter[Pipeline], *BarriersToBegin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGTextureRef Texture)
|
|
{
|
|
check(Texture);
|
|
|
|
if (Texture->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(Texture->ReferenceCount > 0 || Texture->bExternal || IsImmediateMode());
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
{
|
|
FRDGPass* Pass = Passes[PassHandle];
|
|
if (!Pass->bFirstTextureAllocated)
|
|
{
|
|
GRenderTargetPool.AddPhaseEvent(Pass->GetName());
|
|
Pass->bFirstTextureAllocated = 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (IsTransient(Texture))
|
|
{
|
|
if (IRHITransientResourceAllocator* AllocatorRHI = TransientResourceAllocator.GetOrCreate())
|
|
{
|
|
if (FRHITransientTexture* TransientTexture = AllocatorRHI->CreateTexture(Texture->Desc, Texture->Name))
|
|
{
|
|
Texture->SetRHI(TransientTexture, Allocator);
|
|
|
|
check(GetPrologueBarrierPassHandle(PassHandle) == PassHandle);
|
|
|
|
FRDGSubresourceState InitialState;
|
|
InitialState.SetPass(ERHIPipeline::Graphics, PassHandle);
|
|
InitialState.Access = ERHIAccess::Discard;
|
|
InitAsWholeResource(Texture->GetState(), InitialState);
|
|
|
|
AddToPrologueBarriers(PassHandle, [&](FRDGBarrierBatchBegin& Barriers)
|
|
{
|
|
Barriers.AddAlias(Texture, FRHITransientAliasingInfo::Acquire(Texture->GetRHIUnchecked(), TransientTexture->GetAliasingOverlaps()));
|
|
});
|
|
|
|
#if STATS
|
|
GRDGStatTransientTextureCount++;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Texture->bTransient)
|
|
{
|
|
Texture->SetRHI(GRenderTargetPool.FindFreeElementForRDG(RHICmdList, Texture->Desc, Texture->Name));
|
|
}
|
|
|
|
Texture->FirstPass = PassHandle;
|
|
|
|
check(Texture->HasRHI());
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGTextureSRVRef SRV)
|
|
{
|
|
check(SRV);
|
|
|
|
if (SRV->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FRDGTextureRef Texture = SRV->Desc.Texture;
|
|
FRHITexture* TextureRHI = Texture->GetRHIUnchecked();
|
|
check(TextureRHI);
|
|
|
|
SRV->ResourceRHI = Texture->ViewCache->GetOrCreateSRV(TextureRHI, SRV->Desc);
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGTextureUAVRef UAV)
|
|
{
|
|
check(UAV);
|
|
|
|
if (UAV->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FRDGTextureRef Texture = UAV->Desc.Texture;
|
|
FRHITexture* TextureRHI = Texture->GetRHIUnchecked();
|
|
check(TextureRHI);
|
|
|
|
UAV->ResourceRHI = Texture->ViewCache->GetOrCreateUAV(TextureRHI, UAV->Desc);
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGBufferRef Buffer)
|
|
{
|
|
check(Buffer);
|
|
|
|
if (Buffer->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(Buffer->ReferenceCount > 0 || Buffer->bExternal || IsImmediateMode());
|
|
|
|
// If transient then create the resource on the transient allocator. External or extracted resource can't be transient because of lifetime tracking issues.
|
|
if (IsTransient(Buffer))
|
|
{
|
|
if (IRHITransientResourceAllocator* AllocatorRHI = TransientResourceAllocator.GetOrCreate())
|
|
{
|
|
if (FRHITransientBuffer* TransientBuffer = AllocatorRHI->CreateBuffer(Translate(Buffer->Desc), Buffer->Name))
|
|
{
|
|
Buffer->SetRHI(TransientBuffer, Allocator);
|
|
|
|
check(GetPrologueBarrierPassHandle(PassHandle) == PassHandle);
|
|
|
|
FRDGSubresourceState& InitialState = Buffer->GetState();
|
|
InitialState.SetPass(ERHIPipeline::Graphics, PassHandle);
|
|
InitialState.Access = ERHIAccess::Discard;
|
|
|
|
AddToPrologueBarriers(PassHandle, [&](FRDGBarrierBatchBegin& Barriers)
|
|
{
|
|
Barriers.AddAlias(Buffer, FRHITransientAliasingInfo::Acquire(Buffer->GetRHIUnchecked(), TransientBuffer->GetAliasingOverlaps()));
|
|
});
|
|
|
|
#if STATS
|
|
GRDGStatTransientBufferCount++;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Buffer->bTransient)
|
|
{
|
|
Buffer->SetRHI(GRenderGraphResourcePool.FindFreeBufferInternal(RHICmdList, Buffer->Desc, Buffer->Name));
|
|
}
|
|
|
|
Buffer->FirstPass = PassHandle;
|
|
|
|
check(Buffer->HasRHI());
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGBufferSRVRef SRV)
|
|
{
|
|
check(SRV);
|
|
|
|
if (SRV->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FRDGBufferRef Buffer = SRV->Desc.Buffer;
|
|
FRHIBuffer* BufferRHI = Buffer->GetRHIUnchecked();
|
|
check(BufferRHI);
|
|
|
|
FRHIBufferSRVCreateInfo SRVCreateInfo = SRV->Desc;
|
|
|
|
if (Buffer->Desc.UnderlyingType == FRDGBufferDesc::EUnderlyingType::StructuredBuffer)
|
|
{
|
|
// RDG allows structured buffer views to be typed, but the view creation logic requires that it
|
|
// be unknown (as do platform APIs -- structured buffers are not typed). This could be validated
|
|
// at the high level but the current API makes it confusing. For now, it's considered a no-op.
|
|
SRVCreateInfo.Format = PF_Unknown;
|
|
}
|
|
|
|
SRV->ResourceRHI = Buffer->ViewCache->GetOrCreateSRV(BufferRHI, SRVCreateInfo);
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGBufferUAV* UAV)
|
|
{
|
|
check(UAV);
|
|
|
|
if (UAV->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FRDGBufferRef Buffer = UAV->Desc.Buffer;
|
|
check(Buffer);
|
|
|
|
FRHIBufferUAVCreateInfo UAVCreateInfo = UAV->Desc;
|
|
|
|
if (Buffer->Desc.UnderlyingType == FRDGBufferDesc::EUnderlyingType::StructuredBuffer)
|
|
{
|
|
// RDG allows structured buffer views to be typed, but the view creation logic requires that it
|
|
// be unknown (as do platform APIs -- structured buffers are not typed). This could be validated
|
|
// at the high level but the current API makes it confusing. For now, it's considered a no-op.
|
|
UAVCreateInfo.Format = PF_Unknown;
|
|
}
|
|
|
|
UAV->ResourceRHI = Buffer->ViewCache->GetOrCreateUAV(Buffer->GetRHIUnchecked(), UAVCreateInfo);
|
|
}
|
|
|
|
void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGView* View)
|
|
{
|
|
if (View->HasRHI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (View->Type)
|
|
{
|
|
case ERDGViewType::TextureUAV:
|
|
BeginResourceRHI(PassHandle, static_cast<FRDGTextureUAV*>(View));
|
|
break;
|
|
case ERDGViewType::TextureSRV:
|
|
BeginResourceRHI(PassHandle, static_cast<FRDGTextureSRV*>(View));
|
|
break;
|
|
case ERDGViewType::BufferUAV:
|
|
BeginResourceRHI(PassHandle, static_cast<FRDGBufferUAV*>(View));
|
|
break;
|
|
case ERDGViewType::BufferSRV:
|
|
BeginResourceRHI(PassHandle, static_cast<FRDGBufferSRV*>(View));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::EndResourceRHI(FRDGPassHandle PassHandle, FRDGTextureRef Texture, uint32 ReferenceCount)
|
|
{
|
|
check(Texture);
|
|
check(Texture->ReferenceCount >= ReferenceCount || IsImmediateMode());
|
|
Texture->ReferenceCount -= ReferenceCount;
|
|
|
|
if (Texture->ReferenceCount == 0)
|
|
{
|
|
if (Texture->bTransient)
|
|
{
|
|
TransientResourceAllocator->DeallocateMemory(Texture->TransientTexture);
|
|
|
|
AddToEpilogueBarriers(PassHandle, [&](FRDGBarrierBatchBegin& Barriers)
|
|
{
|
|
Barriers.AddAlias(Texture, FRHITransientAliasingInfo::Discard(Texture->GetRHIUnchecked()));
|
|
});
|
|
}
|
|
else if (Texture->PooledRenderTarget && Texture->PooledRenderTarget->IsTracked())
|
|
{
|
|
// This releases the reference without invoking a virtual function call.
|
|
TRefCountPtr<FPooledRenderTarget>(MoveTemp(Texture->Allocation));
|
|
}
|
|
|
|
Texture->LastPass = PassHandle;
|
|
}
|
|
}
|
|
|
|
void FRDGBuilder::EndResourceRHI(FRDGPassHandle PassHandle, FRDGBufferRef Buffer, uint32 ReferenceCount)
|
|
{
|
|
check(Buffer);
|
|
check(Buffer->ReferenceCount >= ReferenceCount || IsImmediateMode());
|
|
Buffer->ReferenceCount -= ReferenceCount;
|
|
|
|
if (Buffer->ReferenceCount == 0)
|
|
{
|
|
if (Buffer->bTransient)
|
|
{
|
|
TransientResourceAllocator->DeallocateMemory(Buffer->TransientBuffer);
|
|
|
|
AddToEpilogueBarriers(PassHandle, [&](FRDGBarrierBatchBegin& Barriers)
|
|
{
|
|
Barriers.AddAlias(Buffer, FRHITransientAliasingInfo::Discard(Buffer->GetRHIUnchecked()));
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Buffer->Allocation = nullptr;
|
|
}
|
|
|
|
Buffer->LastPass = PassHandle;
|
|
}
|
|
}
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
|
|
void FRDGBuilder::VisualizePassOutputs(const FRDGPass* Pass)
|
|
{
|
|
#if SUPPORTS_VISUALIZE_TEXTURE
|
|
if (bInDebugPassScope)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Pass->GetParameters().EnumerateTextures([&](FRDGParameter Parameter)
|
|
{
|
|
switch (Parameter.GetType())
|
|
{
|
|
case UBMT_RDG_TEXTURE_ACCESS:
|
|
{
|
|
if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess())
|
|
{
|
|
if (TextureAccess.GetAccess() == ERHIAccess::UAVCompute ||
|
|
TextureAccess.GetAccess() == ERHIAccess::UAVGraphics ||
|
|
TextureAccess.GetAccess() == ERHIAccess::RTV)
|
|
{
|
|
if (TOptional<uint32> CaptureId = GVisualizeTexture.ShouldCapture(TextureAccess->Name, /* MipIndex = */ 0))
|
|
{
|
|
GVisualizeTexture.CreateContentCapturePass(*this, TextureAccess.GetTexture(), *CaptureId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_UAV:
|
|
{
|
|
if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV())
|
|
{
|
|
FRDGTextureRef Texture = UAV->Desc.Texture;
|
|
if (TOptional<uint32> CaptureId = GVisualizeTexture.ShouldCapture(Texture->Name, UAV->Desc.MipLevel))
|
|
{
|
|
GVisualizeTexture.CreateContentCapturePass(*this, Texture, *CaptureId);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RENDER_TARGET_BINDING_SLOTS:
|
|
{
|
|
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
|
|
|
|
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
|
|
{
|
|
FRDGTextureRef Texture = RenderTarget.GetTexture();
|
|
if (TOptional<uint32> CaptureId = GVisualizeTexture.ShouldCapture(Texture->Name, RenderTarget.GetMipIndex()))
|
|
{
|
|
GVisualizeTexture.CreateContentCapturePass(*this, Texture, *CaptureId);
|
|
}
|
|
});
|
|
|
|
const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil;
|
|
|
|
if (FRDGTextureRef Texture = DepthStencil.GetTexture())
|
|
{
|
|
const bool bHasStoreAction = DepthStencil.GetDepthStencilAccess().IsAnyWrite();
|
|
|
|
if (bHasStoreAction)
|
|
{
|
|
const uint32 MipIndex = 0;
|
|
if (TOptional<uint32> CaptureId = GVisualizeTexture.ShouldCapture(Texture->Name, MipIndex))
|
|
{
|
|
GVisualizeTexture.CreateContentCapturePass(*this, Texture, *CaptureId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void FRDGBuilder::ClobberPassOutputs(const FRDGPass* Pass)
|
|
{
|
|
if (!GRDGClobberResources)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bInDebugPassScope)
|
|
{
|
|
return;
|
|
}
|
|
bInDebugPassScope = true;
|
|
|
|
RDG_EVENT_SCOPE(*this, "RDG ClobberResources");
|
|
|
|
const FLinearColor ClobberColor = GetClobberColor();
|
|
|
|
Pass->GetParameters().Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
switch (Parameter.GetType())
|
|
{
|
|
case UBMT_RDG_BUFFER_UAV:
|
|
{
|
|
if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV())
|
|
{
|
|
FRDGBufferRef Buffer = UAV->GetParent();
|
|
|
|
if (UserValidation.TryMarkForClobber(Buffer))
|
|
{
|
|
AddClearUAVPass(*this, UAV, GetClobberBufferValue());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS:
|
|
{
|
|
if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess())
|
|
{
|
|
FRDGTextureRef Texture = TextureAccess.GetTexture();
|
|
|
|
if (UserValidation.TryMarkForClobber(Texture))
|
|
{
|
|
if (EnumHasAnyFlags(TextureAccess.GetAccess(), ERHIAccess::UAVMask))
|
|
{
|
|
for (int32 MipLevel = 0; MipLevel < Texture->Desc.NumMips; MipLevel++)
|
|
{
|
|
AddClearUAVPass(*this, CreateUAV(FRDGTextureUAVDesc(Texture, MipLevel)), ClobberColor);
|
|
}
|
|
}
|
|
else if (EnumHasAnyFlags(TextureAccess.GetAccess(), ERHIAccess::RTV))
|
|
{
|
|
AddClearRenderTargetPass(*this, Texture, ClobberColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_UAV:
|
|
{
|
|
if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV())
|
|
{
|
|
FRDGTextureRef Texture = UAV->GetParent();
|
|
|
|
if (UserValidation.TryMarkForClobber(Texture))
|
|
{
|
|
if (Texture->Desc.NumMips == 1)
|
|
{
|
|
AddClearUAVPass(*this, UAV, ClobberColor);
|
|
}
|
|
else
|
|
{
|
|
for (int32 MipLevel = 0; MipLevel < Texture->Desc.NumMips; MipLevel++)
|
|
{
|
|
AddClearUAVPass(*this, CreateUAV(FRDGTextureUAVDesc(Texture, MipLevel)), ClobberColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RENDER_TARGET_BINDING_SLOTS:
|
|
{
|
|
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
|
|
|
|
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
|
|
{
|
|
FRDGTextureRef Texture = RenderTarget.GetTexture();
|
|
|
|
if (UserValidation.TryMarkForClobber(Texture))
|
|
{
|
|
AddClearRenderTargetPass(*this, Texture, ClobberColor);
|
|
}
|
|
});
|
|
|
|
if (FRDGTextureRef Texture = RenderTargets.DepthStencil.GetTexture())
|
|
{
|
|
if (UserValidation.TryMarkForClobber(Texture))
|
|
{
|
|
AddClearDepthStencilPass(*this, Texture, true, GetClobberDepth(), true, GetClobberStencil());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
bInDebugPassScope = false;
|
|
}
|
|
|
|
#endif //! RDG_ENABLE_DEBUG
|