What is RDG and Why Does It Exist?什么是 RDG?它为何存在?

The Render Dependency Graph (RDG) is UE's frame-level task graph for GPU work. Before RDG, every render pass had to manually issue RHI resource barriers, manage resource lifetimes, and schedule async compute — error-prone, hard to maintain, and impossible to globally optimize. RDG replaces this with a declarative model: you describe what each pass reads and writes, and RDG figures out when to transition resources, whether to merge render passes, and how to overlap async compute — automatically.

Render Dependency Graph(RDG)是 UE 的帧级 GPU 工作任务图。 RDG 出现之前,每个渲染通道都必须手动发出 RHI 资源屏障、管理资源生命周期、调度异步计算—— 这极易出错、难以维护,且无法进行全局优化。 RDG 用声明式模型替代了这一切:你描述每个通道读取和写入什么, RDG 自动确定何时转换资源、是否合并渲染通道、以及如何重叠异步计算。

Concern关注点 Without RDG无 RDG With RDG有 RDG
Resource barriers资源屏障Manually called at each pass boundary每个通道边界手动调用Automatically computed from declared read/write access从声明的读写访问自动计算
Resource lifetime资源生命周期Manually allocated before use, freed after使用前手动分配,结束后释放Auto-allocated between first and last use, then pooled在首次到最后一次使用之间自动分配,之后归还池
Pass culling通道剔除All passes always run所有通道始终执行Unused passes automatically skipped (DFS from outputs)未使用通道自动跳过(从输出 DFS 遍历)
Render pass merging渲染通道合并Not possible不可能Consecutive passes with identical RTs merged into one RHI render pass相同 RT 的连续通道合并为一个 RHI 渲染通道
Async compute异步计算Manual fence insertion手动插入 fenceERDGPassFlags::AsyncCompute — RDG handles all fencingERDGPassFlags::AsyncCompute——RDG 处理所有 fence

The key insight is that RDG operates in two phases: a registration phase (called from the render thread, where you call AddPass()) and an execution phase (where Execute() compiles and runs the graph). All lambdas passed to AddPass() are deferred — they run during Execute(), not when AddPass() is called.

关键洞察是 RDG 在两个阶段运行:注册阶段(从渲染线程调用,在此调用 AddPass()) 和执行阶段Execute() 编译并运行图)。 所有传递给 AddPass() 的 lambda 均为延迟执行——它们在 Execute() 期间运行, 而非在 AddPass() 被调用时。

Resource Types资源类型

RDG wraps all GPU resources in graph-tracked types. The naming convention: FRDGTexture / FRDGBuffer are the resource objects (allocated from the RDG frame allocator), while FRDGTextureRef / FRDGBufferRef are raw pointers to them. Never store these pointers across frames — they are freed when the graph is destroyed.

RDG 将所有 GPU 资源包装为图跟踪类型。命名规范: FRDGTexture / FRDGBuffer 是资源对象(从 RDG 帧分配器分配), 而 FRDGTextureRef / FRDGBufferRef 是指向它们的裸指针。 绝不要跨帧存储这些指针——图销毁时它们会被释放。

// Create a new transient texture (GPU memory allocated only during needed passes)
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
    FIntPoint(1920, 1080),
    PF_FloatRGBA,
    FClearValueBinding::Black,
    TexCreate_RenderTargetable | TexCreate_ShaderResource);

FRDGTextureRef MyTexture = GraphBuilder.CreateTexture(Desc, TEXT("MyPostProcessResult"));

// Register an existing pooled render target (kept alive outside the graph)
FRDGTextureRef ExternalTexture = GraphBuilder.RegisterExternalTexture(MyPooledRenderTarget);

// Create an SRV / UAV view of the texture
FRDGTextureSRVRef SRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc(MyTexture));
FRDGTextureUAVRef UAV = GraphBuilder.CreateUAV(MyTexture);

