## 前言 >RDG = Rendering Dependency Graph RDG主要包含两个重要组件,一个是FRDGBuilder,负责构建Render graph的资源及添加pass等,构建RenderGraph。另一个是FRDGResource,RenderGraph的资源类,所有资源都由它派生。 官方入门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是四字节对齐的; - FVector2D,FIntPoint是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(); 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(); 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, 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, 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, MySRV) SHADER_PARAMETER_RDG_BUFFER_SRV_ARRAY(Buffer, MyArrayOfSRVs, [4]) ``` #### UAV ```c++ SHADER_PARAMETER_UAV(Texture2D, MyUAV) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, MyUAV) SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY(RWBuffer, 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, 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参数比如UnifromBuffer,Texture等。且参数使用" 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(); 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 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 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 TRefCountPtrif 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::GetRHI(); GraphicsPSOInit.BlendState = TStaticBlendState::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); }); ```