概述
RDG(Rendering Dependency Graph),渲染依赖图,是UE4.22开始引进的全新的渲染子系统,是基于有向无环图(Directed Acyclic Graph,DAG)的调度系统,用于执行渲染管线的整帧优化。
传统的图形API(DirectX 11、OpenGL)要求驱动器调用复杂的启发法,以确定何时以及如何在GPU上执行关键的调度操作。例如清空缓存,管理和再使用内存,执行布局转换等等。由于接口存在即时模式特性,因此需要复杂的记录和状态跟踪才能处理各种极端情况。这些情况最终会对性能产生负面影响,阻碍并行。
现代的图形API(DirectX 12、Vulkan和Metal 2)与传统图形API不同,将低级GPU管理的负担转移到应用程序。这使得应用程序可以利用渲染管线的高级情境来驱动调度,从而提高性能并且简化渲染堆栈。
RDG的理念是,不在GPU上立即执行Pass,而是先收集所有需要渲染的Pass,并构建Pass之间的依赖关系,然后在编译中并对进行各类裁剪和优化,最后统一执行。
依赖性图表数据结构的整帧认知与现代图形API的能力相结合,使RDG能够在后台执行复杂的调度任务,RDG包括以下功能:
- 安排异步计算栅栏的执行
- 以最佳生命周期和内存别名分配临时资源
- 使用拆分屏障转换子资源,在GPU上隐藏延迟并改善重叠
- 命令列表并行记录
- 剔除图中未使用的资源和通道
- 验证API的使用和资源依赖关系
- 在RDG Insights中实现图结构和内存生命周期的可视化
此外,RDG利用依赖性图表在通道设置期间提供丰富的验证,对影响功能和性能的问题进行自动捕捉,从而改进开发流程。
RDG机制
基本概念
RDG基础类型和接口主要集中于RenderGraphUtils.h和RenderGraphDefinitions.h之中,其中包括RDG Pass类型、Buffer标记、纹理标记、UAV标记、RDG对象分配器等等。
RDG资源
RDG资源并不是直接用RHI资源,而是包裹了RHI资源引用,然后针对不同类型的资源各自封装,且增加了额外的信息。
RDG系统对基本上所有的RHI资源进行了封装和包裹,以便进一步控制、管理RHI资源,精准控制它们的生命周期、引用关系及调试信息等,进一步优化、裁剪,提升渲染性能。
RDG Pass
Pass是RDG体系中最核心的类型之一,涉及了消费者、生产者、转换依赖、各类资源状态等等数据和处理。RDG的Pass有以下几种类型:
RDG Pass和渲染Pass并非一一对应关系,有可能多个合并成一个渲染Pass。
RDGBuilder
RDGBuilder是RDG体系的核心,负责收集渲染Pass和参数,编译Pass、数据,处理资源依赖,裁剪和优化各类数据,还有提供执行接口。作为RDG系统的驱动器,FRDGBuilder负责存储数据、处理状态转换、自动管理资源生命周期和屏障、裁剪无效资源,以及收集、编译、执行Pass,提取纹理或缓冲等等功能。
AddPass
AddPass会根据传入的参数构建一个RDG Pass的实例,然后设置该Pass的纹理和缓冲区数据,接着用内部设置Pass的依赖Pass等句柄,如果是立即模式,会重定向纹理和缓冲区的Merge状态成Pass状态,并且直接执行。
// Engine\Source\Runtime\RenderCore\Public\RenderGraphBuilder.inl
template <typename ParameterStructType, typename ExecuteLambdaType>
FRDGPassRef FRDGBuilder::AddPass(
FRDGEventName&& Name,
const ParameterStructType* ParameterStruct,
ERDGPassFlags Flags,
ExecuteLambdaType&& ExecuteLambda)
{
#if !USE_NULL_RHI
return AddPassInternal(Forward<FRDGEventName>(Name), ParameterStructType::FTypeInfo::GetStructMetadata(), ParameterStruct, Flags, Forward<ExecuteLambdaType>(ExecuteLambda));
#else
checkNoEntry();
return nullptr;
#endif // !USE_NULL_RHI
}
template <typename ParameterStructType, typename ExecuteLambdaType>
FRDGPassRef FRDGBuilder::AddPassInternal(
FRDGEventName&& Name,
const FShaderParametersMetadata* ParametersMetadata,
const ParameterStructType* ParameterStruct,
ERDGPassFlags Flags,
ExecuteLambdaType&& ExecuteLambda)
{
using LambdaPassType = TRDGLambdaPass<ParameterStructType, ExecuteLambdaType>;
(...)
// 分配RDG Pass实例
FRDGPass* Pass = Allocators.Root.AllocNoDestruct<LambdaPassType>(
MoveTemp(Name),
ParametersMetadata,
ParameterStruct,
OverridePassFlags(Name.GetTCHAR(), Flags),
MoveTemp(ExecuteLambda));
IF_RDG_ENABLE_DEBUG(ClobberPassOutputs(Pass));
// 加入Pass Registry
Passes.Insert(Pass);
// 设置Pass
SetupParameterPass(Pass);
return Pass;
}
SetupParameterPass
// Engine\Source\Runtime\RenderCore\Public\RenderGraphBuilder.inl
FRDGPass* FRDGBuilder::SetupParameterPass(FRDGPass* Pass)
{
(...)
// 获取Pass数据,
SetupPassInternals(Pass);
// ----处理纹理状态、缓冲区状态----
if (ParallelSetup.bEnabled)
{
MarkResourcesAsProduced(Pass);
AsyncSetupQueue.Push(FAsyncSetupOp::SetupPassResources(Pass));
}
else
{
SetupPassResources(Pass);
}
// 如果为立即模式,则重定向纹理和缓冲区的Merge状态为Pass状态,并直接执行
SetupAuxiliaryPasses(Pass);
return Pass;
}
// Engine\Source\Runtime\RenderCore\Private\RenderGraphBuilder.cpp
void FRDGBuilder::SetupPassResources(FRDGPass* Pass)
{
// 设置Pass的资源
(...)
if (ParallelSetup.bEnabled)
{
// 设置依赖关系
SetupPassDependencies(Pass);
for (FRDGPass::FExternalAccessOp Op : Pass->ExternalAccessOps)
{
Op.Resource->AccessModeState.ActiveMode = Op.Mode;
}
}
}
void FRDGBuilder::SetupPassDependencies(FRDGPass* Pass)
{
bool bIsCullRootProducer = false;
// 遍历当前Pass的所有贴图
for (auto& PassState : Pass->TextureStates)
{
FRDGTextureRef Texture = PassState.Texture;
auto& LastProducers = Texture->LastProducers;
Texture->ReferenceCount += PassState.ReferenceCount;
// 遍历当前贴图的所有Producer
for (uint32 Index = 0, Count = LastProducers.Num(); Index < Count; ++Index)
{
const FRDGSubresourceState* SubresourceState = PassState.State[Index];
if (!SubresourceState)
{
continue;
}
FRDGProducerState ProducerState;
ProducerState.Pass = Pass;
ProducerState.Access = SubresourceState->Access;
ProducerState.NoUAVBarrierHandle = SubresourceState->NoUAVBarrierFilter.GetUniqueHandle();
// 添加依赖
bIsCullRootProducer |= AddCullingDependency(LastProducers[Index], ProducerState, Pass->Pipeline) && Texture->IsCullRoot();
}
}
// 遍历当前Pass的所有BufferState
for (auto& PassState : Pass->BufferStates)
{
FRDGBufferRef Buffer = PassState.Buffer;
const FRDGSubresourceState& SubresourceState = PassState.State;
Buffer->ReferenceCount += PassState.ReferenceCount;
FRDGProducerState ProducerState;
ProducerState.Pass = Pass;
ProducerState.Access = SubresourceState.Access;
ProducerState.NoUAVBarrierHandle = SubresourceState.NoUAVBarrierFilter.GetUniqueHandle();
// 添加依赖
bIsCullRootProducer |= AddCullingDependency(Buffer->LastProducer, ProducerState, Pass->Pipeline) && Buffer->IsCullRoot();
}
// 依赖剔除
const bool bCullPasses = GRDGCullPasses > 0;
Pass->bCulled = bCullPasses;
if (bCullPasses && (bIsCullRootProducer || Pass->bHasExternalOutputs || EnumHasAnyFlags(Pass->Flags, ERDGPassFlags::NeverCull)))
{
CullPassStack.Emplace(Pass);
FlushCullStack();
}
}
Execute
// Engine\Source\Runtime\RenderCore\Private\RenderGraphBuilder.cpp
void FRDGBuilder::Execute()
{
(...)
// 在编译之前,在图的末尾创建epilogue pass
SetupEmptyPass(EpiloguePass = Passes.Allocate<FRDGSentinelPass>(Allocators.Root, RDG_EVENT_NAME("Graph Epilogue")));
const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle();
const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();
(...)
// 非立即模式
if (!IsImmediateMode())
{
SubmitParallelSetupTasks();
BeginFlushResourcesRHI();
WaitForParallelSetupTasks();
(...)
Compile();
(...)
}
else
{
SubmitBufferUploads(RHICmdList);
FinalizeResources();
}
SubmitParallelSetupTasks();
EndFlushResourcesRHI();
WaitForParallelSetupTasks();
(...)
// 执行Pass
if (!IsImmediateMode())
{
SCOPED_NAMED_EVENT_TEXT("FRDGBuilder::ExecutePasses", FColor::Magenta);
SCOPE_CYCLE_COUNTER(STAT_RDG_ExecuteTime);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RDG_Execute);
for (FRDGPassHandle PassHandle = ProloguePassHandle; PassHandle <= EpiloguePassHandle; ++PassHandle)
{
(...)
ExecutePass(Pass, RHICmdList);
}
}
else
{
ExecutePass(EpiloguePass, RHICmdList);
}
RHICmdList.SetStaticUniformBuffers({});
(...)
// 执行纹理提取和缓冲区提取
for (const FExtractedTexture& ExtractedTexture : ExtractedTextures)
{
check(ExtractedTexture.Texture->RenderTarget);
*ExtractedTexture.PooledTexture = ExtractedTexture.Texture->RenderTarget;
}
for (const FExtractedBuffer& ExtractedBuffer : ExtractedBuffers)
{
check(ExtractedBuffer.Buffer->PooledBuffer);
*ExtractedBuffer.PooledBuffer = ExtractedBuffer.Buffer->PooledBuffer;
}
(...)
}
void FRDGBuilder::ExecutePass(FRDGPass* Pass, FRHIComputeCommandList& RHICmdListPass)
{
{
SCOPED_GPU_MASK(RHICmdListPass, Pass->GPUMask);
{
FRHICommandListScopedPipeline Scope(RHICmdListPass, Pass->Pipeline);
#if 0 // Disabled by default to reduce memory usage in Insights.
SCOPED_NAMED_EVENT_TCHAR(Pass->GetName(), FColor::Magenta);
#endif
#if RDG_CPU_SCOPES
if (!Pass->bParallelExecute)
{
Pass->CPUScopeOps.Execute();
}
#endif
IF_RDG_ENABLE_DEBUG(ConditionalDebugBreak(RDG_BREAKPOINT_PASS_EXECUTE, BuilderName.GetTCHAR(), Pass->GetName()));
Pass->GPUScopeOpsPrologue.Execute(RHICmdListPass);
// 执行pass的顺序: 1.prologue -> 2.pass主体 -> 3.epilogue
// 整个过程使用指定Pass上的命令列表执行
ExecutePassPrologue(RHICmdListPass, Pass);
#if RDG_DUMP_RESOURCES_AT_EACH_DRAW
BeginPassDump(Pass);
#endif
Pass->Execute(RHICmdListPass);
#if RDG_DUMP_RESOURCES_AT_EACH_DRAW
EndPassDump(Pass);
#endif
ExecutePassEpilogue(RHICmdListPass, Pass);
Pass->GPUScopeOpsEpilogue.Execute(RHICmdListPass);
}
}
// 异步计算完成, 则立即派发
if (!Pass->bParallelExecute && Pass->bDispatchAfterExecute)
{
if (Pass->Pipeline == ERHIPipeline::Graphics)
{
RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
}
}
// 如果是调试模式且非异步计算,则提交命令并刷新到GPU, 然后等待GPU处理完成
if (GRDGDebugFlushGPU)
{
check(!GRDGAsyncCompute && !ParallelExecute.bEnabled);
RHICmdList.SubmitCommandsAndFlushGPU();
RHICmdList.BlockUntilGPUIdle();
}
}
总结
RDG是在RHI之上的又一层封装,以实现RHI命令延迟执行。其将Pipeline分为设置阶段与执行阶段,在设置阶段收集Lambda、记录各个Pass的资源,并建立依赖关系,进行Pass剔除、合并具有相同RenderTarget的Pass等操作,最终将统一执行Lambda。
FRDGBuilder的执行周期可划分为4个阶段:收集Pass、编译Pass、执行Pass、清理
收集Pass阶段:主要是收集渲染模块的所有能够产生RHICommand的Pass(Lambda),非立即模式下收集之后不立即执行,将其延迟执行。AddPass的步骤是先创建FRDGPass的实例,并加入到Pass列表,随后执行SetupPass。SetupPass中主要处理纹理和缓冲区的状态、引用、依赖和标记等。
编译Pass阶段:主要包含构建生产者和消费者的依赖关系,确定Pass的裁剪等各类标记,调整资源的生命周期,裁剪Pass,处理Pass的资源转换和屏障,处理异步计算Pass的依赖和引用关系,查找并建立分叉和合并Pass节点,合并所有具体相同渲染目标的光栅化Pass等步骤。
执行Pass阶段:首先会执行编译,再根据编译结果执行所有符合条件的Pass。执行单个Pass时依次执行前序、主体和后续,相当于执行命令队列的BeginRenderPass、执行Pass主体(Lambda)渲染代码、EndRenderPass。执行Pass主体时过程简洁,就是调用该Pass的Lambda实例。
清理阶段:将清理或重置FRDGBuilder实例内的所有数据和内存。