BlueRoseNote/03-UnrealEngine/Rendering/RenderingPipeline/RenderDependencyGraph学习笔记(一)——概念整理.md

325 lines
18 KiB
Markdown
Raw Normal View History

2023-06-29 11:55:02 +08:00
## 前言
>RDG = Rendering Dependency Graph
RDG主要包含两个重要组件一个是FRDGBuilder负责构建Render graph的资源及添加pass等构建RenderGraph。另一个是FRDGResourceRenderGraph的资源类所有资源都由它派生。
官方入门ppt:https://epicgames.ent.box.com/s/ul1h44ozs0t2850ug0hrohlzm53kxwrz
因为加载速度较慢所以我搬运到了有道云笔记http://note.youdao.com/noteshare?id=a7e2856ad141f44f6b48db6e95419920&sub=E5276AAD6DAA40409586C0552B8E163A
另外我还推荐看:
https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
https://zhuanlan.zhihu.com/p/101149903
**本文也将引用上文中的若干内容。**
**注意**
1. 本文为了便于理解把诸如typedef FShaderDrawSymbols SHADER;这种类型别名都改成原本的类型名了。
2. 本文属于本人边学边写的学习笔记,不可避免地会有错误。如有发现还请指出,尽请见谅。
## 推荐用于学习的代码
PostProcessTestImage.cpp
GpuDebugRendering.cpp
- ShaderPrint.cpp 具体实现
- ShaderPrint.h 绘制函数声明
- ShaderPrintParameters.h 渲染变量与资源声明
本人推荐这个ShaderPrint简单的同时又可以进行扩展以此实现更多debug方式。本文中没有说明出处的代码默认来自于ShaderPrint中。
## 相关头文件
你可以查看RenderGraph.h查看官方对于RDG系统的介绍。
- #include "RenderGraphDefinitions.h"
- #include "RenderGraphResources.h"
- #include "RenderGraphPass.h"
- #include "RenderGraphBuilder.h"
- #include "RenderGraphUtils.h"
- #include "ShaderParameterStruct.h"
- #include "ShaderParameterMacros.h"
## 资源声明与绑定
### Struct的字节对齐问题
在声明Struct时需要注意一下字节对齐的问题。这里我引用一段papalqi博客中的文章
>当然在设置中我们可能还需要注意一些简单的问题。由于unreal 采用16字节自动对齐的原则所以在编写代码时我们实际上对任何成员的顺序是需要注意的。例如下图中的顺序调整。宏系统的另一个特性是着色器数据的自动对齐。Unreal引擎使用与平台无关的数据对齐规则来实现着色器的可移植性。
主要规则是每个成员都是按照其大小的下一个幂进行对齐的但前提是大于4个字节。例如
- 指针是8字节对齐的即使在32位平台上也是如此
- 浮点、uint32、int32是四字节对齐的
- FVector2DFIntPoint是8字节对齐的
- FVector和FVector 4是16字节对齐的。
>作者(Author)papalqi 链接(URL)https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
**所以我们需要根据位宽对变量与资源进行排序:**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRDG-%E5%8F%98%E9%87%8F%E4%BD%8D%E5%AE%BD%E8%87%AA%E5%8A%A8%E5%AF%B9%E9%BD%90.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRDG-%E5%8F%98%E9%87%8F%E4%BD%8D%E5%AE%BD%E6%89%8B%E5%8A%A8%E5%AF%B9%E9%BD%90.png)
进行手动位宽对齐,以减少额外的内存与带宽占用。
### 资源的初始化与绑定
其中资源分为使用RDG托管与非托管的。下面是ShaderPrint的部分代码
```
// Initialize graph managed resources
// Symbols buffer contains Count + 1 elements. The first element is only used as a counter.
FRDGBufferRef SymbolBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(ShaderPrintItem), GetMaxSymbolCount() + 1), TEXT("ShaderPrintSymbolBuffer"));
FRDGBufferRef IndirectDispatchArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4), TEXT("ShaderPrintIndirectDispatchArgs"));
FRDGBufferRef IndirectDrawArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(5), TEXT("ShaderPrintIndirectDrawArgs"));
// Non graph managed resources
FUniformBufferRef UniformBuffer = CreateUniformBuffer(View);
FShaderResourceViewRHIRef ValuesBuffer = View.ShaderPrintValueBuffer.SRV;
FTextureRHIRef FontTexture = GEngine->MiniFontTexture != nullptr ? GEngine->MiniFontTexture->Resource->TextureRHI : GSystemTextures.BlackDummy->GetRenderTargetItem().ShaderResourceTexture;;
```
**使用RDG托管的资源**会使用GraphBuilder.CreateBuffer()创建一个FRDGBufferDesc在之后会使用GraphBuilder.CreateUAV()、CreateSRV()、CreateTexture()创建具体资源时,作为第一个形参。
**不使用RDG托管的资源**都只需要定义、计算后直接绑定即可Uniform的创建请见下文。
```
FShaderBuildIndirectDispatchArgsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FShaderBuildIndirectDispatchArgsCS::FParameters>();
PassParameters->UniformBufferParameters = UniformBuffer;
PassParameters->ValuesBuffer = ValuesBuffer;
PassParameters->RWSymbolsBuffer = GraphBuilder.CreateUAV(SymbolBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->RWIndirectDispatchArgsBuffer = GraphBuilder.CreateUAV(IndirectDispatchArgsBuffer, EPixelFormat::PF_R32_UINT);
```
GpuDebugRendering.cpp中的代码可以看得出是直接绑定的。
```
//bIsBehindDepth是一个之前设置的bool变量
ShaderDrawVSPSParameters* PassParameters = GraphBuilder.AllocParameters<ShaderDrawVSPSParameters>();
PassParameters->ShaderDrawPSParameters.ColorScale = bIsBehindDepth ? 0.4f : 1.0f;
```
### 声明资源宏
这里介绍几种常用的。这些宏位于Runtime\RenderCore\Public\ShaderParameterMacros.h中。另外还有一组RDG版本的宏这些宏声明的资源需要先使用 GraphBuilder.CreateBuffer()初始化资源后再调用对应GraphBuilder.CreateXXXX()完成创建,。这个头文件中还包含了若干案例代码,为了文章的简洁性这里就不贴了。
#### 常规变量
```c++
SHADER_PARAMETER(float, MyScalar)
SHADER_PARAMETER(FMatrix, MyMatrix)
SHADER_PARAMETER_RDG_BUFFER(Buffer<float4>, MyBuffer)
```
#### 结构体
在ShaderPrint中全局的结构体的声明都写在头文件中。在自己定义的GlobalShader调用`SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)`来设置全局结构体的指针进行引用。使用结构体的Shader需要使用`SHADER_USE_PARAMETER_STRUCT(FMyShaderClassCS, FGlobalShader);`进行标记。
```c++
//声明一个全局的结构体变量
EGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, RENDERER_API)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, "MyShaderBindingName");
```
```c++
//声明一个全局结构体的引用
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FGlobalViewParameters,)
SHADER_PARAMETER(FVector4, ViewSizeAndInvSize)
// ...
END_GLOBAL_SHADER_PARAMETER_STRUCT()
BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)
END_SHADER_PARAMETER_STRUCT()
```
```c++
//为使用结构化着色器参数API的着色器类打上标签。
class FMyShaderClassCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMyShaderClassCS);
SHADER_USE_PARAMETER_STRUCT(FMyShaderClassCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters)
SHADER_PARAMETER(FMatrix, ViewToClip)
//...
END_SHADER_PARAMETER_STRUCT()
};
```
#### 数组
```c++
SHADER_PARAMETER_ARRAY(float, MyScalarArray, [8])
SHADER_PARAMETER_ARRAY(FMatrix, MyMatrixArray, [2])
SHADER_PARAMETER_RDG_BUFFER_ARRAY(Buffer<float4>, MyArrayOfBuffers, [4])
```
#### Texture
```c++
SHADER_PARAMETER_TEXTURE(Texture2D, MyTexture)
SHADER_PARAMETER_TEXTURE_ARRAY(Texture2D, MyArrayOfTextures, [8])
```
#### SRV
```c++
SHADER_PARAMETER_SRV(Texture2D, MySRV)
SHADER_PARAMETER_SRV_ARRAY(Texture2D, MyArrayOfSRVs, [8])
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, MySRV)
SHADER_PARAMETER_RDG_BUFFER_SRV_ARRAY(Buffer<float4>, MyArrayOfSRVs, [4])
```
#### UAV
```c++
SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY(RWBuffer<float4>, MyArrayOfUAVs, [4])
```
#### Sampler
```c++
SHADER_PARAMETER_SAMPLER(SamplerState, MySampler)
SHADER_PARAMETER_SAMPLER_ARRAY(SamplerState, MyArrayOfSamplers, [8])
```
#### 不太懂是干什么的
```c++
//Adds a render graph tracked buffer upload.
//Example:
SHADER_PARAMETER_RDG_BUFFER_UPLOAD(Buffer<float4>, MyBuffer)
```
```c++
BEGIN_SHADER_PARAMETER_STRUCT(ShaderDrawVSPSParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugVS::FParameters, ShaderDrawVSParameters)
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugPS::FParameters, ShaderDrawPSParameters)
END_SHADER_PARAMETER_STRUCT()
```
## 设置变量
首先我们要了解清楚给一个Pass设置变量的步骤
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesPassParameterSetup.png)
>当增加一个RGPass它必须带有Shader参数可以是任何Shader参数比如UnifromBufferTexture等。且参数使用" GraphBuilder.AllocaParameter "来分配保留所有参数的结构体因为Lambda执行被延迟确保了正确的生命周期。参数采用宏的形式来声明。且参数结构体的声明最好的方法是内联直接在每个Pass的ShaderClass内声明好结构。
>首先得在Shader里使用宏SHADER_USE_PARAMETERSTRUCT(FYouShader, ShaderType)设置Shader需要使用Prameter。然后需要实现一个FParameter的宏包裹的结构体里面声明该Pass需要用到的所有参数参数基本上都是靠新的RDG系列宏来声明。需要注意一点的是对于UnifromBuffer需要使用StructRef来引用一层可以理解为Parameter结构体里面还有一个结构体。
```c++
FShaderDrawSymbols::FParameters* PassParameters = GraphBuilder.AllocParameters<FShaderDrawSymbols::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction);
PassParameters->UniformBufferParameters = UniformBuffer;
PassParameters->MiniFontTexture = FontTexture;
PassParameters->SymbolsBuffer = GraphBuilder.CreateSRV(SymbolBuffer);
PassParameters->IndirectDrawArgsBuffer = IndirectDrawArgsBuffer;
```
对于代码中UniformBuffer变量ShaderPrint是这么设置的
```
FUniformBufferRef UniformBuffer = CreateUniformBuffer(View);
```
```
typedef TUniformBufferRef<FUniformBufferParameters> FUniformBufferRef;
// Fill the uniform buffer parameters
void SetUniformBufferParameters(FViewInfo const& View, FUniformBufferParameters& OutParameters)
{
const float FontWidth = (float)FMath::Max(CVarFontSize.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().X, 1);
const float FontHeight = (float)FMath::Max(CVarFontSize.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().Y, 1);
const float SpaceWidth = (float)FMath::Max(CVarFontSpacingX.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().X, 1);
const float SpaceHeight = (float)FMath::Max(CVarFontSpacingY.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().Y, 1);
OutParameters.FontSize = FVector4(FontWidth, FontHeight, SpaceWidth + FontWidth, SpaceHeight + FontHeight);
OutParameters.MaxValueCount = GetMaxValueCount();
OutParameters.MaxSymbolCount = GetMaxSymbolCount();
}
// Return a uniform buffer with values filled and with single frame lifetime
FUniformBufferRef CreateUniformBuffer(FViewInfo const& View)
{
FUniformBufferParameters Parameters;
SetUniformBufferParameters(View, Parameters);
return FUniformBufferRef::CreateUniformBufferImmediate(Parameters, UniformBuffer_SingleFrame);
}
```
看得出创建步骤为:
1. 创建之前使用宏声明的结构体的对象。
2. 对结构体中变量进行赋值。
3. 包一层TUniformBufferRef并使用CreateUniformBufferImmediate返回FUniformBufferRef。
4. 绘制函数中对对应命名空间FParameters结构体进行资源绑定。
可以看得出对于Uniform中的普通变量是直接设置的。
### 创建Buffer
调用GraphBuilder.CreateBuffer()即可创建缓存返回一个FRDGBufferRef对象。但CreateBuffer()的第一个形参中FRDGBufferDesc可以使用Desc有好几种CreateIndirectDesc、CreateStructuredDesc、CreateBufferDesc。其中CreateIndirectDesc应该是与RDG的IndirectDraw/Dispatch机制有关。对于结构体可以使用CreateStructuredDesc声明资源时用StructuredBuffer与RWStructuredBuffer宏。使用CreateBufferDesc需要手动计算占用空间与元素个数。代码大致如下
```
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4), TEXT("BurleyIndirectDispatchArgs"));
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(ShaderDrawDebugElement), GetMaxShaderDrawElementCount()), TEXT("ShaderDrawDataBuffer"));
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("HairDebugSampleCounter"));
```
### 绑定SRV与UAV
使用SRV/UAV宏后其对应的Buffer需要使用GraphBuilder.CreateSRV/GraphBuilder.CreateUAV进行绑定。注意这里分为Buffer_UAV与Texture_UAV
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesReadingFromABufferUsingAnSRV.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesBindingUAVsForPixelShaders.png)
**Buffer_UAV**:在创建Buffer后再调用CreateUAV
```
//HairStrandsClusters.cpp中的代码
FRDGBufferRef GlobalRadiusScaleBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(float), ClusterData.ClusterCount), TEXT("HairGlobalRadiusScaleBuffer"));
Parameters->GlobalRadiusScaleBuffer = GraphBuilder.CreateUAV(GlobalRadiusScaleBuffer, PF_R32_FLOAT);
```
**使用Texture2D来绑定SRV与UAV。**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesCreateAUACForTexture.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesCreateASRVForTexture.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesPassParameter.png)
### 绑定RenderTarget
如需使用RenderTarget只要在FParameter结构体声明中加一行RENDER_TARGET_BINDING_SLOTS()即可。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RenderTargetBindingsSlots.png)
之后就可以进行RT的绑定。
**绑定储存颜色数据的RT**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesBindingAColorRenderTarget.png)
颜色数据RT绑定时需要注意数组下标号。
**绑定深度模板缓存**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/BindingDepthStencilTarget.png)
FParameter中的RenderTarget对象为FShadowMapRenderTargets类其地址与类内的容器变量ColorTargets一样所以可以使用这个写法。
```
class FShadowMapRenderTargets
{
public:
TArray<IPooledRenderTarget*, SceneRenderingAllocator> ColorTargets;
IPooledRenderTarget* DepthTarget;
}
```
### 绑定非当前GraphPass的资源
>需要注意的是一个GraphPass内的资源有可能不是由Graph创建的这个时候就需要使用GraphBuilder.RegisterExternalBuffer/Texture来把某个PoolRT或者RHIBuffer转成RDGResource才能使用。同样的吧一个RDGResource转成PoolRT或者RHIBuffer的方法则是GraphBuilder.QueueExternalBuffer/Texture感觉这两对更适合叫ImportResource和ExportResource。如下图。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRegistration.png)
>Check out GRenderTargetPool.CreateUntrackedElement()to get a TRefCountPtr<IPooledRenderTarget>if need to register a different from RHI resource (for instance the very old FRenderTarget)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesExtractionQueries.png)
## AddPass
>GraphBuilder.AddPass()主要用来配置管线状态用于延迟执行。比如使用 FGraphicsPipelineStateInitializer对象来配置PSO并调用RHI的API来进行绘制。或者使用SetComputeSahder()来DispatchCompute。注意此时还不会实际执行绘制而是在所有AddPass完成后调用GraphBuilder.Execute()才实际执行。而且更主要的是SetShaderParameters()也是在这儿做这个函数是UE封装的因为我们的一个Pass只能有一个AllocParameter所以这个东西里面是塞了UnifromBuffer SRV UAV等等各种东西。在以前的流程里面是自己再Shader里封装Shader.SetSRV/UAV/Unifrom等等的函数而现在则只需要吧所有参数塞一起并在GraphPass内设置即可。RDG会自动检测参数的每一个成员和类型自动SetUAV/SRV/Unifrom。
```
GraphBuilder.AddPass(
RDG_EVENT_NAME("DrawSymbols"),
PassParameters,
ERDGPassFlags::Raster,
[VertexShader, PixelShader, PassParameters](FRHICommandListImmediate& RHICmdListImmediate)
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdListImmediate.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdListImmediate, GraphicsPSOInit);
SetShaderParameters(RHICmdListImmediate, VertexShader, VertexShader.GetVertexShader(), *PassParameters);
SetShaderParameters(RHICmdListImmediate, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
RHICmdListImmediate.DrawIndexedPrimitiveIndirect(GTwoTrianglesIndexBuffer.IndexBufferRHI, PassParameters->IndirectDrawArgsBuffer->GetIndirectRHICallBuffer(), 0);
});
```