// Create a structured buffer
FRDGBufferDesc BufDesc = FRDGBufferDesc::CreateStructuredDesc(sizeof(FMyStruct), 1024);
FRDGBufferRef MyBuffer = GraphBuilder.CreateBuffer(BufDesc, TEXT("MyBuffer"));
Type类型 Macro Meaning in parameter struct在参数结构体中的含义
FRDGTextureRefSHADER_PARAMETER_RDG_TEXTURESRV access (read-only) to a texture对纹理的 SRV 访问(只读)
FRDGTextureUAVRefSHADER_PARAMETER_RDG_TEXTURE_UAVUAV read/write access to a texture对纹理的 UAV 读写访问
FRDGTextureSRVRefSHADER_PARAMETER_RDG_TEXTURE_SRVSRV with explicit mip/array range具有明确 mip/数组范围的 SRV
FRDGBufferRefSHADER_PARAMETER_RDG_BUFFERDirect buffer reference (implies read)直接缓冲区引用(隐含读取)
FRDGBufferSRVRefSHADER_PARAMETER_RDG_BUFFER_SRVTyped / structured buffer SRV类型化/结构化缓冲区 SRV
FRDGBufferUAVRefSHADER_PARAMETER_RDG_BUFFER_UAVTyped / structured buffer UAV类型化/结构化缓冲区 UAV
n/aRENDER_TARGET_BINDING_SLOTS()Render target + depth stencil bindings (Raster passes only)渲染目标 + 深度模板绑定(仅光栅通道)

Pass Parameter Structs — The ContractPass 参数结构体 — 合约

The parameter struct passed to AddPass() is the contract between the pass and the graph. RDG reflects over the struct at compile time to extract every declared RDG resource, then uses this information to track read/write access, insert barriers, and manage lifetimes. Only macros prefixed with SHADER_PARAMETER_RDG_ or RDG_ are tracked — plain SHADER_PARAMETER entries are invisible to the graph.

传递给 AddPass() 的参数结构体是通道与图之间的合约。 RDG 在编译时反射结构体,提取所有声明的 RDG 资源,然后利用这些信息追踪读写访问、插入屏障并管理生命周期。 只有以 SHADER_PARAMETER_RDG_RDG_ 为前缀的宏才会被追踪—— 普通的 SHADER_PARAMETER 条目对图不可见。

// Declare a pass parameter struct
BEGIN_SHADER_PARAMETER_STRUCT(FMyPassParameters, )
    // These are tracked by RDG (access is declared implicitly by the macro):
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture)         // read
    SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, Output) // write
    SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, LightData)    // read

    // This is NOT tracked by RDG (no resource barrier management):
    SHADER_PARAMETER(FVector4f, InvSize)

    // For raster passes: render target slots
    RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()

// Allocate from the graph allocator (tied to graph lifetime, NOT frame)
FMyPassParameters* PassParams = GraphBuilder.AllocParameters<FMyPassParameters>();
PassParams->InputTexture = SourceTexture;
PassParams->Output       = GraphBuilder.CreateUAV(OutputTexture);
PassParams->InvSize      = FVector4f(1.0f/Width, 1.0f/Height, 0, 0);

AddPass() — Registering WorkAddPass() — 注册工作

AddPass() is the core API. It takes a name, a parameter struct, flags describing the GPU workload type, and a lambda that will be called later during Execute(). The lambda captures by value anything it needs — all closures are deferred.

AddPass() 是核心 API。它接收名称、参数结构体、描述 GPU 工作类型的标志, 以及一个将在 Execute() 期间调用的 lambda。 lambda 按值捕获所需的任何内容——所有闭包都被延迟执行。

// Compute pass (Graphics queue)
GraphBuilder.AddPass(
    RDG_EVENT_NAME("MyBlurPass"),
    PassParams,
    ERDGPassFlags::Compute,
    [PassParams, BlurRadius](FRHIComputeCommandList& RHICmdList)
    {
        // Lambda runs during Execute() — RHI resources are valid here
        TShaderMapRef<FMyBlurCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
        FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParams, FIntVector(DispatchX, DispatchY, 1));
    });

