找回密码
 立即注册
查看: 841|回复: 9

RenderDependencyGraph学习笔记(一)——概念整理

[复制链接]
发表于 2021-4-30 13:08 | 显示全部楼层 |阅读模式
前言

RDG = Rendering Dependency Graph
RDG主要包含两个重要组件,一个是FRDGBuilder,负责构建Render graph的资源及添加pass等,构建RenderGraph。另一个是FRDGResource,RenderGraph的资源类,所有资源都由它派生。
官方入门ppt:
因为加载速度较慢,所以我搬运到了有道云笔记:
另外我还推荐看:
本文也将引用上文中的若干内容。
注意
    本文为了便于理解,把诸如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 TRefCountPtrif 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);
});

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-4-30 13:16 | 显示全部楼层
这么好的文自然是我来捧场拉,写的很好了,很多时候做的魔改都没有考虑性能,正好最近在研究UBO CacheFriendly那一套 很有收获啊
发表于 2021-4-30 13:23 | 显示全部楼层
感谢大佬鼓励
发表于 2021-4-30 13:27 | 显示全部楼层
SHADER PARAMETER STRUCT INCLUDE 在shader中展开的方式和
SHADER PARAMETER STRUCT 不一样,详见官方PPT59页
发表于 2021-4-30 13:33 | 显示全部楼层
[干杯]
发表于 2021-4-30 13:42 | 显示全部楼层
感谢提醒
发表于 2021-4-30 13:49 | 显示全部楼层
你好,我是unreal的初学者,在官方论坛看到有人提起这个东西,想请教下如果我的需求是在postprocess中想添加需要用到multiple pass和multiple rt的后期效果,我是不是只能求助于这个系统?需要改动到引擎代码吗?还是引擎有提供回调函数调用我写好的接口?除了这个功能还有别的方法能实现这样的需求吗?请教下大佬
[爱心]
发表于 2021-4-30 13:50 | 显示全部楼层
目前ue4.26的渲染管线采用这个框架编写,所以想要添加多pass就必须要学会这个框架,当然没有类似unity3d的shaderlab所以这个需求要修改引擎
发表于 2021-4-30 13:55 | 显示全部楼层
一般的需求ue4内置的后处理材质都能做
发表于 2021-4-30 14:02 | 显示全部楼层
好的,谢谢大佬,感谢指教!网上资源很少,我再琢磨下
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-26 00:53 , Processed in 0.094402 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表