渲染依赖图(3/4)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMyUniformParameters, UniformBuffer) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT()
void AddPass(FRDGBuilder& GraphBuilder, TShaderMapRef<FShader> PixelShader, FRDGTexture* InputTexture, FRDGTexture* OutputTexture)
{
// 首先创建统一缓冲区。
FMyUniformParameters* UniformParameters = GraphBuilder.AllocParameters<FMyUniformParameters>();
UniformParameters->Texture = InputTexture;
TRDGUniformBuffer<FMyUniformParameters>* UniformBuffer = GraphBuilder.CreateUniformBuffer(UniformParameters);
// 现在构造通道。
FMyPassParameters* PassParameters = GraphBuilder.AllocParameters<FMyPassParameters>();
PassParameters->UniformBuffer = UniformBuffer;
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ELoad);
GraphBuilder.AddPass(
RDG_EVENT_NAME("MyPass"),
PassParameters,
ERDGPassFlags::Raster,
[PassParameters, UniformBuffer, PixelShader](FRHICommandList& RHICmdList)
{
// ... 绑定着色器等
// 你可以在此处访问RHI统一缓冲区!
FRHIUniformBuffer* UniformBufferRHI = UniformBuffer->GetRHI();
// 你也可以访问RDG纹理,它是RHI纹理!
FRHITexture* TextureRHI = (*UniformBuffer)->Texture->GetRHI();
// 你也可以调用相同的SetShaderParameters辅助方法来绑定RDG统一缓冲区。
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetComputeShader(), *PassParameters);
});
}
上传缓冲区
如果RDG资源在执行图之前需要来自CPU的初始数据,则 FRDGBuilder::QueueBufferUpload 方法是最有效的调度方法。RDG在图编译期间一起批量上传,并且可能与其他编译任务重叠进行。
下面的示例代码演示了如何将CPU数据数组上传到RDG缓冲区。
缓冲区上传示例:
FRDGBuffer IndexBuffer = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateUploadDesc(sizeof(uint32), NumIndices),
TEXT("MyIndexBuffer"));
// 针对延迟,使用内部RDG分配器为分配数据数组。
FRDGUploadData Indices(GraphBuilder, NumIndices);
// 分配数据
Indices[0] = // ...;
Indices[1] = // ...;
Indices[NumIndices - 1] = // ...;
// 上传数据
GraphBuilder.QueueBufferUpload(IndexBuffer, Indices, ERDGInitialDataFlags::NoCopy);
Copy full snippet
FRDGBuffer IndexBuffer = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateUploadDesc(sizeof(uint32), NumIndices), TEXT("MyIndexBuffer"));
// 针对延迟,使用内部RDG分配器为分配数据数组。
FRDGUploadData<int32> Indices(GraphBuilder, NumIndices);
// 分配数据
Indices[0] = // ...;
Indices[1] = // ...;
Indices[NumIndices - 1] = // ...;
// 上传数据
GraphBuilder.QueueBufferUpload(IndexBuffer, Indices, ERDGInitialDataFlags::NoCopy);
在RDG中使用上传缓冲区时,请考虑以下事项:
- 请务必使用RDG执行上传。 在一个通道中使用立即命令列表手动锁定/解锁会引入一个同步点并阻止并行执行。
- 上传缓冲区自动标记为非临时。临时资源不支持CPU上传。
请使用最准确的 ERDGInitialDataFlags。
- 如果数据生命周期足以承受图的延迟,请使用
NoCopy。否则,让RDG进行复制。
内存生命周期
在处理内存生命周期时,设置和执行时间轴的拆分需谨慎。一个常见的错误是将内存传递到RDG Lambda中,但图在执行时不能保证该内存仍存在。
为了帮助解决这个问题,RDG包含了自己的线性分配器,具有适当的生命周期保证。API支持不同开销的分配。
对于POD类型,请使用 FRDGBuilder::AllocPOD。
如果C++对象需要析构函数跟踪,请使用 FRDGBuilder::AllocObject。
C++代码示例:
// 不佳!
FMyObject Object;
GraphBuilder.AddPass(..., [&Object] (FRHICommandList&) { /** Object is captured by reference but exists on the stack! 指针无效!*/ });
// 良好
TUniquePtr Object(new FMyObject());
GraphBuilder.AddPass(..., [Object = MoveTemp(Object)] (FRHICommandList&) { /** Object is valid but was expensive to allocate. */ });
// 最佳情况是C++对象(调用析构函数,增加一点开销)
FMyObject* Object = GraphBuilder.AllocObject();
GraphBuilder.AddPass(..., [Object = MoveTemp(Object)] (FRHICommandList&) { /** Object is valid and was cheap to allocate. */ });
// 最佳情况是POD结构体(不会调用析构函数)
FMyObject* Object = GraphBuilder.AllocPOD();
...
// 对于原始内存。
void* Memory = GraphBuilder.Alloc(SizeInBytes, AlignInBytes);
// 对于RDG通道参数——可能会执行额外的跟踪。
FMyPassParameters* PassParameters = GraphBuilder.AllocParameters();
Copy full snippet
// 不佳! FMyObject Object; GraphBuilder.AddPass(..., [&Object] (FRHICommandList&) { /* Object is captured by reference but exists on the stack! 指针无效!/ });
// 良好
TUniquePtr<FMyObject> Object(new FMyObject());
GraphBuilder.AddPass(..., [Object = MoveTemp(Object)] (FRHICommandList&) { /** Object is valid but was expensive to allocate. */ });
// 最佳情况是C++对象(调用析构函数,增加一点开销)
FMyObject* Object = GraphBuilder.AllocObject<FMyObject>();
GraphBuilder.AddPass(..., [Object = MoveTemp(Object)] (FRHICommandList&) { /** Object is valid and was cheap to allocate. */ });
// 最佳情况是POD结构体(不会调用析构函数)
FMyObject* Object = GraphBuilder.AllocPOD<FMyObject>();
...
// 对于原始内存。
void* Memory = GraphBuilder.Alloc(SizeInBytes, AlignInBytes);
// 对于RDG通道参数——可能会执行额外的跟踪。
FMyPassParameters* PassParameters = GraphBuilder.AllocParameters<FMyPassParameters>();
所有分配的内存都将持续存在,直到图构建器实例被销毁。它使用线性分配器并且非常快。
性能分析
RDG支持引擎中各种分析器的范围定义:
- 使用
RDG_EVENT_SCOPE在通道周围添加GPU配置文件范围。这些将由RenderDoc等外部分析器以及RDG Insights使用。 - 使用
RDG_GPU_STAT_SCOPE为stat gpu命令添加新范围。 - 使用
RDG_CSV_STAT_EXCLUSIVE_SCOPE为CSV分析器添加新范围。
RDG范围将构建器作为输入,并适当考虑单独的设置和执行时间轴。
约定
以下是使用RDG编写代码时使用的常规编码约定。遵循这些约定可确保整个渲染器的一致性。
- 采用句点分隔为资源构建命名空间。 例如,
TSR.History.ScreenPercentage。这简化了RDG Insights和其他工具中的名称过滤。
命名图生成器实例:GraphBuilder
在着色器实例上内联命名着色器参数:FParameters
使用 RDG_EVENT_SCOPE 作为通道的命名空间。
尽可能使用 RenderGraphUtils.h 或 ScreenPass.h 中的辅助函数。