// Raster pass — render target bound via RENDER_TARGET_BINDING_SLOTS
PassParams->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::EClear);
PassParams->RenderTargets.DepthStencil = FDepthStencilBinding(DepthTexture,
    ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite);

GraphBuilder.AddPass(
    RDG_EVENT_NAME("MyDrawPass"),
    PassParams,
    ERDGPassFlags::Raster,
    [](FRHICommandList& RHICmdList)
    {
        // RHI render pass is already begun by RDG
        DrawPrimitives(RHICmdList);
    });

// NeverCull: outputs go outside graph (e.g., writing to a screen output)
GraphBuilder.AddPass(RDG_EVENT_NAME("Present"), PassParams,
    ERDGPassFlags::Raster | ERDGPassFlags::NeverCull, [...]{...});
ERDGPassFlagsMeaning含义
RasterGraphics pipe + render pass management by RDG图形管线 + RDG 管理渲染通道
ComputeCompute on Graphics pipe (no render pass)图形管线上的计算(无渲染通道)
AsyncComputeCompute on the separate Async Compute queue独立异步计算队列上的计算
NeverCullPass (and its producers) will never be culled by the graph — use when outputs leave the graph通道(及其生产者)永不被图剔除——输出离开图时使用
SkipRenderPassRDG will not call BeginRenderPass/EndRenderPass — you control it manuallyRDG 不调用 BeginRenderPass/EndRenderPass——手动控制
NeverMergePrevents this pass from being merged with adjacent raster passes防止此通道与相邻光栅通道合并

Compile() — Pass Culling & Render Pass MergingCompile() — 通道剔除与渲染通道合并

Compile() is invoked at the start of Execute(). It performs two critical optimizations that dramatically reduce GPU overhead:

Compile()Execute() 开始时调用, 执行两项显著减少 GPU 开销的关键优化:

1. Pass Culling (DFS from outputs)1. 通道剔除(从输出 DFS 遍历)

Controlled by r.RDG.CullPasses=1 (default). All passes start marked as culled. A depth-first search starts from "root" passes — those with NeverCull flag or passes that write to extracted resources. The DFS walks backwards through producer/consumer edges, marking every transitively needed pass as alive. Any pass not reached is skipped entirely — no lambda execution, no resource allocation, no barriers.

r.RDG.CullPasses=1(默认)控制。所有通道初始标记为已剔除。 从"根"通道开始深度优先搜索——即具有 NeverCull 标志或写入被提取资源的通道。 DFS 沿生产者/消费者边反向遍历,将每个传递需要的通道标记为存活。 未触达的通道完全跳过——无 lambda 执行,无资源分配,无屏障。

2. Render Pass Merging2. 渲染通道合并

Consecutive raster passes that share identical render target bindings are merged into a single RHI render pass. The compiler (lines 1415–1486 in RenderGraphBuilder.cpp) scans forward through all raster passes, accumulating candidates. When a non-mergeable pass is encountered (different RTs, NeverMerge flag, writes outside the render pass), it commits the current merge set: the first pass in the set is assigned bSkipRenderPassEnd, the last is assigned bSkipRenderPassBegin, and intermediate passes get both. This allows multiple passes to "share" one BeginRenderPass / EndRenderPass call.

共享相同渲染目标绑定的连续光栅通道被合并为单个 RHI 渲染通道。 编译器(RenderGraphBuilder.cpp 第 1415–1486 行)向前扫描所有光栅通道,累积候选通道。 遇到不可合并的通道(不同 RT、NeverMerge 标志、在渲染通道外写入)时,提交当前合并集: 集合中第一个通道被分配 bSkipRenderPassEnd, 最后一个被分配 bSkipRenderPassBegin,中间通道同时获得两者。 这使多个通道能"共享"一次 BeginRenderPass / EndRenderPass 调用。

