365 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 剖析虚幻渲染体系11- RDG
date: 2024-02-04 21:42:54
excerpt:
tags:
rating: ⭐
---
# 前言
原文地址:https://www.cnblogs.com/timlly/p/15217090.html
# 概念
- FRDGAllocator简单的C++对象分配器, 用MemStack分配器追踪和销毁物体。
- FComputeShaderUtils
- Dispatch()派发ComputeShader到RHI命令列表携带其参数。
- DispatchIndirect()派发非直接的ComputeShader到RHI命令列表,,携带其参数。
- AddPass():派发计算着色器到**RenderGraphBuilder** 携带其参数。
- ClearUAV()清理UAV。
- AddCopyTexturePass()增加拷贝纹理Pass。
- AddCopyToResolveTargetPass()增加拷贝到解析目标的Pass。
- AddEnqueueCopyPass()增加回读纹理的Pass。
- AddPass()无参数的Pass增加。
- AddBeginUAVOverlapPass()/AddEndUAVOverlapPass() 其它特殊Pass。
- FRDGResourceRDG资源并不是直接用RHI资源而是包裹了RHI资源引用然后针对不同类型的资源各自封装且增加了额外的信息。
- FRDGUniformBuffer、TRDGUniformBuffer
- FRDGParentResource一种由图跟踪分配生命周期的渲染图资源。可能有引用它的子资源(例如视图)
- FRDGView
- FRDGBuffer、FRDGBufferSRV、FRDGBufferUAV
- FRDGViewableResource一种由图跟踪分配生命周期的RPGResource。可能有引用它的子资源
- FRDGTexture
- FRDGView
- FRDGUnorderedAccessView、FRDGTextureUAV
- FRDGShaderResourceView、FRDGTextureSRV
- FRDGTextureDesc创建渲染纹理的描述信息。
- FRDGPooledTexture贴图池里的贴图资源。
- FRDGPooledBuffer池化的缓冲区。
- FRHITransition用于表示RHI中挂起的资源转换的**不透明表面材质**数据结构。
- FRDGBarrierBatchRDG屏障批。
- FRDGBarrierBatchBegin
- FRDGBarrierBatchEnd
- FRDGPass
- TRDGLambdaPass
- FRDGSentinelPass哨兵Pass用于开始/结束。
- [[#FRDGBuilder]]
## RDG基础类型
```c++
enum class ERDGBuilderFlags
{
None = 0,
/** Allows the builder to parallelize execution of passes. Without this flag, all passes execute on the render thread. */
AllowParallelExecute = 1 << 0
};
/** Flags to annotate a pass with when calling AddPass. */
enum class ERDGPassFlags : uint16
{
/** Pass doesn't have any inputs or outputs tracked by the graph. This may only be used by the parameterless AddPass function. */
None = 0,
/** Pass uses rasterization on the graphics pipe. */
Raster = 1 << 0,
/** Pass uses compute on the graphics pipe. */
Compute = 1 << 1,
/** Pass uses compute on the async compute pipe. */
AsyncCompute = 1 << 2,
/** Pass uses copy commands on the graphics pipe. */
Copy = 1 << 3,
/** Pass (and its producers) will never be culled. Necessary if outputs cannot be tracked by the graph. */
NeverCull = 1 << 4,
/** Render pass begin / end is skipped and left to the user. Only valid when combined with 'Raster'. Disables render pass merging for the pass. */
SkipRenderPass = 1 << 5,
/** Pass will never have its render pass merged with other passes. */
NeverMerge = 1 << 6,
/** Pass will never run off the render thread. */
NeverParallel = 1 << 7,
ParallelTranslate = 1 << 8,
/** Pass uses copy commands but writes to a staging resource. */
Readback = Copy | NeverCull
};
/** Flags to annotate a render graph buffer. */
enum class ERDGBufferFlags : uint8
{
None = 0,
/** Tag the buffer to survive through frame, that is important for multi GPU alternate frame rendering. */
MultiFrame = 1 << 0,
/** The buffer is ignored by RDG tracking and will never be transitioned. Use the flag when registering a buffer with no writable GPU flags.
* Write access is not allowed for the duration of the graph. This flag is intended as an optimization to cull out tracking of read-only
* buffers that are used frequently throughout the graph. Note that it's the user's responsibility to ensure the resource is in the correct
* readable state for use with RDG passes, as RDG does not know the exact state of the resource.
*/
SkipTracking = 1 << 1,
/** When set, RDG will perform its first barrier without splitting. Practically, this means the resource is left in its initial state
* until the first pass it's used within the graph. Without this flag, the resource is split-transitioned at the start of the graph.
*/
ForceImmediateFirstBarrier = 1 << 2,
};
/** Flags to annotate a render graph texture. */
enum class ERDGTextureFlags : uint8
{
None = 0,
/** Tag the texture to survive through frame, that is important for multi GPU alternate frame rendering. */
MultiFrame = 1 << 0,
/** The buffer is ignored by RDG tracking and will never be transitioned. Use the flag when registering a buffer with no writable GPU flags.
* Write access is not allowed for the duration of the graph. This flag is intended as an optimization to cull out tracking of read-only
* buffers that are used frequently throughout the graph. Note that it's the user's responsibility to ensure the resource is in the correct
* readable state for use with RDG passes, as RDG does not know the exact state of the resource.
*/
SkipTracking = 1 << 1,
/** When set, RDG will perform its first barrier without splitting. Practically, this means the resource is left in its initial state
* until the first pass it's used within the graph. Without this flag, the resource is split-transitioned at the start of the graph.
*/
ForceImmediateFirstBarrier = 1 << 2,
/** Prevents metadata decompression on this texture. */
MaintainCompression = 1 << 3,
};
ENUM_CLASS_FLAGS(ERDGTextureFlags);
/** Flags to annotate a view with when calling CreateUAV. */
enum class ERDGUnorderedAccessViewFlags : uint8
{
None = 0,
// The view will not perform UAV barriers between consecutive usage.
SkipBarrier = 1 << 0
};
ENUM_CLASS_FLAGS(ERDGUnorderedAccessViewFlags);
/** The set of concrete parent resource types. */
enum class ERDGViewableResourceType : uint8
{
Texture,
Buffer,
MAX
};
/** The set of concrete view types. */
enum class ERDGViewType : uint8
{
TextureUAV,
TextureSRV,
BufferUAV,
BufferSRV,
MAX
};
inline ERDGViewableResourceType GetParentType(ERDGViewType ViewType)
{
switch (ViewType)
{
case ERDGViewType::TextureUAV:
case ERDGViewType::TextureSRV:
return ERDGViewableResourceType::Texture;
case ERDGViewType::BufferUAV:
case ERDGViewType::BufferSRV:
return ERDGViewableResourceType::Buffer;
}
checkNoEntry();
return ERDGViewableResourceType::MAX;
}
enum class ERDGResourceExtractionFlags : uint8
{
None = 0,
// Allows the resource to remain transient. Only use this flag if you intend to register the resource back
// into the graph and release the reference. This should not be used if the resource is cached for a long
// period of time.
AllowTransient = 1,
};
enum class ERDGInitialDataFlags : uint8
{
/** Specifies the default behavior, which is to make a copy of the initial data for replay when
* the graph is executed. The user does not need to preserve lifetime of the data pointer.
*/
None = 0,
/** Specifies that the user will maintain ownership of the data until the graph is executed. The
* upload pass will only use a reference to store the data. Use caution with this flag since graph
* execution is deferred! Useful to avoid the copy if the initial data lifetime is guaranteed to
* outlive the graph.
*/
NoCopy = 1 << 0
};
enum class ERDGPooledBufferAlignment : uint8
{
// The buffer size is not aligned.
None,
// The buffer size is aligned up to the next page size.
Page,
// The buffer size is aligned up to the next power of two.
PowerOfTwo
};
/** Returns the equivalent parent resource type for a view type. */
inline ERDGViewableResourceType GetViewableResourceType(ERDGViewType ViewType)
{
switch (ViewType)
{
case ERDGViewType::TextureUAV:
case ERDGViewType::TextureSRV:
return ERDGViewableResourceType::Texture;
case ERDGViewType::BufferUAV:
case ERDGViewType::BufferSRV:
return ERDGViewableResourceType::Buffer;
default:
checkNoEntry();
return ERDGViewableResourceType::MAX;
}
}
using ERDGTextureMetaDataAccess = ERHITextureMetaDataAccess;
/** Returns the associated FRHITransitionInfo plane index. */
inline int32 GetResourceTransitionPlaneForMetadataAccess(ERDGTextureMetaDataAccess Metadata)
{
switch (Metadata)
{
case ERDGTextureMetaDataAccess::CompressedSurface:
case ERDGTextureMetaDataAccess::HTile:
case ERDGTextureMetaDataAccess::Depth:
return FRHITransitionInfo::kDepthPlaneSlice;
case ERDGTextureMetaDataAccess::Stencil:
return FRHITransitionInfo::kStencilPlaneSlice;
default:
return 0;
}
}
```
## FRDGBuilder
FRDGBuilder是RDG体系的心脏和发动机也是个大管家负责收集渲染Pass和参数编译Pass、数据处理资源依赖裁剪和优化各类数据还有提供执行接口。
重要函数:
- FindExternalTexture():查找外部纹理, 若找不到返回null.
- RegisterExternalTexture()注册外部池内RT到RDG, 以便RDG追踪之. 池内RT可能包含两种RHI纹理: MSAA和非MSAA。
- RegisterExternalBuffer()注册外部缓冲区到RDG, 以便RDG追踪之.
- 资源创建接口:
- CreateTexture()
- CreateBuffer()
- CreateUAV()
- CreateSRV()
- CreateUniformBuffer()
- 分配内存, 内存由RDG管理生命周期。
- Alloc()
- AllocPOD()
- AllocObject()
- AllocParameters()
- AddPass()
- FRDGPassRef AddPass(FRDGEventName&& Name, const ParameterStructType* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda); 增加有参数的LambdaPass。
- FRDGPassRef AddPass(FRDGEventName&& Name, const FShaderParametersMetadata* ParametersMetadata, const void* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda); 增加带有实时生成结构体的LambdaPass
- FRDGPassRef AddPass(FRDGEventName&& Name, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);增加没有参数的LambdaPass
- QueueTextureExtraction()在Builder执行末期, 提取池内纹理到指定的指针. 对于RDG创建的资源, 这将延长GPU资源的生命周期直到执行指针被填充. 如果指定纹理将转换为AccessFinal状态, 否则将转换为kDefaultAccessFinal状态。
- QueueBufferExtraction()在Builder执行末期, 提取缓冲区到指定的指针。
- PreallocateTexture()/PreallocateBuffer():预分配资源. 只对RDG创建的资源, 会强制立即分配底层池内资源, 有效地将其推广到外部资源. 这将增加内存压力但允许使用GetPooled{Texture, Buffer}查询池中的资源. 主要用于增量地将代码移植到RDG.
- GetPooledTexture()/GetPooledBuffer():立即获取底层资源, 只允许用于注册或预分配的资源。
- SetTextureAccessFinal()/SetBufferAccessFinal():设置执行之后的状态。
- 变量
- RDG对象注册表
- FRDGPassRegistry Passes;
- FRDGTextureRegistry Textures;
- FRDGBufferRegistry Buffers;
- FRDGViewRegistry Views;
- FRDGUniformBufferRegistry UniformBuffers;
### FRDGBuilder::Compile
RDG编译期间的逻辑非常复杂步骤繁多先后经历构建生产者和消费者的依赖关系确定Pass的裁剪等各类标记调整资源的生命周期裁剪Pass处理Pass的资源转换和屏障处理异步计算Pass的依赖和引用关系查找并建立分叉和合并Pass节点合并所有具体相同渲染目标的光栅化Pass等步骤。
# RDG开发
### 注册外部资源
如果我们已有非RDG创建的资源可以在RDG使用么答案是可以通过FRDGBuilder::RegisterExternalXXX接口可以完成将外部资源注册到RDG系统中。下面以注册纹理为例
```c++
// 在RDG外创建RHI资源.
FRHIResourceCreateInfo CreateInfo;
FTexture2DRHIRef MyRHITexture = RHICreateTexture2D(1024, 768, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);
// 将外部创建的RHI资源注册成RDG资源.
FRDGTextureRef MyExternalRDGTexture = GraphBuilder.RegisterExternalTexture(MyRHITexture);
```
需要注意的是外部注册的资源RDG无法控制和管理其生命周期需要保证RDG使用期间外部资源的生命周期处于正常状态否则将引发异常甚至程序崩溃。
如果想从RDG资源获取RHI资源的实例以下代码可达成
```c++
FRHITexture* MyRHITexture = MyRDGTexture.GetRHI();
```
用图例展示RHI资源和RDG资源之间的转换关系
- FRHIResource =>FRDGBuilder::RegisterExternalXXX =>FRDGResource
- FRDGResource => FRDGResource::GetRHI => FRHIResource
### 提取资源
RDG收集Pass之后并非立即执行而是延迟执行包括资源被延迟创建或分配这就导致了一个问题如果想将渲染后的资源赋值给某个变量无法使用立即模式需要适配延迟执行模式。这种适配延迟执行的资源提取是通过以下接口来实现的
- FRDGBuilder::QueueTextureExtraction
- FRDGBuilder::QueueBufferExtraction
```c++
// 创建RDG纹理.
FRDGTextureRef MyRDGTexture;
FRDGTextureDesc MyTextureDesc = FRDGTextureDesc::Create2D(OutputExtent, HistoryPixelFormat, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_UAV);
MyRDGTexture = GraphBuilder.CreateTexture(MyTextureDesc, "MyRDGTexture", ERDGTextureFlags::MultiFrame);
// 创建UAV并作为Pass的shader参数.
(......)
PassParameters->MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);
(......)
// 增加Pass, 以便渲染图像到MyRDGTextureUAV.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("MyCustomPass", ...), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(8, 8));
// 入队提取资源.
TRefCountPtr<IPooledRenderTarget>* OutputRT;
GraphBuilder.QueueTextureExtraction(MyRDGTexture, &OutputRT);
// 对提取的OutputRT进行后续操作.
(......)
```
# RDG调试
RDG系统存在一些控制台命令其名称和描述如下
|控制台变量|描述|
|---|---|
|**r.RDG.AsyncCompute**|控制异步计算策略0-禁用1-为异步计算Pass启用标记默认2-开启所有使用compute命令列表的计算通道。|
|**r.RDG.Breakpoint**|当满足某些条件时断点到调试器的断点位置。0-禁用1~4-不同的特殊调试模式。|
|**r.RDG.ClobberResources**|在分配时间用指定的清理颜色清除所有渲染目标和纹理/缓冲UAV。用于调试。|
|**r.RDG.CullPasses**|RDG是否开启裁剪无用的Pass。0-禁用1-开启(默认)。|
|**r.RDG.Debug**|允许输出在连接和执行过程中发现的效率低下的警告。|
|**r.RDG.Debug.FlushGPU**|开启每次Pass执行后刷新指令到GPU。当设置(r.RDG.AsyncCompute=0)时禁用异步计算。|
|**r.RDG.Debug.GraphFilter**|将某些调试事件过滤到特定的图中。|
|**r.RDG.Debug.PassFilter**|将某些调试事件过滤到特定的Pass。|
|**r.RDG.Debug.ResourceFilter**|将某些调试事件过滤到特定的资源。|
|**r.RDG.DumpGraph**|将多个可视化日志转储到磁盘。0-禁用1-显示生产者、消费者Pass依赖2-显示资源状态和转换3-显示图形、异步计算的重叠。|
|**r.RDG.ExtendResourceLifetimes**|RDG将把资源生命周期扩展到图的全部长度。会增加内存的占用。|
|**r.RDG.ImmediateMode**|在创建Pass时执行Pass。当在Pass的Lambda中崩溃时连接代码的调用堆栈非常有用。|
|**r.RDG.MergeRenderPasses**|图形将合并相同的、连续的渲染通道到一个单一的渲染通道。0-禁用1-开启(默认)。|
|**r.RDG.OverlapUAVs**|RDG将在需要时重叠UAV的工作。如果禁用UAV屏障总是插入。|
|**r.RDG.TransitionLog**|输出资源转换到控制台。|
|**r.RDG.VerboseCSVStats**|控制RDG的CSV分析统计的详细程度。0-为图形执行生成一个CSV配置文件1-为图形执行的每个阶段生成一个CSV文件。|
除了以上列出的RDG控制台还有一些命令可以显示RDG系统运行过程中的有用信息。
`vis`列出所有有效的纹理,输入之后可能显示如下所示的信息: