前言
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
本文也将引用上文中的若干内容。
注意:
- 本文为了便于理解,把诸如typedef FShaderDrawSymbols SHADER;这种类型别名都改成原本的类型名了。
- 本文属于本人边学边写的学习笔记,不可避免地会有错误。如有发现还请指出,尽请见谅。
推荐用于学习的代码
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/
所以我们需要根据位宽对变量与资源进行排序:
进行手动位宽对齐,以减少额外的内存与带宽占用。
资源的初始化与绑定
其中资源分为使用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()完成创建,。这个头文件中还包含了若干案例代码,为了文章的简洁性这里就不贴了。
常规变量
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);
进行标记。
//声明一个全局的结构体变量
EGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, RENDERER_API)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, "MyShaderBindingName");
//声明一个全局结构体的引用
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()
//为使用结构化着色器参数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()
};
数组
SHADER_PARAMETER_ARRAY(float, MyScalarArray, [8])
SHADER_PARAMETER_ARRAY(FMatrix, MyMatrixArray, [2])
SHADER_PARAMETER_RDG_BUFFER_ARRAY(Buffer<float4>, MyArrayOfBuffers, [4])
Texture
SHADER_PARAMETER_TEXTURE(Texture2D, MyTexture)
SHADER_PARAMETER_TEXTURE_ARRAY(Texture2D, MyArrayOfTextures, [8])
SRV
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
SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY(RWBuffer<float4>, MyArrayOfUAVs, [4])
Sampler
SHADER_PARAMETER_SAMPLER(SamplerState, MySampler)
SHADER_PARAMETER_SAMPLER_ARRAY(SamplerState, MyArrayOfSamplers, [8])
不太懂是干什么的
//Adds a render graph tracked buffer upload.
//Example:
SHADER_PARAMETER_RDG_BUFFER_UPLOAD(Buffer<float4>, MyBuffer)
BEGIN_SHADER_PARAMETER_STRUCT(ShaderDrawVSPSParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugVS::FParameters, ShaderDrawVSParameters)
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugPS::FParameters, ShaderDrawPSParameters)
END_SHADER_PARAMETER_STRUCT()
设置变量
首先我们要了解清楚给一个Pass设置变量的步骤:
当增加一个RGPass它必须带有Shader参数,可以是任何Shader参数比如UnifromBuffer,Texture等。且参数使用” GraphBuilder.AllocaParameter “来分配保留所有参数的结构体,因为Lambda执行被延迟确保了正确的生命周期。参数采用宏的形式来声明。且参数结构体的声明最好的方法是内联,直接在每个Pass的ShaderClass内声明好结构。
首先得在Shader里使用宏SHADER_USE_PARAMETERSTRUCT(FYouShader, ShaderType)设置Shader需要使用Prameter。然后需要实现一个FParameter的宏包裹的结构体里面声明该Pass需要用到的所有参数,参数基本上都是靠新的RDG系列宏来声明。需要注意一点的是对于UnifromBuffer需要使用StructRef来引用一层,可以理解为Parameter结构体里面还有一个结构体。
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);
}
看得出创建步骤为:
- 创建之前使用宏声明的结构体的对象。
- 对结构体中变量进行赋值。
- 包一层TUniformBufferRef,并使用CreateUniformBufferImmediate返回FUniformBufferRef。
- 绘制函数中,对对应命名空间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:
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。
绑定RenderTarget
如需使用RenderTarget,只要在FParameter结构体声明中加一行RENDER_TARGET_BINDING_SLOTS()即可。
之后就可以进行RT的绑定。
绑定储存颜色数据的RT
颜色数据RT绑定时需要注意数组下标号。
绑定深度模板缓存
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。如下图。
Check out GRenderTargetPool.CreateUntrackedElement()to get a TRefCountPtr
if need to register a different from RHI resource (for instance the very old FRenderTarget)
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);
});