// Example: three passes drawing into the same GBuffer RT set
// → RDG merges them into one RHI render pass
//
// Pass A: writes GBufferA,B,C,Depth  → BeginRenderPass(GBuffer RTs)
// Pass B: writes GBufferA,B,C,Depth  → (no BeginRenderPass/EndRenderPass)
// Pass C: writes GBufferA,B,C,Depth  → EndRenderPass()
//
// Result: GPU sees ONE render pass (better for TBDR, fewer flushes on tiling GPUs)

// Force a pass to NOT merge (e.g., it writes to a UAV inside the render pass):
GraphBuilder.AddPass(Name, Params, ERDGPassFlags::Raster | ERDGPassFlags::NeverMerge, [...]{});

Execute() — Resource Allocation & Barrier GenerationExecute() — 资源分配与屏障生成

Execute() is where the magic happens. After compilation, it allocates GPU resources and sets up barriers. Key aspects:

Execute() 是奇迹发生之处。编译后,它分配 GPU 资源并设置屏障。关键方面:

Async Compute — Overlap on the GPU异步计算 — GPU 上的重叠

Adding a pass with ERDGPassFlags::AsyncCompute tells RDG to schedule it on the hardware's separate Async Compute queue. RDG automatically inserts the necessary Graphics→AsyncCompute and AsyncCompute→Graphics fences. The practical effect is that the Async Compute pass runs in parallel with Graphics passes on the GPU, filling otherwise idle execution units.

使用 ERDGPassFlags::AsyncCompute 添加通道告诉 RDG 将其调度到硬件独立的异步计算队列。 RDG 自动插入所需的 Graphics→AsyncCompute 和 AsyncCompute→Graphics fence。 实际效果是异步计算通道在 GPU 上与图形通道并行运行,填充原本闲置的执行单元。

// Async compute pass — runs in parallel with Graphics on the GPU
GraphBuilder.AddPass(
    RDG_EVENT_NAME("LumenScreenProbeGather"),
    PassParams,
    ERDGPassFlags::AsyncCompute,  // ← Async compute queue
    [PassParams](FRHIComputeCommandList& RHICmdList)
    {
        // Runs on async compute, overlaps with shadow map rendering on Graphics
    });

// Check at runtime if async compute is available:
if (GraphBuilder.IsAsyncComputeEnabled())
{
    // Use ERDGPassFlags::AsyncCompute
}
else
{
    // Fall back to ERDGPassFlags::Compute (same queue as Graphics)
}
Async Compute dependency rule: If an async compute pass reads a resource written by a graphics pass (or vice versa), RDG automatically inserts a cross-queue fence. You do not need to manage this manually — that's the whole point of declaring resources in the parameter struct. 异步计算依赖规则:如果异步计算通道读取图形通道写入的资源(反之亦然),RDG 自动插入跨队列 fence。你不需要手动管理这个——这正是在参数结构体中声明资源的全部意义。

Resource Lifetime & Extraction资源生命周期与提取

By default, graph-created resources exist only within the graph's execution. To keep a resource alive beyond the current frame (e.g., for temporal effects), use QueueTextureExtraction(). This promotes the internal resource to an external pooled render target, which persists in the pool until no longer needed.

默认情况下,图创建的资源仅在图的执行期间存在。要在当前帧之后保持资源存活 (例如,用于时间效果),使用 QueueTextureExtraction()。 这将内部资源提升为外部池化渲染目标,在池中持久存在,直到不再需要。

// Keep a texture alive beyond this frame (for TAA history, etc.)
TRefCountPtr<IPooledRenderTarget> PersistentHistory;

GraphBuilder.QueueTextureExtraction(
    HistoryTexture,
    &PersistentHistory,        // Filled after Execute()
    ERHIAccess::SRVGraphics);  // Final state for next frame access

// Next frame: register the extracted texture back into the new graph
FRDGTextureRef HistoryRef = GraphBuilder.RegisterExternalTexture(PersistentHistory);

Writing a Custom RDG Pass — Full Example编写自定义 RDG Pass — 完整示例

Here is a complete working example of a custom full-screen post-process compute pass. This pattern can be used to inject custom effects into the UE rendering pipeline.

下面是一个完整可用的自定义全屏后处理计算通道示例。 此模式可用于向 UE 渲染管线注入自定义效果。

// === 1. Shader declaration (in MyEffect.h / .cpp) ===
class FMyEffectCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FMyEffectCS);
    SHADER_USE_PARAMETER_STRUCT(FMyEffectCS, FGlobalShader);

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColor)
        SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, Output)
        SHADER_PARAMETER(FVector4f, Params)
    END_SHADER_PARAMETER_STRUCT()

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }
};
IMPLEMENT_GLOBAL_SHADER(FMyEffectCS, "/Engine/Private/MyEffect.usf", "MainCS", SF_Compute);

// === 2. Pass registration (called from e.g., PostProcessing.cpp or SceneExtension) ===
void AddMyEffectPass(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneColor, FRDGTextureRef Output,
                      FIntPoint Resolution)
{
    TShaderMapRef<FMyEffectCS> Shader(GetGlobalShaderMap(GMaxRHIFeatureLevel));

    FMyEffectCS::FParameters* PassParams = GraphBuilder.AllocParameters<FMyEffectCS::FParameters>();
    PassParams->SceneColor = SceneColor;
    PassParams->Output     = GraphBuilder.CreateUAV(Output);
    PassParams->Params     = FVector4f(1.0f/Resolution.X, 1.0f/Resolution.Y, 0, 0);

    FComputeShaderUtils::AddFullscreenPass(
        GraphBuilder,
        GetGlobalShaderMap(GMaxRHIFeatureLevel),
        RDG_EVENT_NAME("MyEffect"),
        Shader,
        PassParams,
        FIntVector(FMath::DivideAndRoundUp(Resolution.X, 8),
                   FMath::DivideAndRoundUp(Resolution.Y, 8), 1));
}

// === 3. HLSL shader (Engine/Shaders/Private/MyEffect.usf) ===
// Texture2D SceneColor;
// RWTexture2D<float4> Output;
// [numthreads(8, 8, 1)]
// void MainCS(uint3 DispatchThreadId : SV_DispatchThreadID) { ... }

Debugging RDG调试 RDG

RDG has extensive built-in debugging tools. The most useful console variables:

RDG 内置了丰富的调试工具。最有用的控制台变量:

CVarEffect效果
r.RDG.ImmediateMode=1Execute each pass immediately as AddPass() is called — disables all optimization but makes stack traces readableAddPass() 被调用时立即执行每个通道——禁用所有优化但使堆栈跟踪可读
r.RDG.CullPasses=0Disable pass culling — every registered pass will execute禁用通道剔除——每个注册的通道都将执行
r.RDG.Debug=1Enable verbose RDG logging and validation启用详细 RDG 日志记录和验证
r.RDG.Breakpoint=PassNameBreak into the debugger when executing the named pass执行指定名称通道时进入调试器
r.RDG.ParallelExecute=0Disable parallel pass execution (useful to isolate crashes)禁用并行通道执行(用于隔离崩溃)
vis ResourceNameConsole command: visualize an RDG texture on screen控制台命令:在屏幕上可视化 RDG 纹理
RenderDoc + RDG: With r.RDG.Debug=1, RDG event names set via RDG_EVENT_NAME() appear as nested GPU debug groups in RenderDoc's event list. This makes it trivial to find exactly which pass produced a given draw call. Always name your passes descriptively. RenderDoc + RDG:使用 r.RDG.Debug=1 时,通过 RDG_EVENT_NAME() 设置的 RDG 事件名称会以嵌套 GPU 调试组的形式出现在 RenderDoc 的事件列表中。这使得精确定位哪个通道产生了特定绘制调用变得简单。始终为通道起描述性名称。

Source Navigation源码导航

All paths relative to Engine/Source/Runtime/RenderCore/

所有路径相对于 Engine/Source/Runtime/RenderCore/

Unreal Engine 5.6 · Engine/Source/Runtime/RenderCore/
Source analysis · For learning purposes