TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역] 언리얼 렌더링 시스템 해부하기(11)- RDG

jplee 2024. 9. 14. 01:23

역자의 말.

RDG 는 RHI 와 더불어 언리얼 렌더링의 구조적 근간을 받치고 있는 두 개의 커다란 기둥입니다.테크니컬 아티스트가 커스텀 Pass 또는 지오메트리 셰이딩 처리등을 새롭게 개발해야 한다면 이것은 매우 중요하며 정확히 이해하지 않았다면 매우 난해한 문제점들에 봉착하게 될 것입니다.우리는 앞으로 단순히 머티리얼 편집기에 의존 하지 않고 좀 더 진보된 무엇인가를 만들기 위해 습득해야 할 여러가지들이 있을 것이고 그것은 향후 몇 년 이내에 시각화 처리 분야의 테크니컬 아티스트로서 필수적인 지식이 될 것입니다. 그럼으로 오늘도 멋진 토픽을 공유 해 준 장선생의 언리얼 렌더링 시스템 해부하기 시리즈를 탐독 해 봅시다.


저자

向往 - 知乎

UE微信技术群加81079389(注明知乎) 回答数 3,获得 2,384 次赞同

www.zhihu.com

그래픽 렌더링, 게임 엔진、GPU。知乎:http://www.zhihu.com/people/timlly-chang。UE技术群:943549515,위챗 그룹 먼저 추가 81079389(블로거 참고 사항)
 

11.1 이 문서의 개요

RDG의 정식 명칭은 렌더링종속성 그래프로, UE 4.22부터 도입된 새로운 렌더링 서브시스템으로, 디렉티드 비순환 그래프(DAG) 스케줄링 시스템에 기반하여 다음을 수행하는 데 사용됩니다.렌더링 파이프라인의 전체 프레임 최적화를 수행하는 데 사용됩니다.
최신 그래픽 API(DirectX 12, Vulkan, Metal 2)를 활용하여 자동 비동기 컴퓨팅 스케줄링은 물론 보다 효율적인 메모리 관리 및 배리어 관리를 통해 성능을 향상시킵니다.
기존 그래픽 API(DirectX 11, OpenGL)는 드라이버가 복잡한 휴리스틱을 호출하여 GPU에서 중요한 스케줄링 작업을 수행할 시기와 방법을 결정해야 합니다.예를 들어 캐시 비우기, 메모리 관리 및 재사용, 레이아웃 변환 수행 등이 있습니다.인터페이스의 즉각적인 모드 특성으로 인해 극단적인 상황을 처리하려면 복잡한 로깅과 상태 추적이 필요합니다.이러한 상황은 궁극적으로 성능에 부정적인 영향을 미치고 병렬 처리를 방해합니다.
최신 그래픽 API(DirectX 12, Vulkan 및 Metal 2)는 기존 그래픽 API와 달리 저수준 GPU 관리의 부담을 애플리케이션으로 이전합니다.따라서 애플리케이션은 렌더링 파이프라인의 높은 수준의 컨텍스트를 활용하여 스케줄링을 실행할 수 있으므로 성능이 향상되고 렌더링 스택이 간소화됩니다.
RDG 개념은 GPU에서 패스를 즉시 실행하는 대신 렌더링이 필요한 모든 패스를 먼저 수집한 다음 종속성 순서대로 차트를 컴파일하고 실행하며, 이 과정에서 다양한 유형의 자르기 및 최적화가 수행됩니다.
종속 그래프 데이터 구조에 대한 전체 프레임 인식과 최신 그래픽 API의 기능이 결합되어 RDG가 백그라운드에서 복잡한 스케줄링 작업을 수행할 수 있습니다:

  • 비동기 컴퓨팅 채널의 자동 스케줄링 및 격리를 수행합니다.
  • 프레임의 분리된 간격 동안 리소스 간의 별칭 메모리를 활성 상태로 유지합니다.
  • 파이프라인 지연을 방지하기 위해 가능한 한 빨리 배리어 및 레이아웃 변환을 시작하세요.

또한 RDG는 종속성 차트를 활용하여 채널 설정 중에 풍부한 유효성 검사를 제공하고 기능 및 성능에 영향을 미치는 문제를 자동으로 캡처하여 개발 프로세스를 개선합니다.
RDG는 UE의 독창적인 개념이자 기술이 아니며, 이미 GDC 2017에서 Frost는 프레임 그래프(Frame Graph) 기술을 구현하여 적용했으며, 프레임 그래프는 엔진의 모든 종류의 렌더링 기능(Feature)을 상위 레이어 렌더링 로직(Renderer)과 하위 레이어 리소스(Shader)에서 분리하는 것을 목표로 합니다,RenderContext, 그래픽 API 등)를 분리하여 추가적인 디커플링, 최적화를 수행하며, 그 중 가장 중요한 것은 멀티 스레드 및 병렬 렌더링입니다.

프레임 그래프는 프레임에 사용된 모든 정보를 포함하는 렌더 패스 및 리소스의 상위 수준 표현으로, 패스 간에 순서와 종속성을 지정할 수 있으며, 그 예는 아래와 같습니다:

프레임 맵 접근 방식을 사용하여 Frost 엔진에서 구현된 디퍼드 렌더링의 시퀀스 및 종속성 그래프입니다.
UE의 RDG는 프레임 그래프를 기반으로 정밀하게 커스터마이징되고 구현되었다고 해도 과언이 아닙니다.UE4.26에서는 씬 렌더링, 포스트 프로세싱, 라이트 트레이싱 및 기타 모듈이 모두 RHI 명령을 직접 호출하는 기존 방식 대신 RDG를 사용하는 등 RDG가 많이 대중화되었습니다.
이 게시물에서는 UE RDG의 다음 요소에 중점을 둡니다:

  • RDG의 기본 개념 및 유형.
  • RDG 사용량.
  • RDG의 내부 메커니즘과 원칙.

 

11.2 RDG 기본 사항

이 장에서는 RDG에 관련된 주요 유형, 개념, 인터페이스 등을 설명하는 것으로 시작합니다.

11.2.1 RDG 기본 유형

RDG 기본 유형과 인터페이스는 주로 RenderGraphUtils.h와 RenderGraphDefinitions.h에 집중되어 있습니다.분석의 일부는 다음과 같습니다:

// Engine\Source\Runtime\RenderCore\Public\RenderGraphDefinitions.h

// RDG Pass타입.
enum class ERDGPassFlags : uint8
{
    None = 0,                 // 매개변수가 없는 AddPass 함수에 사용됩니다.
    Raster = 1 << 0,        // 패스는 그래픽 파이프라인에서 래스터화를 사용합니다.
    Compute = 1 << 1,        // 패스는 그래픽 파이프라인에서 컴퓨팅을 사용합니다.
    AsyncCompute = 1 << 2,    // Pass는 비동기 컴퓨팅 파이프라인에서 컴퓨팅을 사용합니다.
    Copy = 1 << 3,            // Pass는 그래픽 파이프라인에서 복사 명령을 사용합니다..
    NeverCull = 1 << 4,     // 자르기에 최적화되지 않음, 스페셜 패스에 사용됨.
    SkipRenderPass = 1 << 5,        // BeginRenderPass/EndRenderPass를 무시하고 사용자가 호출할 수 있도록 합니다. 래스터 바인딩에서만 작동합니다. 패스 병합은 비활성화됩니다.
    UntrackedAccess = 1 << 6,        // 패스는 RDG에 등록될 수 있는 원시 RHI 리소스에 액세스하지만 모든 리소스는 현재 상태로 유지합니다. 이 플래그를 사용하면 채널의 분할 장벽을 가로지르는 그래픽 스케줄링을 방지할 수 있습니다. 모든 분할은 패스 실행 이후까지 지연됩니다. 패스 실행 중에는 리소스의 상태가 변경되지 않을 수 있습니다. 성능에 영향을 미치는 배리어. AsyncCompute와 결합할 수 없습니다.
    Readback = Copy | NeverCull,    // Pass는 복사 명령을 사용하지만 스테이징 리소스에 씁니다.

    CommandMask = Raster | Compute | AsyncCompute | Copy, // 전달하기 위해 제출된 RHI 명령의 유형을 나타내는 플래그 마스크입니다.
    ScopeMask = NeverCull | UntrackedAccess // 패스 플래그 범위에서 사용할 수 있는 플래그 마스크
};

// Buffer표시.
enum class ERDGBufferFlags : uint8
{
    None = 0, // 표시되지 않음.
    MultiFrame = 1 << 0 // 여러 프레임에서 생존합니다.
};

// 텍스처 마커.
enum class ERDGTextureFlags : uint8
{
    None = 0,
    MultiFrame = 1 << 0, // 여러 프레임에서 생존합니다.
    MaintainCompression = 1 << 1, // 이 텍스처의 메타데이터 압축 해제를 방지합니다.
};

// UAV 마킹.
enum class ERDGUnorderedAccessViewFlags : uint8
{
    None = 0,
    SkipBarrier = 1 << 0 // 忽略屏障.
};

// 상위 리소스 유형.
enum class ERDGParentResourceType : uint8
{
    Texture,
    Buffer,
    MAX
};

// 보기 유형.
enum class ERDGViewType : uint8
{
    TextureUAV, // 텍스처 UAV(데이터 쓰기용)
    TextureSRV, // 텍스처 SRV(데이터 읽기용)
    BufferUAV,  // 버퍼링된 UAV(데이터 쓰기용)
    BufferSRV,  // 버퍼링된 SRV(데이터 읽기용)
    MAX
};

// 뷰를 만들 때 텍스처 메타데이터 평면을 지정하는 데 사용됩니다.
enum class ERDGTextureMetaDataAccess : uint8
{
    None = 0,             // 마스터 평면의 기본 압축이 사용됩니다.
    CompressedSurface,     // 주 평면은 압축되어 있지 않습니다.
    Depth,      // 뎁스 평면은 기본적으로 를 사용하여 압축됩니다.
    Stencil, // 템플릿 평면은 기본적으로 를 사용하여 압축됩니다.
    HTile,      // HTile 평면.
    FMask,   // F마스크 평면.
    CMask    // 마스크 평면.
};

// 간단한 C++ 객체 얼로케이터, MemStack 얼로케이터로 객체 추적 및 삭제.
class FRDGAllocator final
{
public:
    FRDGAllocator();
    ~FRDGAllocator();

    // 원시 메모리를 할당합니다.
    FORCEINLINE void* Alloc(uint32 SizeInBytes, uint32 AlignInBytes)
    {
        return MemStack.Alloc(SizeInBytes, AlignInBytes);
    }
    // 소멸자를 추적하지 않고 POD 메모리를 할당합니다.
    template <typename PODType>
    FORCEINLINE PODType* AllocPOD()
    {
        return reinterpret_cast<PODType*>(Alloc(sizeof(PODType), alignof(PODType)));
    }
    // 소멸자 추적을 통한 C++ 객체 할당.
    template <typename ObjectType, typename... TArgs>
    FORCEINLINE ObjectType* AllocObject(TArgs&&... Args)
    {
        TTrackedAlloc<ObjectType>* TrackedAlloc = new(MemStack) TTrackedAlloc<ObjectType>(Forward<TArgs&&>(Args)...);
        check(TrackedAlloc);
        TrackedAllocs.Add(TrackedAlloc);
        return TrackedAlloc->Get();
    }
    // 소멸자 추적 없는 C++ 객체 할당. (위험하므로 주의해서 사용하세요.)
    template <typename ObjectType, typename... TArgs>
    FORCEINLINE ObjectType* AllocNoDestruct(TArgs&&... Args)
    {
        return new (MemStack) ObjectType(Forward<TArgs&&>(Args)...);
    }

    // 할당된 메모리를 모두 확보합니다.
    void ReleaseAll();

private:
    class FTrackedAlloc
    {
    public:
        virtual ~FTrackedAlloc() = default;
    };

    template <typename ObjectType>
    class TTrackedAlloc : public FTrackedAlloc
    {
    public:
        template <typename... TArgs>
        FORCEINLINE TTrackedAlloc(TArgs&&... Args) : Object(Forward<TArgs&&>(Args)...) {}

        FORCEINLINE ObjectType* Get() { return &Object; }

    private:
        ObjectType Object;
    };

    // 디스펜서.
    FMemStackBase MemStack;
    // 할당된 모든 객체.
    TArray<FTrackedAlloc*, SceneRenderingAllocator> TrackedAllocs;
};

// Engine\Source\Runtime\RenderCore\Public\RenderGraphUtils.h

// 사용하지 않는 리소스를 정리합니다.
extern RENDERCORE_API void ClearUnusedGraphResourcesImpl(const FShaderParameterBindings& ShaderBindings, ...);
(......)

// 예비 인스턴스와 함께 사용할 수 있는 외부 텍스처를 등록합니다.
FRDGTextureRef RegisterExternalTextureWithFallback(FRDGBuilder& GraphBuilder, ...);
inline FRDGTextureRef TryRegisterExternalTexture(FRDGBuilder& GraphBuilder, ...);
inline FRDGBufferRef TryRegisterExternalBuffer(FRDGBuilder& GraphBuilder, ...);

// 셰이더 계산을 위한 툴 클래스입니다.
struct RENDERCORE_API FComputeShaderUtils
{
    // 이상적인 그룹 크기는 8x8이며, GCN에서는 최소 하나의 웨이브, Nvidia에서는 두 개의 워프를 차지합니다.
    static constexpr int32 kGolden2DGroupSize = 8;
    static FIntVector GetGroupCount(const int32 ThreadCount, const int32 GroupSize);

    // 인수를 전달하여 계산 셰이더를 RHI 명령 목록에 디스패치합니다.
    template<typename TShaderClass>
    static void Dispatch(FRHIComputeCommandList& RHICmdList, const TShaderRef<TShaderClass>& ComputeShader, const typename TShaderClass::FParameters& Parameters, FIntVector GroupCount);
    // 인수를 전달하여 지시되지 않은 연산 셰이더를 RHI 명령 목록에 디스패치합니다.
    template<typename TShaderClass>
    static void DispatchIndirect(FRHIComputeCommandList& RHICmdList, const TShaderRef<TShaderClass>& ComputeShader, const typename TShaderClass::FParameters& Parameters, FRHIVertexBuffer* IndirectArgsBuffer, uint32 IndirectArgOffset);
    // 인수를 사용하여 렌더 그래프 빌더에 계산 셰이더를 전송합니다.
    template<typename TShaderClass>
    static void AddPass(FRDGBuilder& GraphBuilder,FRDGEventName&& PassName,ERDGPassFlags PassFlags,const TShaderRef<TShaderClass>& ComputeShader,typename TShaderClass::FParameters* Parameters,FIntVector GroupCount);

    (......)

    // UAV 지우기.
    static void ClearUAV(FRDGBuilder& GraphBuilder, FGlobalShaderMap* ShaderMap, FRDGBufferUAVRef UAV, uint32 ClearValue);
    static void ClearUAV(FRDGBuilder& GraphBuilder, FGlobalShaderMap* ShaderMap, FRDGBufferUAVRef UAV, FVector4 ClearValue);
};

// 복사 텍스처 패스를 추가합니다.
void AddCopyTexturePass(FRDGBuilder& GraphBuilder, FRDGTextureRef InputTexture, FRDGTextureRef OutputTexture, const FRHICopyTextureInfo& CopyInfo);
(......)

// 구문 분석 대상에 Pass의 복사본을 추가합니다.
void AddCopyToResolveTargetPass(FRDGBuilder& GraphBuilder, FRDGTextureRef InputTexture, FRDGTextureRef OutputTexture, const FResolveParams& ResolveParams);

// Pass에 대한 모든 유형의 리소스를 정리합니다.
void AddClearUAVPass(FRDGBuilder& GraphBuilder, FRDGBufferUAVRef BufferUAV, uint32 Value);
void AddClearUAVFloatPass(FRDGBuilder& GraphBuilder, FRDGBufferUAVRef BufferUAV, float Value);
void AddClearUAVPass(FRDGBuilder& GraphBuilder, FRDGTextureUAVRef TextureUAV, const FUintVector4& ClearValues);
void AddClearRenderTargetPass(FRDGBuilder& GraphBuilder, FRDGTextureRef Texture);
void AddClearDepthStencilPass(FRDGBuilder& GraphBuilder,FRDGTextureRef Texture,bool bClearDepth,float Depth,bool bClearStencil,uint8 Stencil);
void AddClearStencilPass(FRDGBuilder& GraphBuilder, FRDGTextureRef Texture);
(......)

// 패스에 리드백 텍스처를 추가합니다.
void AddEnqueueCopyPass(FRDGBuilder& GraphBuilder, FRHIGPUTextureReadback* Readback, FRDGTextureRef SourceTexture, FResolveRect Rect = FResolveRect());
// 읽기 백 버퍼 패스를 추가합니다.
void AddEnqueueCopyPass(FRDGBuilder& GraphBuilder, FRHIGPUBufferReadback* Readback, FRDGBufferRef SourceBuffer, uint32 NumBytes);

// 리소스 만들기.
FRDGBufferRef CreateStructuredBuffer(FRDGBuilder& GraphBuilder, ...);
FRDGBufferRef CreateVertexBuffer(FRDGBuilder& GraphBuilder, ...);

// 매개 변수 없이 패스 증가 .
template <typename ExecuteLambdaType>
void AddPass(FRDGBuilder& GraphBuilder, FRDGEventName&& Name, ExecuteLambdaType&& ExecuteLambda);
template <typename ExecuteLambdaType>
void AddPass(FRDGBuilder& GraphBuilder, ExecuteLambdaType&& ExecuteLambda);

// 기타 특별 패스
void AddBeginUAVOverlapPass(FRDGBuilder& GraphBuilder);
void AddEndUAVOverlapPass(FRDGBuilder& GraphBuilder);

(......)

11.2.2 RDG 리소스

RDG 리소스는 RHI 리소스를 직접 사용하지 않고 RHI 리소스 참조를 래핑한 다음 다른 유형의 리소스와 추가 정보에 대해 개별적으로 캡슐화합니다. 일부 RDG는 아래에 정의되어 있습니다:

// Engine\Source\Runtime\RenderCore\Public\RenderGraphResources.h

class FRDGResource
{
public:
    // 복사 생성자를 제거합니다.
    FRDGResource(const FRDGResource&) = delete;
    virtual ~FRDGResource() = default;

    //////////////////////////////////////////////////////////////////////////
    // 다음 인터페이스는 패스 실행 중에 RDG에서만 호출할 수 있습니다.

    // 이 리소스가 사용 중인지 여부를 표시하며, 사용 중이 아닌 경우 정리됩니다.
#if RDG_ENABLE_DEBUG
    virtual void MarkResourceAsUsed();
#else
    inline  void MarkResourceAsUsed() {}
#endif
    // RDG에 대한 RHI 리소스 참조를 받으세요.
    FRHIResource* GetRHI() const
    {
        ValidateRHIAccess();
        return ResourceRHI;
    }

    //////////////////////////////////////////////////////////////////////////

protected:
    FRDGResource(const TCHAR* InName);

    // 이 리소스를 RHI 리소스를 위한 단순 통과 컨테이너로 할당하세요.
    void SetPassthroughRHI(FRHIResource* InResourceRHI)
    {
        ResourceRHI = InResourceRHI;
#if RDG_ENABLE_DEBUG
        DebugData.bAllowRHIAccess = true;
        DebugData.bPassthrough = true;
#endif
    }

    bool IsPassthrough() const
    {
#if RDG_ENABLE_DEBUG
        return DebugData.bPassthrough;
#else
        return false;
#endif
    }

    /** Verify that the RHI resource can be accessed at a pass execution. */
    void ValidateRHIAccess() const
    {
#if RDG_ENABLE_DEBUG
        checkf(DebugData.bAllowRHIAccess,
            TEXT("Accessing the RHI resource of %s at this time is not allowed. If you hit this check in pass, ")
            TEXT("that is due to this resource not being referenced in the parameters of your pass."),
            Name);
#endif
    }

    FRHIResource* GetRHIUnchecked() const
    {
        return ResourceRHI;
    }

    // RHI 리소스 인용.
    FRHIResource* ResourceRHI = nullptr;

private:
    // 디버깅 정보.
#if RDG_ENABLE_DEBUG
    class FDebugData
    {
    private:
        // 런타임에 패스의 람다에서 리소스가 실제로 사용되는지 추적하여 패스에 대한 불필요한 리소스 종속성을 감지합니다.
        bool bIsActuallyUsedByPass = false;
        // 패스 실행 중 기본 RHI 자체에서 액세스를 허용하는지 여부를 추적합니다.
        bool bAllowRHIAccess = false;
        // true이면 리소스가 빌더에 연결되지 않고 RDG에 코드를 스테이징하기 위한 가상 컨테이너로 존재합니다.
        bool bPassthrough = false;
    } DebugData;
#endif
};

class FRDGUniformBuffer : public FRDGResource
{
public:
    // RHI 받기.
    FRHIUniformBuffer* GetRHI() const
    {
        return static_cast<FRHIUniformBuffer*>(FRDGResource::GetRHI());
    }
        
    (......)

protected:
    template <typename TParameterStruct>
    explicit FRDGUniformBuffer(TParameterStruct* InParameters, const TCHAR* InName)
        : FRDGResource(InName)
        , ParameterStruct(InParameters)
        , bGlobal(ParameterStruct.HasStaticSlot());

private:
    // 매개변수 구조.
    const FRDGParameterStruct ParameterStruct;
    // RHI 리소스.
    TRefCountPtr<FRHIUniformBuffer> UniformBufferRHI;
    // RDG 핸들.
    FRDGUniformBufferHandle Handle;
    // 글로벌 바인딩 또는 로컬 바인딩.
    uint8 bGlobal : 1;
};

// RDGUniformBuffer模板类.
template <typename ParameterStructType>
class TRDGUniformBuffer : public FRDGUniformBuffer
{
public:
    const TRDGParameterStruct<ParameterStructType>& GetParameters() const;
    TUniformBufferRef<ParameterStructType> GetRHIRef() const;
    const ParameterStructType* operator->() const;

    (......)
};

// 그래프 추적에 의해 라이프사이클이 할당된 렌더링된 그래프 리소스입니다. 이를 참조하는 하위 리소스(예: 뷰)가 있을 수 있습니다.
class FRDGParentResource : public FRDGResource
{
public:
    // 상위 리소스 유형.
    const ERDGParentResourceType Type;
    bool IsExternal() const;

protected:
    FRDGParentResource(const TCHAR* InName, ERDGParentResourceType InType);

    // 외부 리소스 여부
    uint8 bExternal : 1;
    // 리소스가 추출되었는지 여부입니다.
    uint8 bExtracted : 1;
    // 이 리소스를 획득/폐기해야 하는지 여부입니다.
    uint8 bTransient : 1;
    // 마지막 소유자가 배포했는지 여부.
    uint8 bLastOwner : 1;
    // 잘립니다.
    uint8 bCulled : 1;
    // Pass의 비동기 계산에 사용되는지 여부입니다.
    uint8 bUsedByAsyncComputePass : 1;

private:
    // 참조 개수.
    uint16 ReferenceCount = 0;
    // 사용자 할당 리소스의 초기 및 최종 상태(알려진 경우)
    ERHIAccess AccessInitial = ERHIAccess::Unknown;
    ERHIAccess AccessFinal = ERHIAccess::Unknown;

    FRDGPassHandle AcquirePass;
    FRDGPassHandle FirstPass;
    FRDGPassHandle LastPass;

    (......)
};

// 렌더링된 텍스처에 대한 설명을 생성합니다.
struct RENDERCORE_API FRDGTextureDesc
{
    static FRDGTextureDesc Create2D(...);
    static FRDGTextureDesc Create2DArray(...);
    static FRDGTextureDesc Create3D(...);
    static FRDGTextureDesc CreateCube(...);
    static FRDGTextureDesc CreateCubeArray(...);
    
    bool IsTexture2D() const;
    bool IsTexture3D() const;
    bool IsTextureCube() const;
    bool IsTextureArray() const;
    bool IsMipChain() const;
    bool IsMultisample() const;
    FIntVector GetSize() const;
    
    // 하위 리소스 레이아웃 .
    FRDGTextureSubresourceLayout GetSubresourceLayout() const;
    bool IsValid() const;

    // 클리어런스 값입니다.
    FClearValueBinding ClearValue;
    ETextureDimension Dimension = ETextureDimension::Texture2D;
    // 클리어런스 표시.
    ETextureCreateFlags Flags = TexCreate_None;
    // 픽셀 형식.
    EPixelFormat Format = PF_Unknown;
    // 텍스처 범위(x 및 y)
    FIntPoint Extent = FIntPoint(1, 1);
    // 3D 텍스처의 깊이.
    uint16 Depth = 1;
    uint16 ArraySize = 1;
    // 텍스처 레벨 수입니다.
    uint8 NumMips = 1;
    // 샘플 수입니다.
    uint8 NumSamples = 1;
};

// 풀의 RT 설명을 RDG 텍스처 설명으로 변환합니다.
inline FRDGTextureDesc Translate(const FPooledRenderTargetDesc& InDesc, ERenderTargetTexture InTexture = ERenderTargetTexture::Targetable);
// 풀에서 RDG 텍스처 설명을 RT 설명으로 변환합니다.
inline FPooledRenderTargetDesc Translate(const FRDGTextureDesc& InDesc);

// 풀의 텍스처.
class RENDERCORE_API FRDGPooledTexture
{
public:
    // 설명.
    const FRDGTextureDesc Desc;

    // 참조 카운팅.
    uint32 GetRefCount() const;
    uint32 AddRef() const;
    uint32 Release() const;

private:
    FRDGPooledTexture(FRHITexture* InTexture, const FRDGTextureDesc& InDesc, const FUnorderedAccessViewRHIRef& FirstMipUAV);

    // 캐시된 UAV를 초기화합니다.
    void InitViews(const FUnorderedAccessViewRHIRef& FirstMipUAV);
    void Finalize();
    void Reset();

    // 해당 RHI 텍스처.
    FRHITexture* Texture = nullptr;
    // 텍스처 오브젝트입니다.
    FRDGTexture* Owner = nullptr;
    // 하위 리소스 레이아웃.
    FRDGTextureSubresourceLayout Layout;
    // 하위 리소스 상태.
    FRDGTextureSubresourceState State;

    // RHI 텍스처 캐싱을 위한 UAV/SRV.
    TArray<FUnorderedAccessViewRHIRef, TInlineAllocator<1>> MipUAVs;
    TArray<TPair<FRHITextureSRVCreateInfo, FShaderResourceViewRHIRef>, TInlineAllocator<1>> SRVs;
    FUnorderedAccessViewRHIRef HTileUAV;
    FShaderResourceViewRHIRef  HTileSRV;
    FUnorderedAccessViewRHIRef StencilUAV;
    FShaderResourceViewRHIRef  FMaskSRV;
    FShaderResourceViewRHIRef  CMaskSRV;

    mutable uint32 RefCount = 0;
};

// RDG 텍스처.
class RENDERCORE_API FRDGTexture final : public FRDGParentResource
{
public:
    // 아직 RDG로 전달되지 않은 패스에 대한 패스 스루 텍스처를 생성하여 RHI 균일 버퍼를 RDG 파라미터로 채우는 데 적합합니다.
    static FRDGTextureRef GetPassthrough(const TRefCountPtr<IPooledRenderTarget>& PooledRenderTarget);

    // 설명 및 라벨링.
    const FRDGTextureDesc Desc;
    const ERDGTextureFlags Flags;

    //////////////////////////////////////////////////////////////////////////
    //! The following methods may only be called during pass execution.

    IPooledRenderTarget* GetPooledRenderTarget() const
    FRHITexture* GetRHI() const

    //////////////////////////////////////////////////////////////////////////

    FRDGTextureSubresourceLayout GetSubresourceLayout() const;
    FRDGTextureSubresourceRange GetSubresourceRange() const;
    FRDGTextureSubresourceRange GetSubresourceRangeSRV() const;

private:
    FRDGTexture(const TCHAR* InName, const FRDGTextureDesc& InDesc, ERDGTextureFlags InFlags, ERenderTargetTexture InRenderTargetTexture);

    void SetRHI(FPooledRenderTarget* PooledRenderTarget, FRDGTextureRef& OutPreviousOwner);
    void Finalize();
    FRHITexture* GetRHIUnchecked() const;
    bool IsLastOwner() const;
    FRDGTextureSubresourceState& GetState();
    const ERenderTargetTexture RenderTargetTexture;

    // 하위 리소스의 변환을 용이하게 하기 위한 레이아웃입니다.
    FRDGTextureSubresourceLayout Layout;
    // 실행 도중 풀링된 텍스처가 다음 텍스처를 할당합니다.
    FRDGTextureHandle NextOwner;
    // 빌더에 등록된 핸들입니다.
    FRDGTextureHandle Handle;

    // 풀의 텍스처.
    IPooledRenderTarget* PooledRenderTarget = nullptr;
    FRDGPooledTexture* PooledTexture = nullptr;
    // 풀 텍스처 캐시의 상태 포인터
    FRDGTextureSubresourceState* State = nullptr;
    // 강력한 레퍼런스를 보유할 때 엄격하게 유효합니다.
    TRefCountPtr<IPooledRenderTarget> Allocation;

    // 그래프 작성 시 병합된 하위 리소스의 상태 추적하기
    FRDGTextureTransientSubresourceStateIndirect MergeState;
    // 그래프를 구성하는 동안 각 하위 리소스를 통과하는 프로듀서를 추적합니다 .
    TRDGTextureSubresourceArray<FRDGPassHandle> LastProducers;
};

// 풀링된 버퍼.
class RENDERCORE_API FRDGPooledBuffer
{
public:
    const FRDGBufferDesc Desc;

    FRHIUnorderedAccessView* GetOrCreateUAV(FRDGBufferUAVDesc UAVDesc);
    FRHIShaderResourceView* GetOrCreateSRV(FRDGBufferSRVDesc SRVDesc);

    FRHIVertexBuffer* GetVertexBufferRHI() const;
    FRHIIndexBuffer* GetIndexBufferRHI() const;
    FRHIStructuredBuffer* GetStructuredBufferRHI() const;

    uint32 GetRefCount() const;
    uint32 AddRef() const;
    uint32 Release() const;
    
    (......)

private:
    FRDGPooledBuffer(const FRDGBufferDesc& InDesc);

    // 버텍스/인덱스/구조 버퍼링 .
    FVertexBufferRHIRef VertexBuffer;
    FIndexBufferRHIRef IndexBuffer;
    FStructuredBufferRHIRef StructuredBuffer;
    // UAV/SRV.
    TMap<FRDGBufferUAVDesc, FUnorderedAccessViewRHIRef, FDefaultSetAllocator, TUAVFuncs<FRDGBufferUAVDesc, FUnorderedAccessViewRHIRef>> UAVs;
    TMap<FRDGBufferSRVDesc, FShaderResourceViewRHIRef, FDefaultSetAllocator, TSRVFuncs<FRDGBufferSRVDesc, FShaderResourceViewRHIRef>> SRVs;

    void Reset();
    void Finalize();
        
    const TCHAR* Name = nullptr;

    // 소유자.
    FRDGBufferRef Owner = nullptr;
    FRDGSubresourceState State;

    mutable uint32 RefCount = 0;
    uint32 LastUsedFrame = 0;
};

// 렌더링 트래킹을 위한 버퍼.
class RENDERCORE_API FRDGBuffer final : public FRDGParentResource
{
public:
    const FRDGBufferDesc Desc;
    const ERDGBufferFlags Flags;

    //////////////////////////////////////////////////////////////////////////
    //! The following methods may only be called during pass execution.

    // RHI 리소스에 액세스합니다.
    FRHIVertexBuffer* GetIndirectRHICallBuffer() const
    FRHIVertexBuffer* GetRHIVertexBuffer() const
    FRHIStructuredBuffer* GetRHIStructuredBuffer() const

    //////////////////////////////////////////////////////////////////////////

private:
    FRDGBuffer(const TCHAR* InName, const FRDGBufferDesc& InDesc, ERDGBufferFlags InFlags);

    // RHI 리소스 설정하기.
    void SetRHI(FRDGPooledBuffer* InPooledBuffer, FRDGBufferRef& OutPreviousOwner);
    void Finalize();

    FRDGSubresourceState& GetState() const
    // RDG 핸들.
    FRDGBufferHandle Handle;
    // 이 리소스를 마지막으로 처리한 사람입니다.
    FRDGPassHandle LastProducer;
    // 다음 소유자.
    FRDGBufferHandle NextOwner;

    // 주어진 풀링 버퍼 .
    FRDGPooledBuffer* PooledBuffer = nullptr;
    // 하위 리소스 상태.
    FRDGSubresourceState* State = nullptr;
    TRefCountPtr<FRDGPooledBuffer> Allocation;
    FRDGSubresourceState* MergeState = nullptr;
};

(......)

RDG 시스템에서는 기본적으로 모든 RHI 리소스를 캡슐화하고 래핑하여 수명 주기, 참조 관계, 디버깅 정보 등을 정밀하게 제어하고 관리하여 렌더링 성능을 개선하기 위해 최적화하고 맞춤화할 수 있습니다.
 

11.2.3 RDG Pass

RDG 패스 모듈은 베리어, 리소스 변환 및 RDGPass의 개념을 다룹니다:

// Engine\Source\Runtime\RHI\Public\RHI.h

// RHI에서 보류 중인 리소스 전환을 나타내기 위한 불투명한 데이터 구조입니다.
struct FRHITransition
{
public:
    template <typename T>
    inline T* GetPrivateData()
    {
        uintptr_t Addr = Align(uintptr_t(this + 1), GRHITransitionPrivateData_AlignInBytes);
        return reinterpret_cast<T*>(Addr);
    }

    template <typename T>
    inline const T* GetPrivateData() const
    {
        return const_cast<FRHITransition*>(this)->GetPrivateData<T>();
    }

private:
    FRHITransition(const FRHITransition&) = delete;
    FRHITransition(FRHITransition&&) = delete;
    FRHITransition(ERHIPipeline SrcPipelines, ERHIPipeline DstPipelines);
    ~FRHITransition();

    // 할당된 총 크기를 가져옵니다.
    static uint64 GetTotalAllocationSize()
    // 정렬된 바이트 수를 가져옵니다.
    static uint64 GetAlignment();

    // 마킹을 시작합니다.
    inline void MarkBegin(ERHIPipeline Pipeline) const
    {
        int8 Mask = int8(Pipeline);
        int8 PreviousValue = FPlatformAtomics::InterlockedAnd(&State, ~Mask);
        if (PreviousValue == Mask)
        {
            Cleanup();
        }
    }
    // 종료 마커.
    inline void MarkEnd(ERHIPipeline Pipeline) const
    {
        int8 Mask = int8(Pipeline) << int32(ERHIPipeline::Num);
        int8 PreviousValue = FPlatformAtomics::InterlockedAnd(&State, ~Mask);
        if (PreviousValue == Mask)
        {
            Cleanup();
        }
    }
    // RHI 변환 및 할당된 메모리를 포함한 변환 리소스를 정리합니다.
    inline void Cleanup() const;

    mutable int8 State;

#if DO_CHECK
    mutable ERHIPipeline AllowedSrc;
    mutable ERHIPipeline AllowedDst;
#endif

#if ENABLE_RHI_VALIDATION
    // Fence.
    RHIValidation::FFence* Fence = nullptr;
    // 보류 중인 작업 시작
    RHIValidation::FOperationsList PendingOperationsBegin;
    // 보류 중인 작업의 종료입니다.
    RHIValidation::FOperationsList PendingOperationsEnd;
#endif
};

// Engine\Source\Runtime\RenderCore\Public\RenderGraphPass.h

// RDG 배리어 로트
class RENDERCORE_API FRDGBarrierBatch
{
public:
    FRDGBarrierBatch(const FRDGBarrierBatch&) = delete;
    bool IsSubmitted() const
    FString GetName() const;

protected:
    FRDGBarrierBatch(const FRDGPass* InPass, const TCHAR* InName);
    void SetSubmitted();
    ERHIPipeline GetPipeline() const

private:
    bool bSubmitted = false;
    // 그래픽 또는 비동기 컴퓨트
    ERHIPipeline Pipeline;

#if RDG_ENABLE_DEBUG
    const FRDGPass* Pass;
    const TCHAR* Name;
#endif
};

// 배리어 배치 시작
class RENDERCORE_API FRDGBarrierBatchBegin final : public FRDGBarrierBatch
{
public:
    FRDGBarrierBatchBegin(const FRDGPass* InPass, const TCHAR* InName, TOptional<ERHIPipeline> InOverridePipelineForEnd = {});
    ~FRDGBarrierBatchBegin();

    // 리소스를 배치로 전환하는 작업이 증가했습니다.
    void AddTransition(FRDGParentResourceRef Resource, const FRHITransitionInfo& Info);

    const FRHITransition* GetTransition() const;
    bool IsTransitionValid() const;
    void SetUseCrossPipelineFence();
    // 베리어 제출/리소스 전환.
    void Submit(FRHIComputeCommandList& RHICmdList);

private:
    TOptional<ERHIPipeline> OverridePipelineToEnd;
    bool bUseCrossPipelineFence = false;

    // 커밋 후 저장된 리소스 변환으로, 배치가 완료되면 다시 null로 할당됩니다.
    const FRHITransition* Transition = nullptr;
    // 수행할 비동기 리소스 변환의 배열입니다.
    TArray<FRHITransitionInfo, TInlineAllocator<1, SceneRenderingAllocator>> Transitions;

#if RDG_ENABLE_DEBUG
    // 디버깅 목적으로만 사용되는 트랜지션 배열과 일치하는 RDG 리소스 배열입니다.
    TArray<FRDGParentResource*, SceneRenderingAllocator> Resources;
#endif
};

// 배리어 로트 끝
class RENDERCORE_API FRDGBarrierBatchEnd final : public FRDGBarrierBatch
{
public:
    FRDGBarrierBatchEnd(const FRDGPass* InPass, const TCHAR* InName);
    ~FRDGBarrierBatchEnd();

    // 예약 메모리.
    void ReserveMemory(uint32 ExpectedDependencyCount);
    // 시작 배치에 종속성을 삽입하면 시작 배치가 둘 이상의 종료 배치에 삽입될 수 있습니다.
    void AddDependency(FRDGBarrierBatchBegin* BeginBatch);
    // 전환을 위한 리소스 제출.
    void Submit(FRHIComputeCommandList& RHICmdList);

private:
    // 이 최종 배치는 배치 완료 후 리콜하여 배치 변환을 시작할 수 있습니다.
    TArray<FRDGBarrierBatchBegin*, TInlineAllocator<1, SceneRenderingAllocator>> Dependencies;
};

// RGD 채널 기본 클래스.
class RENDERCORE_API FRDGPass
{
public:
    FRDGPass(FRDGEventName&& InName, FRDGParameterStruct InParameterStruct, ERDGPassFlags InFlags);
    FRDGPass(const FRDGPass&) = delete;
    virtual ~FRDGPass() = default;

    // 채널 데이터 인터페이스.
    const TCHAR* GetName() const;
    FORCEINLINE const FRDGEventName& GetEventName() const;
    FORCEINLINE ERDGPassFlags GetFlags() const;
    FORCEINLINE ERHIPipeline GetPipeline() const;
    // RDG 패스 매개변수.
    FORCEINLINE FRDGParameterStruct GetParameters() const;
    FORCEINLINE FRDGPassHandle GetHandle() const;
    bool IsMergedRenderPassBegin() const;
    bool IsMergedRenderPassEnd() const;
    bool SkipRenderPassBegin() const;
    bool SkipRenderPassEnd() const;
    bool IsAsyncCompute() const;
    bool IsAsyncComputeBegin() const;
    bool IsAsyncComputeEnd() const;
    bool IsGraphicsFork() const;
    bool IsGraphicsJoin() const;
    // 프로듀서 핸들.
    const FRDGPassHandleArray& GetProducers() const;
    // 파이프라인 횡단 생산자
    FRDGPassHandle GetCrossPipelineProducer() const;
    // 크로스 파이프라인 소비자.
    FRDGPassHandle GetCrossPipelineConsumer() const;
    // 포크 패스.
    FRDGPassHandle GetGraphicsForkPass() const;
    // 패스 통합.
    FRDGPassHandle GetGraphicsJoinPass() const;
    
#if RDG_CPU_SCOPES
    FRDGCPUScopes GetCPUScopes() const;
#endif
#if RDG_GPU_SCOPES
    FRDGGPUScopes GetGPUScopes() const;
#endif

private:
    // 사전 시퀀스 장벽.
    FRDGBarrierBatchBegin& GetPrologueBarriersToBegin(FRDGAllocator& Allocator);
    FRDGBarrierBatchEnd& GetPrologueBarriersToEnd(FRDGAllocator& Allocator);
    // 포스트 시퀀스 장벽.
    FRDGBarrierBatchBegin& GetEpilogueBarriersToBeginForGraphics(FRDGAllocator& Allocator);
    FRDGBarrierBatchBegin& GetEpilogueBarriersToBeginForAsyncCompute(FRDGAllocator& Allocator);
    FRDGBarrierBatchBegin& GetEpilogueBarriersToBeginFor(FRDGAllocator& Allocator, ERHIPipeline PipelineForEnd);

    //////////////////////////////////////////////////////////////////////////
    //! User Methods to Override

    // 구현.
    virtual void ExecuteImpl(FRHIComputeCommandList& RHICmdList) = 0;

    //////////////////////////////////////////////////////////////////////////

    // 구현.
    void Execute(FRHIComputeCommandList& RHICmdList);

    // 데이터를 전달합니다.
    const FRDGEventName Name;
    const FRDGParameterStruct ParameterStruct;
    const ERDGPassFlags Flags;
    const ERHIPipeline Pipeline;
    FRDGPassHandle Handle;

    // 합격 표시.
    union
    {
        struct
        {
            uint32 bSkipRenderPassBegin : 1;
            uint32 bSkipRenderPassEnd : 1;
            uint32 bAsyncComputeBegin : 1;
            uint32 bAsyncComputeEnd : 1;
            uint32 bAsyncComputeEndExecute : 1;
            uint32 bGraphicsFork : 1;
            uint32 bGraphicsJoin : 1;
            uint32 bUAVAccess : 1;
            IF_RDG_ENABLE_DEBUG(uint32 bFirstTextureAllocated : 1);
        };
        uint32 PackedBits = 0;
    };

    // 최신 크로스 파이프라인 생산자 핸들.
    FRDGPassHandle CrossPipelineProducer;
    // 가장 빠른 크로스 파이프라인 소비자의 핸들입니다.
    FRDGPassHandle CrossPipelineConsumer;

    // (비동기 컴퓨트 전용) 비동기 컴퓨트 간격의 포크/조인인 그래픽 패스입니다.
    FRDGPassHandle GraphicsForkPass;
    FRDGPassHandle GraphicsJoinPass;

    // 이 채널의 프리앰블/후속 장벽을 처리하는 채널입니다.
    FRDGPassHandle PrologueBarrierPass;
    FRDGPassHandle EpilogueBarrierPass;

    // 생산자 패스 목록.
    FRDGPassHandleArray Producers;

    // 텍스처 상태.
    struct FTextureState
    {
        FRDGTextureTransientSubresourceState State;
        FRDGTextureTransientSubresourceStateIndirect MergeState;
        uint16 ReferenceCount = 0;
    };

    // 버퍼 상태.
    struct FBufferState
    {
        FRDGSubresourceState State;
        FRDGSubresourceState* MergeState = nullptr;
        uint16 ReferenceCount = 0;
    };

    // 텍스처/버퍼를 Pass에서 어떻게 사용되는지에 대한 정보에 매핑합니다.  
    TSortedMap<FRDGTexture*, FTextureState, SceneRenderingAllocator> TextureStates;
    TSortedMap<FRDGBuffer*, FBufferState, SceneRenderingAllocator> BufferStates;
    // 이 패스를 실행하는 동안 시작하도록 예약된 패스 매개변수 목록입니다.
    TArray<FRDGPass*, TInlineAllocator<1, SceneRenderingAllocator>> ResourcesToBegin;
    TArray<FRDGPass*, TInlineAllocator<1, SceneRenderingAllocator>> ResourcesToEnd;
    // 획득이 완료된 *후* 획득한 텍스처 목록과 *폐기하기 전에 획득한 텍스처 목록입니다. 획득은 할당된 모든 텍스처에 적용됩니다.
    TArray<FRHITexture*, SceneRenderingAllocator> TexturesToAcquire;
    // 패스가 *완료된 후* 버려지는 텍스처 목록은 *취득(획득)* 후 *취득(획득)*됩니다. 버리기는 일시적인 것으로 표시된 텍스처에만 적용되며, 텍스처의 마지막 별칭(앨리아)은 자동 버리기 동작을 사용합니다(사용자에게 또는 풀로 다시 깔끔하게 전환할 수 있도록 지원하기 위해).
    TArray<FRHITexture*, SceneRenderingAllocator> TexturesToDiscard;

    FRDGBarrierBatchBegin* PrologueBarriersToBegin = nullptr;
    FRDGBarrierBatchEnd* PrologueBarriersToEnd = nullptr;
    FRDGBarrierBatchBegin* EpilogueBarriersToBeginForGraphics = nullptr;
    FRDGBarrierBatchBegin* EpilogueBarriersToBeginForAsyncCompute = nullptr;

    EAsyncComputeBudget AsyncComputeBudget = EAsyncComputeBudget::EAll_4;
};

// RDG 패스 람다 실행 함수.
template <typename ParameterStructType, typename ExecuteLambdaType>
class TRDGLambdaPass : public FRDGPass
{
    (......)

    TRDGLambdaPass(FRDGEventName&& InName, const ParameterStructType* InParameterStruct, ERDGPassFlags InPassFlags, ExecuteLambdaType&& InExecuteLambda);

private:
    // 구현.
    void ExecuteImpl(FRHIComputeCommandList& RHICmdList) override
    {
        check(!kSupportsRaster || RHICmdList.IsImmediate());
        // 람다 인스턴스 호출하기.
        ExecuteLambda(static_cast<TRHICommandList&>(RHICmdList));
    }

    람다 예제.
    ExecuteLambdaType ExecuteLambda;
};

// 빈 람다로 전달합니다.
template <typename ExecuteLambdaType>
class TRDGEmptyLambdaPass : public TRDGLambdaPass<FEmptyShaderParameters, ExecuteLambdaType>
{
public:
    TRDGEmptyLambdaPass(FRDGEventName&& InName, ERDGPassFlags InPassFlags, ExecuteLambdaType&& InExecuteLambda);

private:
    FEmptyShaderParameters EmptyShaderParameters;
};

// 사전/사후 주문 패스에 사용됩니다.
class FRDGSentinelPass final : public FRDGPass
{
public:
    FRDGSentinelPass(FRDGEventName&& Name);

private:
    void ExecuteImpl(FRHIComputeCommandList&) override;
    FEmptyShaderParameters EmptyShaderParameters;
};

위의 내용은 RDG의 패스가 더 복잡하며 소비자, 생산자, 변환 종속성, 다양한 유형의 리소스 상태 등의 데이터와 처리를 포함하는 RDG 시스템에서 가장 핵심적인 유형 중 하나라는 것을 보여줍니다.RDG의 패스에는 다음과 같은 몇 가지 유형이 있습니다:
 
 
 

11.2.3 RDG Pass

RDG Pass 모듈은 베리어, 리소스 변환, RDG Pass 등의 개념을 다루고 있습니다.

// Engine\Source\Runtime\RHI\Public\RHI.h

// RHI에서 보류 중인 리소스 전환을 나타내기 위한 불투명한 데이터 구조입니다.
struct FRHITransition
{
public:
    template <typename T>
    inline T* GetPrivateData()
    {
        uintptr_t Addr = Align(uintptr_t(this + 1), GRHITransitionPrivateData_AlignInBytes);
        return reinterpret_cast<T*>(Addr);
    }

    template <typename T>
    inline const T* GetPrivateData() const
    {
        return const_cast<FRHITransition*>(this)->GetPrivateData<T>();
    }

private:
    FRHITransition(const FRHITransition&) = delete;
    FRHITransition(FRHITransition&&) = delete;
    FRHITransition(ERHIPipeline SrcPipelines, ERHIPipeline DstPipelines);
    ~FRHITransition();

    // 할당된 총 크기를 가져옵니다.
    static uint64 GetTotalAllocationSize()
    // 정렬된 바이트 수를 가져옵니다.
    static uint64 GetAlignment();

    // 마킹을 시작합니다.
    inline void MarkBegin(ERHIPipeline Pipeline) const
    {
        int8 Mask = int8(Pipeline);
        int8 PreviousValue = FPlatformAtomics::InterlockedAnd(&State, ~Mask);
        if (PreviousValue == Mask)
        {
            Cleanup();
        }
    }
    // 종료 마커.
    inline void MarkEnd(ERHIPipeline Pipeline) const
    {
        int8 Mask = int8(Pipeline) << int32(ERHIPipeline::Num);
        int8 PreviousValue = FPlatformAtomics::InterlockedAnd(&State, ~Mask);
        if (PreviousValue == Mask)
        {
            Cleanup();
        }
    }
    // RHI 변환 및 할당된 메모리를 포함한 변환 리소스를 정리합니다.
    inline void Cleanup() const;

    mutable int8 State;

#if DO_CHECK
    mutable ERHIPipeline AllowedSrc;
    mutable ERHIPipeline AllowedDst;
#endif

#if ENABLE_RHI_VALIDATION
    // Fence.
    RHIValidation::FFence* Fence = nullptr;
    // 교수형 시작 작업.
    RHIValidation::FOperationsList PendingOperationsBegin;
    // 보류 중인 작업의 종료입니다.
    RHIValidation::FOperationsList PendingOperationsEnd;
#endif
};

// Engine\Source\Runtime\RenderCore\Public\RenderGraphPass.h

// RDG 배리어 로트
class RENDERCORE_API FRDGBarrierBatch
{
public:
    FRDGBarrierBatch(const FRDGBarrierBatch&) = delete;
    bool IsSubmitted() const
    FString GetName() const;

protected:
    FRDGBarrierBatch(const FRDGPass* InPass, const TCHAR* InName);
    void SetSubmitted();
    ERHIPipeline GetPipeline() const

private:
    bool bSubmitted = false;
    // 그래픽 또는 비동기 컴퓨트
    ERHIPipeline Pipeline;

#if RDG_ENABLE_DEBUG
    const FRDGPass* Pass;
    const TCHAR* Name;
#endif
};

// 배리어 배치 시작
class RENDERCORE_API FRDGBarrierBatchBegin final : public FRDGBarrierBatch
{
public:
    FRDGBarrierBatchBegin(const FRDGPass* InPass, const TCHAR* InName, TOptional<ERHIPipeline> InOverridePipelineForEnd = {});
    ~FRDGBarrierBatchBegin();

    // 리소스를 배치로 전환하는 작업이 증가했습니다.
    void AddTransition(FRDGParentResourceRef Resource, const FRHITransitionInfo& Info);

    const FRHITransition* GetTransition() const;
    bool IsTransitionValid() const;
    void SetUseCrossPipelineFence();
    // 제출 장벽/리소스 전환.
    void Submit(FRHIComputeCommandList& RHICmdList);

private:
    TOptional<ERHIPipeline> OverridePipelineToEnd;
    bool bUseCrossPipelineFence = false;

    // 커밋 후 저장된 리소스 변환으로, 배치가 완료되면 다시 null로 할당됩니다.
    const FRHITransition* Transition = nullptr;
    // 수행할 비동기 리소스 변환의 배열입니다.
    TArray<FRHITransitionInfo, TInlineAllocator<1, SceneRenderingAllocator>> Transitions;

#if RDG_ENABLE_DEBUG
    // 디버깅 목적으로만 사용되는 트랜지션 배열과 일치하는 RDG 리소스 배열입니다.
    TArray<FRDGParentResource*, SceneRenderingAllocator> Resources;
#endif
};

// 배리어 로트 끝
class RENDERCORE_API FRDGBarrierBatchEnd final : public FRDGBarrierBatch
{
public:
    FRDGBarrierBatchEnd(const FRDGPass* InPass, const TCHAR* InName);
    ~FRDGBarrierBatchEnd();

    // 예약 메모리.
    void ReserveMemory(uint32 ExpectedDependencyCount);
    // 시작 배치에 종속성을 삽입하면 시작 배치가 둘 이상의 종료 배치에 삽입될 수 있습니다.
    void AddDependency(FRDGBarrierBatchBegin* BeginBatch);
    // 리소스 전환 제출.
    void Submit(FRHIComputeCommandList& RHICmdList);

private:
    // 이 최종 배치는 배치 완료 후 리콜하여 배치 변환을 시작할 수 있습니다.
    TArray<FRDGBarrierBatchBegin*, TInlineAllocator<1, SceneRenderingAllocator>> Dependencies;
};

// RGD 채널 기본 클래스.
class RENDERCORE_API FRDGPass
{
public:
    FRDGPass(FRDGEventName&& InName, FRDGParameterStruct InParameterStruct, ERDGPassFlags InFlags);
    FRDGPass(const FRDGPass&) = delete;
    virtual ~FRDGPass() = default;

    // 채널 데이터 인터페이스.
    const TCHAR* GetName() const;
    FORCEINLINE const FRDGEventName& GetEventName() const;
    FORCEINLINE ERDGPassFlags GetFlags() const;
    FORCEINLINE ERHIPipeline GetPipeline() const;
    // RDG 패스 매개변수.
    FORCEINLINE FRDGParameterStruct GetParameters() const;
    FORCEINLINE FRDGPassHandle GetHandle() const;
    bool IsMergedRenderPassBegin() const;
    bool IsMergedRenderPassEnd() const;
    bool SkipRenderPassBegin() const;
    bool SkipRenderPassEnd() const;
    bool IsAsyncCompute() const;
    bool IsAsyncComputeBegin() const;
    bool IsAsyncComputeEnd() const;
    bool IsGraphicsFork() const;
    bool IsGraphicsJoin() const;
    // 프로듀서 핸들.
    const FRDGPassHandleArray& GetProducers() const;
    // 크로스 파이프라인 프로듀서.
    FRDGPassHandle GetCrossPipelineProducer() const;
    // 크로스 파이프라인 소비자.
    FRDGPassHandle GetCrossPipelineConsumer() const;
    // 포크 패스.
    FRDGPassHandle GetGraphicsForkPass() const;
    // 패스 통합.
    FRDGPassHandle GetGraphicsJoinPass() const;
    
#if RDG_CPU_SCOPES
    FRDGCPUScopes GetCPUScopes() const;
#endif
#if RDG_GPU_SCOPES
    FRDGGPUScopes GetGPUScopes() const;
#endif

private:
    // 사전 시퀀스 베리어.
    FRDGBarrierBatchBegin& GetPrologueBarriersToBegin(FRDGAllocator& Allocator);
    FRDGBarrierBatchEnd& GetPrologueBarriersToEnd(FRDGAllocator& Allocator);
    // 포스트 시퀀스 베리어.
    FRDGBarrierBatchBegin& GetEpilogueBarriersToBeginForGraphics(FRDGAllocator& Allocator);
    FRDGBarrierBatchBegin& GetEpilogueBarriersToBeginForAsyncCompute(FRDGAllocator& Allocator);
    FRDGBarrierBatchBegin& GetEpilogueBarriersToBeginFor(FRDGAllocator& Allocator, ERHIPipeline PipelineForEnd);

    //////////////////////////////////////////////////////////////////////////
    //! User Methods to Override

    // 구현.
    virtual void ExecuteImpl(FRHIComputeCommandList& RHICmdList) = 0;

    //////////////////////////////////////////////////////////////////////////

    // 구현.
    void Execute(FRHIComputeCommandList& RHICmdList);

    // 데이터를 전달합니다.
    const FRDGEventName Name;
    const FRDGParameterStruct ParameterStruct;
    const ERDGPassFlags Flags;
    const ERHIPipeline Pipeline;
    FRDGPassHandle Handle;

    // 합격 표시.
    union
    {
        struct
        {
            uint32 bSkipRenderPassBegin : 1;
            uint32 bSkipRenderPassEnd : 1;
            uint32 bAsyncComputeBegin : 1;
            uint32 bAsyncComputeEnd : 1;
            uint32 bAsyncComputeEndExecute : 1;
            uint32 bGraphicsFork : 1;
            uint32 bGraphicsJoin : 1;
            uint32 bUAVAccess : 1;
            IF_RDG_ENABLE_DEBUG(uint32 bFirstTextureAllocated : 1);
        };
        uint32 PackedBits = 0;
    };

    // 최신 크로스 파이프라인 프로듀서 핸들.
    FRDGPassHandle CrossPipelineProducer;
    // 가장 빠른 크로스 파이프라인 소비자의 핸들입니다.
    FRDGPassHandle CrossPipelineConsumer;

    // (비동기 컴퓨트 전용) 비동기 컴퓨트 간격의 포크/조인인 그래픽 패스입니다.
    FRDGPassHandle GraphicsForkPass;
    FRDGPassHandle GraphicsJoinPass;

    // 이 채널의 프리앰블/후속 장벽을 처리하는 채널입니다.
    FRDGPassHandle PrologueBarrierPass;
    FRDGPassHandle EpilogueBarrierPass;

    // 프로듀서 패스 목록.
    FRDGPassHandleArray Producers;

    // 텍스처 상태.
    struct FTextureState
    {
        FRDGTextureTransientSubresourceState State;
        FRDGTextureTransientSubresourceStateIndirect MergeState;
        uint16 ReferenceCount = 0;
    };

    // 버퍼 상태.
    struct FBufferState
    {
        FRDGSubresourceState State;
        FRDGSubresourceState* MergeState = nullptr;
        uint16 ReferenceCount = 0;
    };

    // 텍스처/버퍼를 Pass에서 어떻게 사용되는지에 대한 정보에 매핑합니다. 
    TSortedMap<FRDGTexture*, FTextureState, SceneRenderingAllocator> TextureStates;
    TSortedMap<FRDGBuffer*, FBufferState, SceneRenderingAllocator> BufferStates;
    // 이 패스를 실행하는 동안 시작하도록 예약된 패스 매개변수 목록입니다.
    TArray<FRDGPass*, TInlineAllocator<1, SceneRenderingAllocator>> ResourcesToBegin;
    TArray<FRDGPass*, TInlineAllocator<1, SceneRenderingAllocator>> ResourcesToEnd;
    // 획득이 완료된 *후* 획득한 텍스처 목록과 *폐기하기 전에 획득한 텍스처 목록입니다. 획득은 할당된 모든 텍스처에 적용됩니다.
    TArray<FRHITexture*, SceneRenderingAllocator> TexturesToAcquire;
    // 패스가 *완료된 후* 버려지는 텍스처 목록은 *취득(획득)* 후 *취득(획득)*됩니다. 버리기는 일시적인 것으로 표시된 텍스처에만 적용되며, 텍스처의 마지막 별칭(앨리아)은 자동 버리기 동작을 사용합니다(사용자에게 또는 풀로 다시 깔끔하게 전환할 수 있도록 지원하기 위해).
    TArray<FRHITexture*, SceneRenderingAllocator> TexturesToDiscard;

    FRDGBarrierBatchBegin* PrologueBarriersToBegin = nullptr;
    FRDGBarrierBatchEnd* PrologueBarriersToEnd = nullptr;
    FRDGBarrierBatchBegin* EpilogueBarriersToBeginForGraphics = nullptr;
    FRDGBarrierBatchBegin* EpilogueBarriersToBeginForAsyncCompute = nullptr;

    EAsyncComputeBudget AsyncComputeBudget = EAsyncComputeBudget::EAll_4;
};

// RDG 패스 람다 실행 함수.
template <typename ParameterStructType, typename ExecuteLambdaType>
class TRDGLambdaPass : public FRDGPass
{
    (......)

    TRDGLambdaPass(FRDGEventName&& InName, const ParameterStructType* InParameterStruct, ERDGPassFlags InPassFlags, ExecuteLambdaType&& InExecuteLambda);

private:
    // 구현.
    void ExecuteImpl(FRHIComputeCommandList& RHICmdList) override
    {
        check(!kSupportsRaster || RHICmdList.IsImmediate());
        // 람다 인스턴스 호출하기.
        ExecuteLambda(static_cast<TRHICommandList&>(RHICmdList));
    }

    람다 예제.
    ExecuteLambdaType ExecuteLambda;
};

// 빈 람다로 전달합니다.
template <typename ExecuteLambdaType>
class TRDGEmptyLambdaPass : public TRDGLambdaPass<FEmptyShaderParameters, ExecuteLambdaType>
{
public:
    TRDGEmptyLambdaPass(FRDGEventName&& InName, ERDGPassFlags InPassFlags, ExecuteLambdaType&& InExecuteLambda);

private:
    FEmptyShaderParameters EmptyShaderParameters;
};

// 선주문/후주문 패스에 사용.
class FRDGSentinelPass final : public FRDGPass
{
public:
    FRDGSentinelPass(FRDGEventName&& Name);

private:
    void ExecuteImpl(FRHIComputeCommandList&) override;
    FEmptyShaderParameters EmptyShaderParameters;
};

위의 내용은 RDG의 Pass가 더 복잡함을 보여주며 소비자, 생산자, 전환 의존성, 다양한 리소스 상태 및 기타 데이터 및 처리를 포함하는 RDG 시스템의 가장 핵심적인 유형 중 하나입니다. RDG의 패스는 다음과 같은 유형이 있습니다.

RDG 패스와 렌더링 패스는 일대일 대응 관계가 아닙니다. 여러 개의 렌더링 패스가 하나로 통합될 수 있습니다. 자세한 내용은 다음 장을 참조하십시오. RDG 패스는 다중 스레드 처리, 리소스 상태 변환 및 의존 처리에서 가장 복잡합니다. 그러나 이 섹션에서는 다루지 않고 나중에 자세히 설명합니다.

11.2.4 FRDGBuilder

FRDBuilder는 RDG 아키텍처의 핵심이자 엔진이며 렌더링 패스와 매개 변수 수집, 패스, 데이터, 리소스 종속성 처리, 데이터 맞춤화 및 최적화, 실행 인터페이스 제공을 담당하는 대형 스튜어드입니다. 여기에는 다음과 같은 내용이 명시되어 있습니다:

class RENDERCORE_API FRDGBuilder
{
public:
    FRDGBuilder(FRHICommandListImmediate& InRHICmdList, FRDGEventName InName = {}, const char* UnaccountedCSVStat = kDefaultUnaccountedCSVStat);
    FRDGBuilder(const FRDGBuilder&) = delete;

    // 외부 텍스처를 찾고, 찾지 못하면 null을 반환합니다.
    FRDGTextureRef FindExternalTexture(FRHITexture* Texture) const;
    FRDGTextureRef FindExternalTexture(IPooledRenderTarget* ExternalPooledTexture, ERenderTargetTexture Texture) const;

    // RDG가 추적할 수 있도록 외부 풀 RT를 RDG에 등록합니다. 풀 RT에는 MSAA와 비 MSAA의 두 가지 유형의 RHI 텍스처가 포함될 수 있습니다.
    FRDGTextureRef RegisterExternalTexture(
        const TRefCountPtr<IPooledRenderTarget>& ExternalPooledTexture,
        ERenderTargetTexture Texture = ERenderTargetTexture::ShaderResource,
        ERDGTextureFlags Flags = ERDGTextureFlags::None);
    FRDGTextureRef RegisterExternalTexture(
        const TRefCountPtr<IPooledRenderTarget>& ExternalPooledTexture,
        const TCHAR* NameIfNotRegistered,
        ERenderTargetTexture RenderTargetTexture = ERenderTargetTexture::ShaderResource,
        ERDGTextureFlags Flags = ERDGTextureFlags::None);

    // RDG에서 추적할 수 있도록 외부 버퍼를 RDG에 등록합니다.
    FRDGBufferRef RegisterExternalBuffer(const TRefCountPtr<FRDGPooledBuffer>& ExternalPooledBuffer, ERDGBufferFlags Flags = ERDGBufferFlags::None);
    FRDGBufferRef RegisterExternalBuffer(const TRefCountPtr<FRDGPooledBuffer>& ExternalPooledBuffer, ERDGBufferFlags Flags, ERHIAccess AccessFinal);
    FRDGBufferRef RegisterExternalBuffer(
        const TRefCountPtr<FRDGPooledBuffer>& ExternalPooledBuffer,
        const TCHAR* NameIfNotRegistered,
        ERDGBufferFlags Flags = ERDGBufferFlags::None);

    // 리소스 생성 인터페이스.
    FRDGTextureRef CreateTexture(const FRDGTextureDesc& Desc, const TCHAR* Name, ERDGTextureFlags Flags = ERDGTextureFlags::None);
    FRDGBufferRef CreateBuffer(const FRDGBufferDesc& Desc, const TCHAR* Name, ERDGBufferFlags Flags = ERDGBufferFlags::None);
    FRDGTextureSRVRef CreateSRV(const FRDGTextureSRVDesc& Desc);
    FRDGBufferSRVRef CreateSRV(const FRDGBufferSRVDesc& Desc);
    FORCEINLINE FRDGBufferSRVRef CreateSRV(FRDGBufferRef Buffer, EPixelFormat Format);
    FRDGTextureUAVRef CreateUAV(const FRDGTextureUAVDesc& Desc, ERDGUnorderedAccessViewFlags Flags = ERDGUnorderedAccessViewFlags::None);
    FORCEINLINE FRDGTextureUAVRef CreateUAV(FRDGTextureRef Texture, ERDGUnorderedAccessViewFlags Flags = ERDGUnorderedAccessViewFlags::None);
    FRDGBufferUAVRef CreateUAV(const FRDGBufferUAVDesc& Desc, ERDGUnorderedAccessViewFlags Flags = ERDGUnorderedAccessViewFlags::None);
    FORCEINLINE FRDGBufferUAVRef CreateUAV(FRDGBufferRef Buffer, EPixelFormat Format, ERDGUnorderedAccessViewFlags Flags = ERDGUnorderedAccessViewFlags::None);
    template <typename ParameterStructType>
    TRDGUniformBufferRef<ParameterStructType> CreateUniformBuffer(ParameterStructType* ParameterStruct);

    // 수명 주기 동안 RDG가 관리하는 메모리를 할당합니다.
    void* Alloc(uint32 SizeInBytes, uint32 AlignInBytes);
    template <typename PODType>
    PODType* AllocPOD();
    template <typename ObjectType, typename... TArgs>
    ObjectType* AllocObject(TArgs&&... Args);
    template <typename ParameterStructType>
    ParameterStructType* AllocParameters();
    
    // 매개변수와 람다가 첨부된 패스를 추가합니다.
    template <typename ParameterStructType, typename ExecuteLambdaType>
    FRDGPassRef AddPass(FRDGEventName&& Name, const ParameterStructType* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);
    // 매개변수 없이 람다만 있는 패스를 추가합니다.
    template <typename ExecuteLambdaType>
    FRDGPassRef AddPass(FRDGEventName&& Name, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);

    // 빌더 실행이 끝나면 풀 텍스처를 지정된 포인터로 추출합니다. RDG로 생성된 리소스의 경우, 실행되고 포인터가 채워질 때까지 GPU 리소스의 수명이 연장됩니다. 지정하면 텍스처가 AccessFinal 상태로 전환되고, 그렇지 않으면 kDefaultAccessFinal 상태로 전환됩니다.
    void QueueTextureExtraction(FRDGTextureRef Texture, TRefCountPtr<IPooledRenderTarget>* OutPooledTexturePtr);
    void QueueTextureExtraction(FRDGTextureRef Texture, TRefCountPtr<IPooledRenderTarget>* OutPooledTexturePtr, ERHIAccess AccessFinal);

    // 빌더 실행이 끝나면 버퍼가 지정된 포인터로 추출됩니다.
    void QueueBufferExtraction(FRDGBufferRef Buffer, TRefCountPtr<FRDGPooledBuffer>* OutPooledBufferPtr);
    void QueueBufferExtraction(FRDGBufferRef Buffer, TRefCountPtr<FRDGPooledBuffer>* OutPooledBufferPtr, ERHIAccess AccessFinal);

    // 리소스 사전 할당. RDG로 생성된 리소스에 대해서만 기본 풀에 있는 리소스를 즉시 할당하여 외부 리소스로 효과적으로 승격시킵니다. 이렇게 하면 메모리 압박이 증가하지만 GetPooled{Texture, Buffer}를 사용하여 풀의 리소스를 쿼리할 수 있습니다. 주로 코드를 RDG로 점진적으로 포팅하는 데 사용됩니다.
    void PreallocateTexture(FRDGTextureRef Texture);
    void PreallocateBuffer(FRDGBufferRef Buffer);

    // 등록되거나 사전 할당된 리소스에 대해서만 허용되는 기본 리소스에 즉시 액세스할 수 있습니다.
    const TRefCountPtr<IPooledRenderTarget>& GetPooledTexture(FRDGTextureRef Texture) const;
    const TRefCountPtr<FRDGPooledBuffer>& GetPooledBuffer(FRDGBufferRef Buffer) const;

    // 실행 후 상태를 설정합니다.
    void SetTextureAccessFinal(FRDGTextureRef Texture, ERHIAccess Access);
    void SetBufferAccessFinal(FRDGBufferRef Buffer, ERHIAccess Access);

    void RemoveUnusedTextureWarning(FRDGTextureRef Texture);
    void RemoveUnusedBufferWarning(FRDGBufferRef Buffer);

    // 큐패스 수행, 렌더 타깃(RHI 렌더패스) 설정, 리소스 변환 및 큐 텍스처 추출을 관리합니다.
    void Execute();

    // 그래픽 리소스 풀의 프레임 단위 업데이트를 렌더링합니다.
    static void TickPoolElements();
    // RDG에서 사용하는 명령 목록입니다.
    FRHICommandListImmediate& RHICmdList;

private:
    static const ERHIAccess kDefaultAccessInitial = ERHIAccess::Unknown;
    static const ERHIAccess kDefaultAccessFinal = ERHIAccess::SRVMask;
    static const char* const kDefaultUnaccountedCSVStat;

    // RDG에서 사용하는 AsyncCompute 명령 목록입니다.
    FRHIAsyncComputeCommandListImmediate& RHICmdListAsyncCompute;
    FRDGAllocator Allocator;

    const FRDGEventName BuilderName;

    ERDGPassFlags OverridePassFlags(const TCHAR* PassName, ERDGPassFlags Flags, bool bAsyncComputeSupported);
    FORCEINLINE FRDGPassHandle GetProloguePassHandle() const;
    FORCEINLINE FRDGPassHandle GetEpiloguePassHandle() const;

    // RDG 개체 레지스트리.
    FRDGPassRegistry Passes;
    FRDGTextureRegistry Textures;
    FRDGBufferRegistry Buffers;
    FRDGViewRegistry Views;
    FRDGUniformBufferRegistry UniformBuffers;

    // 잘린 패스.
    FRDGPassBitArray PassesToCull;
    // 매개 변수 없이 전달합니다.
    FRDGPassBitArray PassesWithEmptyParameters;

    // 중복 제거를 위해 등록된 렌더링 맵 대응 요소에 대한 외부 리소스를 추적합니다.
    TSortedMap<FRHITexture*, FRDGTexture*, TInlineAllocator<4, SceneRenderingAllocator>> ExternalTextures;
    TSortedMap<const FRDGPooledBuffer*, FRDGBuffer*, TInlineAllocator<4, SceneRenderingAllocator>> ExternalBuffers;

    FRDGPass* ProloguePass = nullptr;
    FRDGPass* EpiloguePass = nullptr;

    // 추출할 리소스 목록입니다.
    TArray<TPair<FRDGTextureRef, TRefCountPtr<IPooledRenderTarget>*>, TInlineAllocator<4, SceneRenderingAllocator>> ExtractedTextures;
    TArray<TPair<FRDGBufferRef, TRefCountPtr<FRDGPooledBuffer>*>, TInlineAllocator<4, SceneRenderingAllocator>> ExtractedBuffers;
    // 재할당을 피하기 위해 여기에 저장되는 중간 작업용 텍스처 상태입니다.
    FRDGTextureTransientSubresourceStateIndirect ScratchTextureState;
    EAsyncComputeBudget AsyncComputeBudgetScope = EAsyncComputeBudget::EAll_4;

    // 편집.
    void Compile();
    // 클리어런스.
    void Clear();

    // 리소스 전환 시작.
    void BeginResourceRHI(FRDGUniformBuffer* UniformBuffer);
    void BeginResourceRHI(FRDGPassHandle, FRDGTexture* Texture);
    void BeginResourceRHI(FRDGPassHandle, FRDGTextureSRV* SRV);
    void BeginResourceRHI(FRDGPassHandle, FRDGTextureUAV* UAV);
    void BeginResourceRHI(FRDGPassHandle, FRDGBuffer* Buffer);
    void BeginResourceRHI(FRDGPassHandle, FRDGBufferSRV* SRV);
    void BeginResourceRHI(FRDGPassHandle, FRDGBufferUAV* UAV);

    // 리소스 전환 완료.
    void EndResourceRHI(FRDGPassHandle, FRDGTexture* Texture, uint32 ReferenceCount);
    void EndResourceRHI(FRDGPassHandle, FRDGBuffer* Buffer, uint32 ReferenceCount);

    // 패스 인터페이스.
    void SetupPassInternal(FRDGPass* Pass, FRDGPassHandle PassHandle, ERHIPipeline PassPipeline);
    void SetupPass(FRDGPass* Pass);
    void SetupEmptyPass(FRDGPass* Pass);
    void ExecutePass(FRDGPass* Pass);

    // 앞뒤로 전달합니다.
    void ExecutePassPrologue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass);
    void ExecutePassEpilogue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass);

    // 리소스 및 장벽 수집.
    void CollectPassResources(FRDGPassHandle PassHandle);
    void CollectPassBarriers(FRDGPassHandle PassHandle, FRDGPassHandle& LastUntrackedPassHandle);

    // 패스 종속성을 추가합니다.
    void AddPassDependency(FRDGPassHandle ProducerHandle, FRDGPassHandle ConsumerHandle);

    // 백 시퀀스 변환을 추가합니다.
    void AddEpilogueTransition(FRDGTextureRef Texture, FRDGPassHandle LastUntrackedPassHandle);
    void AddEpilogueTransition(FRDGBufferRef Buffer, FRDGPassHandle LastUntrackedPassHandle);

    // 일반 전환 추가.
    void AddTransition(FRDGPassHandle PassHandle, FRDGTextureRef Texture, const FRDGTextureTransientSubresourceStateIndirect& StateAfter, FRDGPassHandle LastUntrackedPassHandle);
    void AddTransition(FRDGPassHandle PassHandle, FRDGBufferRef Buffer, FRDGSubresourceState StateAfter, FRDGPassHandle LastUntrackedPassHandle);

    void AddTransitionInternal(
        FRDGParentResource* Resource,
        FRDGSubresourceState StateBefore,
        FRDGSubresourceState StateAfter,
        FRDGPassHandle LastUntrackedPassHandle,
        const FRHITransitionInfo& TransitionInfo);

    // 렌더링 패스 정보를 가져옵니다.
    FRHIRenderPassInfo GetRenderPassInfo(const FRDGPass* Pass) const;
    // 하위 리소스 할당.
    FRDGSubresourceState* AllocSubresource(const FRDGSubresourceState& Other);

#if RDG_ENABLE_DEBUG
    void VisualizePassOutputs(const FRDGPass* Pass);
    void ClobberPassOutputs(const FRDGPass* Pass);
#endif
};

RDG 시스템의 드라이버인 FRDGBuilder는 데이터 저장, 상태 전환 처리, 리소스 수명 주기 및 배리어 자동 관리, 유효하지 않은 리소스 트리밍, 패스 수집, 컴파일 및 실행, 텍스처 또는 버퍼 추출 등의 작업을 담당합니다. 내부 실행 메커니즘은 복잡하며 다음 장에서 자세히 분석할 것입니다.
 
 

11.3 RDG 메커니즘

이 섹션에서는 RDG의 작동 메커니즘, 프로세스 및 원리, 렌더링의 장점과 기능에 대해 중점적으로 설명합니다.
RDG 사용 방법만 배우려는 학생은 이 장을 건너뛰고 바로 11.4 RDG 개발로 넘어갈 수 있습니다.

11.3.1 RDG 메커니즘 개요

패스로 설계된 람다 범위를 설정하는 렌더링 종속성 그래프 프레임워크(RDGF)는 지연 실행을 사용하여 RHI에 GPU 명령을 내립니다. 패스는 FRDGBuilder::AddPass()를 사용하여 생성됩니다. 패스가 생성되면 Shader 파라미터를 받습니다. 모든 셰이더 파라미터가 될 수 있지만 프레임워크는 그래픽 리소스 렌더링에 가장 관심이 있습니다.

모든 패스 파라미터를 보유하는 구조체는 Lambda 실행이 지연되므로 올바른 수명 주기를 보장하기 위해 FRDGBuilder::AllocParameters()를 사용하여 할당해야 합니다.
FRDGBuilder::CreateTexture() 또는 FRDGBuilder::CreateBuffer()로 생성된 렌더링된 다이어그램 리소스는 리소스 디스크립터만 기록합니다. 리소스가 필요할 때 차트별로 할당됩니다. 렌더 맵은 리소스의 수명 주기를 추적하고 나머지 패스가 더 이상 참조하지 않을 때 메모리를 해제하고 재사용합니다.

렌더링 맵은 각 패스가 어떤 리소스를 사용하고 있는지 알아야 하므로 패스에 사용되는 모든 렌더링 맵 리소스는 FRDGBuilder::AddPass()에 지정된 Pass 파라미터에 포함되어야 합니다.
리소스는 패스가 실행될 때만 할당되도록 보장됩니다. 따라서 리소스에 대한 액세스는 FRDGBuilder::AddPass()를 사용하여 생성된 패스의 람다 범위 내에서만 수행해야 합니다. Pass에서 사용하는 리소스 중 일부를 나열하지 않으면 문제가 발생할 수 있습니다.

매개변수에서 Pass에 필요한 것보다 많은 그래프 리소스를 참조하면 해당 리소스의 수명 주기에 대한 그래프 정보가 인위적으로 증가하므로 참조하지 않는 것이 중요합니다. 이로 인해 메모리 사용량이 증가하거나 Pass가 중복 실행되지 않을 수 있습니다. 그 예로 셰이더에서 사용하지 않는 리소스 참조를 자동으로 지우는 ClearUnusedGraphResources() 함수를 들 수 있습니다. 리소스가 Pass에서 사용되지 않으면 경고가 표시됩니다.

람다 범위의 패스 실행은 FRDGBuilder::AddPass() 이후 언제든 발생할 수 있습니다. 디버깅을 위해 즉시 모드로 AddPass()에서 직접 발생할 수 있습니다. 패스 실행 중 오류가 발생하면 즉시 모드를 사용하면 오류의 원인이 될 수 있는 패스 설정이 포함된 호출 스택을 사용할 수 있습니다.즉시 모드는 명령줄 명령 -rdgimmediate 또는 콘솔 변수 r.RDG.ImmediateMode=1을 사용하여 활성화할 수 있습니다.

레거시 코드에서 생성된 풀 관리 리소스 텍스처 FPooledRenderTarget은 FRDGBuilder::RegisterExternalTexture()를 사용하여 렌더링 맵에서 사용할 수 있습니다.
패스 종속성에 대한 정보에 따라 메모리 압박 우선순위나 패스 GPU 동시 실행 우선순위 등 다양한 하드웨어 대상의 우선순위가 달라질 수 있습니다. 따라서 패스가 실행되는 순서는 보장되지 않으며, 패스가 실행되는 순서는 즉시 모드가 GPU에서 작업을 실행하는 것과 같은 방식으로 중간 리소스에서 작업이 실행될 것임을 보장할 뿐입니다.

렌더링 맵 패스는 외부 데이터 구조의 상태를 수정해서는 안 되는데, 이는 패스 실행 순서에 따라 경계 사례가 발생할 수 있기 때문입니다. 실행 완료 후에도 살아남는 렌더 맵 리소스(예: 뷰포트 백 버퍼, TAA 히스토리...)는 FRDGBuilder::QueueTextureExtraction()을 사용하여 추출해야 합니다. 패스가 추출할 리소스를 생성하거나 외부 텍스처를 수정하는 데 쓸모없는 것으로 감지되면 이 패스는 경고조차 실행하지 않을 수 있습니다.
강력한 기술적 이유(예: VR에서 한 번에 여러 뷰의 스테레오 렌더링)가 아니라면, 동일한 패스에 서로 다른 리소스에 대한 여러 작업을 번들로 묶지 마세요. 이렇게 하면 작업 집합에 더 많은 종속성이 생성되고 개별 작업에는 이러한 종속성의 일부만 필요할 수 있습니다. 스케줄러는 이러한 작업 중 일부를 다른 GPU 작업과 겹칠 수 있습니다. 또한 할당된 일시적 리소스를 더 오랜 시간 동안 유지하여 프레임 전체에서 최대 메모리 압력 피크가 증가할 수 있습니다.

AddPass()는 람다 범위의 실행이 지연될 것으로 예상하지만, 그렇다고 해서 람다 범위를 작성해야 한다는 의미는 아닙니다. 대부분의 경우 더 간단한 툴킷(예: FComputeShaderUtils, FPixelShaderUtils)을 사용하는 것으로 충분합니다.

11.3.2 FRDGBuilder::AddPass

FRDGBuilder::AddPass는 다음 로직으로 Pass 파라미터와 Lambda를 포함하는 Pass를 RDG 시스템에 추가하는 것입니다:

// Engine\Source\Runtime\RenderCore\Public\RenderGraphBuilder.inl

template <typename ParameterStructType, typename ExecuteLambdaType>
FRDGPassRef FRDGBuilder::AddPass(FRDGEventName&& Name, const ParameterStructType* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda)
{
    using LambdaPassType = TRDGLambdaPass<ParameterStructType, ExecuteLambdaType>;

    (......)

    // RDG 패스 인스턴스를 할당합니다.
    FRDGPass* Pass = Allocator.AllocObject<LambdaPassType>(
        MoveTemp(Name),
        ParameterStruct,
        OverridePassFlags(Name.GetTCHAR(), Flags, LambdaPassType::kSupportsAsyncCompute),
        MoveTemp(ExecuteLambda));

    // 패스 목록에 추가합니다.
    Passes.Insert(Pass);
    // 패스 설정.
    SetupPass(Pass);
    
    return Pass;
}

AddPass의 로직은 비교적 간단합니다. 들어오는 데이터에서 FRDGPass 인스턴스를 구성한 다음 목록에 추가하고 패스 데이터를 설정합니다. 다음은 SetupPass의 구체적인 로직입니다:
 

void FRDGBuilder::SetupPass(FRDGPass* Pass)
{
    // 패스 데이터 가져오기.
    const FRDGParameterStruct PassParameters = Pass->GetParameters();
    const FRDGPassHandle PassHandle = Pass->GetHandle();
    const ERDGPassFlags PassFlags = Pass->GetFlags();
    const ERHIPipeline PassPipeline = Pass->GetPipeline();

    bool bPassUAVAccess = false;

    // ---- 텍스처 상태를 처리합니다 ----
    
    Pass->TextureStates.Reserve(PassParameters.GetTextureParameterCount() + (PassParameters.HasRenderTargets() ? (MaxSimultaneousRenderTargets + 1) : 0));
    // 모든 텍스처를 반복하여 각 텍스처의 상태/데이터/레퍼런스 처리를 수행합니다.
    EnumerateTextureAccess(PassParameters, PassFlags, [&](FRDGViewRef TextureView, FRDGTextureRef Texture, ERHIAccess Access, FRDGTextureSubresourceRange Range)
    {
        const FRDGViewHandle NoUAVBarrierHandle = GetHandleIfNoUAVBarrier(TextureView);
        const EResourceTransitionFlags TransitionFlags = GetTextureViewTransitionFlags(TextureView, Texture);

        auto& PassState = Pass->TextureStates.FindOrAdd(Texture);
        PassState.ReferenceCount++;

        const bool bWholeTextureRange = Range.IsWholeResource(Texture->GetSubresourceLayout());
        bool bWholePassState = IsWholeResource(PassState.State);

        // Convert the pass state to subresource dimensionality if we've found a subresource range.
        if (!bWholeTextureRange && bWholePassState)
        {
            InitAsSubresources(PassState.State, Texture->Layout);
            bWholePassState = false;
        }

        const auto AddSubresourceAccess = [&](FRDGSubresourceState& State)
        {
            State.Access = MakeValidAccess(State.Access | Access);
            State.Flags |= TransitionFlags;
            State.NoUAVBarrierFilter.AddHandle(NoUAVBarrierHandle);
            State.Pipeline = PassPipeline;
        };

        if (bWholePassState)
        {
            AddSubresourceAccess(GetWholeResource(PassState.State));
        }
        else
        {
            EnumerateSubresourceRange(PassState.State, Texture->Layout, Range, AddSubresourceAccess);
        }

        bPassUAVAccess |= EnumHasAnyFlags(Access, ERHIAccess::UAVMask);
    });

    // ---- 처리 버퍼 상태 ----
    
    Pass->BufferStates.Reserve(PassParameters.GetBufferParameterCount());
    // 모든 버퍼를 반복하여 각 버퍼에서 상태/데이터/참조 처리를 수행합니다.
    EnumerateBufferAccess(PassParameters, PassFlags, [&](FRDGViewRef BufferView, FRDGBufferRef Buffer, ERHIAccess Access)
    {
        const FRDGViewHandle NoUAVBarrierHandle = GetHandleIfNoUAVBarrier(BufferView);

        auto& PassState = Pass->BufferStates.FindOrAdd(Buffer);
        PassState.ReferenceCount++;
        PassState.State.Access = MakeValidAccess(PassState.State.Access | Access);
        PassState.State.NoUAVBarrierFilter.AddHandle(NoUAVBarrierHandle);
        PassState.State.Pipeline = PassPipeline;

        bPassUAVAccess |= EnumHasAnyFlags(Access, ERHIAccess::UAVMask);
    });

    Pass->bUAVAccess = bPassUAVAccess;

    const bool bEmptyParameters = !Pass->TextureStates.Num() && !Pass->BufferStates.Num();
    PassesWithEmptyParameters.Add(bEmptyParameters);

    // 그래픽 파이프라인에서 Pass는 Pass의 자체 리소스를 시작/종료할 수 있습니다. 비동기 계산은 컴파일 중에 예약됩니다.
    if (PassPipeline == ERHIPipeline::Graphics && !bEmptyParameters)
    {
        Pass->ResourcesToBegin.Add(Pass);
        Pass->ResourcesToEnd.Add(Pass);
    }

    // 내부적으로 패스를 설정합니다.
    SetupPassInternal(Pass, PassHandle, PassPipeline);
}

SetupPassInternal의 구문 분석은 아래에서 계속됩니다:
 

void FRDGBuilder::SetupPassInternal(FRDGPass* Pass, FRDGPassHandle PassHandle, ERHIPipeline PassPipeline)
{
    // 다양한 패스를 각자의 핸들로 설정합니다.
    Pass->GraphicsJoinPass = PassHandle;
    Pass->GraphicsForkPass = PassHandle;
    Pass->PrologueBarrierPass = PassHandle;
    Pass->EpilogueBarrierPass = PassHandle;

    (......)

    // 즉시 모드이고 이월 주문 패스가 아닌 경우.
    if (GRDGImmediateMode && Pass != EpiloguePass)
    {
        // 그래프를 컴파일하지 않으므로 병합 상태를 통과 상태로 리디렉션하기만 하면 됩니다.
        
        // 텍스처의 병합 상태입니다.
        for (auto& TexturePair : Pass->TextureStates)
        {
            auto& PassState = TexturePair.Value;
            const uint32 SubresourceCount = PassState.State.Num();
            PassState.MergeState.SetNum(SubresourceCount);
            for (uint32 Index = 0; Index < SubresourceCount; ++Index)
            {
                if (PassState.State[Index].Access != ERHIAccess::Unknown)
                {
                    PassState.MergeState[Index] = &PassState.State[Index];
                    PassState.MergeState[Index]->SetPass(PassHandle);
                }
            }
        }
        // 버퍼의 병합 상태입니다.
        for (auto& BufferPair : Pass->BufferStates)
        {
            auto& PassState = BufferPair.Value;
            PassState.MergeState = &PassState.State;
            PassState.MergeState->SetPass(PassHandle);
        }

        FRDGPassHandle LastUntrackedPassHandle = GetProloguePassHandle();
        // 패스 리소스 모음.
        CollectPassResources(PassHandle);
        // 패스 장벽 수집.
        CollectPassBarriers(PassHandle, LastUntrackedPassHandle);
        // 패스를 직접 실행합니다.
        ExecutePass(Pass);
    }
}

요약하자면, AddPass는 들어오는 파라미터를 기반으로 RDG 패스 인스턴스를 빌드한 다음 해당 패스의 텍스처와 버퍼 데이터를 설정하고, 패스 등의 핸들에 대한 패스의 종속성 내부 설정을 사용하고, 즉시 모드의 경우 텍스처와 버퍼의 병합 상태를 패스 상태로 리디렉션하여 직접 실행합니다.
 

11.3.3 FRDGBuilder::Compile

FRDGBuilder의 컴파일 로직은 매우 복잡하며 다음과 같이 많은 처리와 최적화를 수행합니다:

void FRDGBuilder::Compile()
{
    uint32 RasterPassCount = 0;
    uint32 AsyncComputePassCount = 0;

    // 태그 비트를 전달합니다.
    FRDGPassBitArray PassesOnAsyncCompute(false, Passes.Num());
    FRDGPassBitArray PassesOnRaster(false, Passes.Num());
    FRDGPassBitArray PassesWithUntrackedOutputs(false, Passes.Num());
    FRDGPassBitArray PassesToNeverCull(false, Passes.Num());

    const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle();
    const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();

    const auto IsCrossPipeline = [&](FRDGPassHandle A, FRDGPassHandle B)
    {
        return PassesOnAsyncCompute[A] != PassesOnAsyncCompute[B];
    };

    const auto IsSortedBefore = [&](FRDGPassHandle A, FRDGPassHandle B)
    {
        return A < B;
    };

    const auto IsSortedAfter = [&](FRDGPassHandle A, FRDGPassHandle B)
    {
        return A > B;
    };

    // 그래프에 생산자/소비자 종속성을 구성하고 패킹된 메타데이터 비트 배열을 구축하여 특정 조건과 일치하는 패스를 검색할 때 캐시 일관성을 개선합니다. 
    // 검색 루트는 필터링에도 사용됩니다. 추적되지 않은 RHI 출력을 전달하는 패스(예: SHADER_PARAMETER_{BUFFER, TEXTURE}_UAV)는 크롭할 수 없으며 외부 리소스의 어떤 패스에도 쓸 수 없습니다. 
    // 리소스 추출은 항상 그래프의 루트인 에필로그 패스까지 수명을 연장합니다. 프리앰블과 에필로그는 보조 패스로서 결코 폐기되지 않습니다.
    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Culling_Dependencies, FColor::Emerald);

        // 자르기 종속성을 추가합니다.
        const auto AddCullingDependency = [&](FRDGPassHandle& ProducerHandle, FRDGPassHandle PassHandle, ERHIAccess Access)
        {
            if (Access != ERHIAccess::Unknown)
            {
                if (ProducerHandle.IsValid())
                {
                    // 패스 종속성을 추가합니다.
                    AddPassDependency(ProducerHandle, PassHandle);
                }

                // 쓰기 가능한 경우 새 프로듀서를 저장합니다.
                if (IsWritableAccess(Access))
                {
                    ProducerHandle = PassHandle;
                }
            }
        };

        // 모든 패스를 반복하고 각 패스의 텍스처와 버퍼 상태 등을 처리합니다.
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            FRDGPass* Pass = Passes[PassHandle];

            bool bUntrackedOutputs = Pass->GetParameters().HasExternalOutputs();

            // Pass의 모든 텍스처 상태를 처리합니다.
            for (auto& TexturePair : Pass->TextureStates)
            {
                FRDGTextureRef Texture = TexturePair.Key;
                auto& LastProducers = Texture->LastProducers;
                auto& PassState = TexturePair.Value.State;

                const bool bWholePassState = IsWholeResource(PassState);
                const bool bWholeProducers = IsWholeResource(LastProducers);

                // 프로듀서 배열은 최소한 패스 상태 배열만큼 커야 합니다.
                if (bWholeProducers && !bWholePassState)
                {
                    InitAsSubresources(LastProducers, Texture->Layout);
                }

                // 자르기 종속성을 추가합니다.
                for (uint32 Index = 0, Count = LastProducers.Num(); Index < Count; ++Index)
                {
                    AddCullingDependency(LastProducers[Index], PassHandle, PassState[bWholePassState ? 0 : Index].Access);
                }

                bUntrackedOutputs |= Texture->bExternal;
            }

            // 패스에 대한 모든 버퍼 상태를 처리합니다.
            for (auto& BufferPair : Pass->BufferStates)
            {
                FRDGBufferRef Buffer = BufferPair.Key;
                AddCullingDependency(Buffer->LastProducer, PassHandle, BufferPair.Value.State.Access);
                bUntrackedOutputs |= Buffer->bExternal;
            }

            // Pass의 다른 마크 및 데이터 처리.
            const ERDGPassFlags PassFlags = Pass->GetFlags();
            const bool bAsyncCompute = EnumHasAnyFlags(PassFlags, ERDGPassFlags::AsyncCompute);
            const bool bRaster = EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster);
            const bool bNeverCull = EnumHasAnyFlags(PassFlags, ERDGPassFlags::NeverCull);

            PassesOnRaster[PassHandle] = bRaster;
            PassesOnAsyncCompute[PassHandle] = bAsyncCompute;
            PassesToNeverCull[PassHandle] = bNeverCull;
            PassesWithUntrackedOutputs[PassHandle] = bUntrackedOutputs;
            AsyncComputePassCount += bAsyncCompute ? 1 : 0;
            RasterPassCount += bRaster ? 1 : 0;
        }

        // 프롤로그/에필로그는 추적 불가능으로 설정되어 있으며 각각 외부 리소스 가져오기/내보내기를 담당합니다.
        PassesWithUntrackedOutputs[ProloguePassHandle] = true;
        PassesWithUntrackedOutputs[EpiloguePassHandle] = true;

        // 추출된 텍스처의 자르기 종속성을 처리합니다.
        for (const auto& Query : ExtractedTextures)
        {
            FRDGTextureRef Texture = Query.Key;
            for (FRDGPassHandle& ProducerHandle : Texture->LastProducers)
            {
                AddCullingDependency(ProducerHandle, EpiloguePassHandle, Texture->AccessFinal);
            }
            Texture->ReferenceCount++;
        }

        // 추출 버퍼 트리밍 종속성 처리하기 .
        for (const auto& Query : ExtractedBuffers)
        {
            FRDGBufferRef Buffer = Query.Key;
            AddCullingDependency(Buffer->LastProducer, EpiloguePassHandle, Buffer->AccessFinal);
            Buffer->ReferenceCount++;
        }
    }

    // -------- 패스 자르기 처리 --------
    
    if (GRDGCullPasses)
    {
        TArray<FRDGPassHandle, TInlineAllocator<32, SceneRenderingAllocator>> PassStack;
        // 모든 패스가 컬링되도록 초기화됩니다.
        PassesToCull.Init(true, Passes.Num());

        // 추적되지 않거나 거부되지 않은 것으로 표시된 출력에 해당하는 패스의 루트 목록을 수집합니다.
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            if (PassesWithUntrackedOutputs[PassHandle] || PassesToNeverCull[PassHandle])
            {
                PassStack.Add(PassHandle);
            }
        }

        // 깊이 우선 검색을 사용하여 비재귀 루프에서 스택 트래버스하고, 루트 액세스 가능한 각 패스 노드를 자르지 않은 것으로 표시합니다.
        while (PassStack.Num())
        {
            const FRDGPassHandle PassHandle = PassStack.Pop();

            if (PassesToCull[PassHandle])
            {
                PassesToCull[PassHandle] = false;
                PassStack.Append(Passes[PassHandle]->Producers);

            #if STATS
                --GRDGStatPassCullCount;
            #endif
            }
        }
    }
    else // 패스 자르기가 활성화되어 있지 않으면 모든 패스가 자르기 없음으로 초기화됩니다.
    {
        PassesToCull.Init(false, Passes.Num());
    }
    
    // -------- 패스 장벽 처리하기 --------

    // 필터링된 그래프를 가로지르며 각 하위 리소스에 대한 장벽을 컴파일하고, 읽기에서 읽기로의 전환 등 일부 전환은 중복됩니다. 
    // RDG는 보수적인 휴리스틱을 사용하므로 병합하지 않도록 선택한다고 해서 반드시 전환이 수행되어야 한다는 의미는 아닙니다. 
    // 두 단계는 별개의 단계입니다. 병합 상태는 첫 번째와 마지막 패스 간격을 추적합니다. 패스 참조도 각 리소스에 누적됩니다. 
    // 컬링된 패스는 참조를 제공할 수 없으므로 반드시 컬링 후에 이루어져야 합니다.

    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Barriers, FColor::Emerald);

        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            // 잘린 패스 또는 매개변수 없는 패스 건너뛰기.
            if (PassesToCull[PassHandle] || PassesWithEmptyParameters[PassHandle])
            {
                continue;
            }

            // 하위 리소스 상태를 병합합니다.
            const auto MergeSubresourceStates = [&](ERDGParentResourceType ResourceType, FRDGSubresourceState*& PassMergeState, FRDGSubresourceState*& ResourceMergeState, const FRDGSubresourceState& PassState)
            {
                // 알 수 없는 상태의 리소스 병합을 건너뜁니다.
                if (PassState.Access == ERHIAccess::Unknown)
                {
                    return;
                }

                if (!ResourceMergeState || !FRDGSubresourceState::IsMergeAllowed(ResourceType, *ResourceMergeState, PassState))
                {
                    // 교차 파이프라인, 병합할 수 없는 상태 변경은 보호를 위해 새로운 패스 종속성이 필요합니다.
                    if (ResourceMergeState && ResourceMergeState->Pipeline != PassState.Pipeline)
                    {
                        AddPassDependency(ResourceMergeState->LastPass, PassHandle);
                    }

                    // 통과 상태에 새 보류 중인 병합 상태를 할당합니다.
                    ResourceMergeState = AllocSubresource(PassState);
                    ResourceMergeState->SetPass(PassHandle);
                }
                else
                {
                    // 패스 상태를 병합된 상태로 병합합니다.
                    ResourceMergeState->Access |= PassState.Access;
                    ResourceMergeState->LastPass = PassHandle;
                }

                PassMergeState = ResourceMergeState;
            };

            const bool bAsyncComputePass = PassesOnAsyncCompute[PassHandle];

            // 현재 처리된 패스 인스턴스를 가져옵니다.
            FRDGPass* Pass = Passes[PassHandle];

            // 현재 패스의 텍스처 상태를 처리합니다.
            for (auto& TexturePair : Pass->TextureStates)
            {
                FRDGTextureRef Texture = TexturePair.Key;
                auto& PassState = TexturePair.Value;

                // 참조 수를 늘립니다.
                Texture->ReferenceCount += PassState.ReferenceCount;
                Texture->bUsedByAsyncComputePass |= bAsyncComputePass;

                const bool bWholePassState = IsWholeResource(PassState.State);
                const bool bWholeMergeState = IsWholeResource(Texture->MergeState);

                // 간단하게 하기 위해 병합/통과 상태 차원이 일치해야 합니다.
                if (bWholeMergeState && !bWholePassState)
                {
                    InitAsSubresources(Texture->MergeState, Texture->Layout);
                }
                else if (!bWholeMergeState && bWholePassState)
                {
                    InitAsWholeResource(Texture->MergeState);
                }

                const uint32 SubresourceCount = PassState.State.Num();
                PassState.MergeState.SetNum(SubresourceCount);

                // 하위 리소스 상태를 병합합니다.
                for (uint32 Index = 0; Index < SubresourceCount; ++Index)
                {
                    MergeSubresourceStates(ERDGParentResourceType::Texture, PassState.MergeState[Index], Texture->MergeState[Index], PassState.State[Index]);
                }
            }

            // 현재 패스 버퍼 상태를 처리합니다.
            for (auto& BufferPair : Pass->BufferStates)
            {
                FRDGBufferRef Buffer = BufferPair.Key;
                auto& PassState = BufferPair.Value;

                Buffer->ReferenceCount += PassState.ReferenceCount;
                Buffer->bUsedByAsyncComputePass |= bAsyncComputePass;

                MergeSubresourceStates(ERDGParentResourceType::Buffer, PassState.MergeState, Buffer->MergeState, PassState.State);
            }
        }
    }

    // 비동기 계산 처리하기 통과.
    if (AsyncComputePassCount > 0)
    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_AsyncCompute, FColor::Emerald);

        FRDGPassBitArray PassesWithCrossPipelineProducer(false, Passes.Num());
        FRDGPassBitArray PassesWithCrossPipelineConsumer(false, Passes.Num());

        // 실행 중인 활성 패스를 반복하여 각 패스의 최신 교차 파이프라인 생산자와 가장 빠른 교차 파이프라인 소비자를 찾아 비동기 계산을 위해 겹치는 영역을 나중에 구성할 때 검색 공간을 줄입니다.
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            if (PassesToCull[PassHandle] || PassesWithEmptyParameters[PassHandle])
            {
                continue;
            }

            FRDGPass* Pass = Passes[PassHandle];

            // 생산자를 반복하고 생산자와 소비자에 대한 참조를 처리합니다.
            for (FRDGPassHandle ProducerHandle : Pass->GetProducers())
            {
                const FRDGPassHandle ConsumerHandle = PassHandle;

                if (!IsCrossPipeline(ProducerHandle, ConsumerHandle))
                {
                    continue;
                }

                FRDGPass* Consumer = Pass;
                FRDGPass* Producer = Passes[ProducerHandle];

                // 생산자를 위한 다른 파이프라인에서 가장 빠른 소비자를 찾습니다.
                if (Producer->CrossPipelineConsumer.IsNull() || IsSortedBefore(ConsumerHandle, Producer->CrossPipelineConsumer))
                {
                    Producer->CrossPipelineConsumer = PassHandle;
                    PassesWithCrossPipelineConsumer[ProducerHandle] = true;
                }

                // 소비자를 위한 다른 파이프라인의 최신 생산자를 찾아보세요.
                if (Consumer->CrossPipelineProducer.IsNull() || IsSortedAfter(ProducerHandle, Consumer->CrossPipelineProducer))
                {
                    Consumer->CrossPipelineProducer = ProducerHandle;
                    PassesWithCrossPipelineProducer[ConsumerHandle] = true;
                }
            }
        }

        // 비동기 계산, 펜싱 및 리소스 할당/회수를 위해 포크/조인 중첩 영역을 생성합니다. 비동기 계산 패스는 두 파이프라인이 병렬로 실행 중이므로 포크/조인이 완료될 때까지 리소스 참조를 할당/해제할 수 없습니다. 따라서 비동기 계산의 모든 리소스 라이프사이클은 전체 비동기 영역으로 확장됩니다.

        const auto IsCrossPipelineProducer = [&](FRDGPassHandle A)
        {
            return PassesWithCrossPipelineConsumer[A];
        };

        const auto IsCrossPipelineConsumer = [&](FRDGPassHandle A)
        {
            return PassesWithCrossPipelineProducer[A];
        };

        
        const auto FindCrossPipelineProducer = [&](FRDGPassHandle PassHandle)
        {
            FRDGPassHandle LatestProducerHandle = ProloguePassHandle;
            FRDGPassHandle ConsumerHandle = PassHandle;

            // 분기점을 만들기 위해 다른 파이프라인에서 최신 생산자를 찾기를 희망합니다. 
            // N개의 생산자 통로로 N개의 자원을 소비할 수 있기 때문에 마지막 한 가지만 신경 씁니다.
            while (ConsumerHandle != Passes.Begin())
            {
                if (!PassesToCull[ConsumerHandle] && !IsCrossPipeline(ConsumerHandle, PassHandle) && IsCrossPipelineConsumer(ConsumerHandle))
                {
                    const FRDGPass* Consumer = Passes[ConsumerHandle];

                    if (IsSortedAfter(Consumer->CrossPipelineProducer, LatestProducerHandle))
                    {
                        LatestProducerHandle = Consumer->CrossPipelineProducer;
                    }
                }
                --ConsumerHandle;
            }

            return LatestProducerHandle;
        };

        // 교차 파이프라인 소비자를 찾는다.
        const auto FindCrossPipelineConsumer = [&](FRDGPassHandle PassHandle)
        {
            check(PassHandle != EpiloguePassHandle);

            FRDGPassHandle EarliestConsumerHandle = EpiloguePassHandle;
            FRDGPassHandle ProducerHandle = PassHandle;

            // 파이프라인 사이에 연결 지점이 생성되므로 다른 파이프라인에서 가장 빠른 사용자를 찾을 수 있을 것으로 예상합니다. 
            // 다른 파이프라인에서 N명의 소비자를 위해 생성할 수 있으므로 가장 먼저 실행하는 소비자만 신경을 씁니다.
            while (ProducerHandle != Passes.End())
            {
                if (!PassesToCull[ProducerHandle] && !IsCrossPipeline(ProducerHandle, PassHandle) && IsCrossPipelineProducer(ProducerHandle))
                {
                    const FRDGPass* Producer = Passes[ProducerHandle];

                    if (IsSortedBefore(Producer->CrossPipelineConsumer, EarliestConsumerHandle))
                    {
                        EarliestConsumerHandle = Producer->CrossPipelineConsumer;
                    }
                }
                ++ProducerHandle;
            }

            return EarliestConsumerHandle;
        };

        // 비동기적으로 계산된 패스의 포크에 그래프 Pass를 삽입합니다.
        const auto InsertGraphicsToAsyncComputeFork = [&](FRDGPass* GraphicsPass, FRDGPass* AsyncComputePass)
        {
            FRDGBarrierBatchBegin& EpilogueBarriersToBeginForAsyncCompute = GraphicsPass->GetEpilogueBarriersToBeginForAsyncCompute(Allocator);

            GraphicsPass->bGraphicsFork = 1;
            EpilogueBarriersToBeginForAsyncCompute.SetUseCrossPipelineFence();

            AsyncComputePass->bAsyncComputeBegin = 1;
            AsyncComputePass->GetPrologueBarriersToEnd(Allocator).AddDependency(&EpilogueBarriersToBeginForAsyncCompute);
        };

        // 그래픽 패스 병합에 패스 비동기 계산을 삽입합니다.
        const auto InsertAsyncToGraphicsComputeJoin = [&](FRDGPass* AsyncComputePass, FRDGPass* GraphicsPass)
        {
            FRDGBarrierBatchBegin& EpilogueBarriersToBeginForGraphics = AsyncComputePass->GetEpilogueBarriersToBeginForGraphics(Allocator);

            AsyncComputePass->bAsyncComputeEnd = 1;
            EpilogueBarriersToBeginForGraphics.SetUseCrossPipelineFence();

            GraphicsPass->bGraphicsJoin = 1;
            GraphicsPass->GetPrologueBarriersToEnd(Allocator).AddDependency(&EpilogueBarriersToBeginForGraphics);
        };

        FRDGPass* PrevGraphicsForkPass = nullptr;
        FRDGPass* PrevGraphicsJoinPass = nullptr;
        FRDGPass* PrevAsyncComputePass = nullptr;

        // 모든 패스를 반복하고, 리소스의 수명을 연장하고, 그래프 패스와 비동기식 컴퓨팅 패스의 교차 및 병합 노드를 처리합니다.
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            if (!PassesOnAsyncCompute[PassHandle] || PassesToCull[PassHandle])
            {
                continue;
            }

            FRDGPass* AsyncComputePass = Passes[PassHandle];

            // 포크 패스 및 병합 패스를 찾습니다.
            const FRDGPassHandle GraphicsForkPassHandle = FindCrossPipelineProducer(PassHandle);
            const FRDGPassHandle GraphicsJoinPassHandle = FindCrossPipelineConsumer(PassHandle);

            AsyncComputePass->GraphicsForkPass = GraphicsForkPassHandle;
            AsyncComputePass->GraphicsJoinPass = GraphicsJoinPassHandle;

            FRDGPass* GraphicsForkPass = Passes[GraphicsForkPassHandle];
            FRDGPass* GraphicsJoinPass = Passes[GraphicsJoinPassHandle];

            // 비동기 계산에 사용되는 리소스의 수명 주기를 그래프 포크/조인까지 확장합니다.
            GraphicsForkPass->ResourcesToBegin.Add(AsyncComputePass);
            GraphicsJoinPass->ResourcesToEnd.Add(AsyncComputePass);

            // 그래프 포크 패스를 비동기 연산 포크 패스에 삽입합니다.
            if (PrevGraphicsForkPass != GraphicsForkPass)
            {
                InsertGraphicsToAsyncComputeFork(GraphicsForkPass, AsyncComputePass);
            }

            // MergePass의 비동기 계산을 그래픽 MergePass에 삽입합니다.
            if (PrevGraphicsJoinPass != GraphicsJoinPass && PrevAsyncComputePass)
            {
                InsertAsyncToGraphicsComputeJoin(PrevAsyncComputePass, PrevGraphicsJoinPass);
            }

            PrevAsyncComputePass = AsyncComputePass;
            PrevGraphicsForkPass = GraphicsForkPass;
            PrevGraphicsJoinPass = GraphicsJoinPass;
        }

        // 다이어그램의 마지막 비동기 계산 패스는 에필로그 패스로 다시 수동 연결해야 합니다.
        if (PrevAsyncComputePass)
        {
            InsertAsyncToGraphicsComputeJoin(PrevAsyncComputePass, EpiloguePass);
            PrevAsyncComputePass->bAsyncComputeEndExecute = 1;
        }
    }

    // 모든 그래픽 파이프라인 패스를 반복하고 동일한 RT를 가진 모든 래스터화된 패스를 동일한 RHI 렌더링 패스로 병합합니다.
    if (GRDGMergeRenderPasses && RasterPassCount > 0)
    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_RenderPassMerge, FColor::Emerald);

        TArray<FRDGPassHandle, SceneRenderingAllocator> PassesToMerge;
        FRDGPass* PrevPass = nullptr;
        const FRenderTargetBindingSlots* PrevRenderTargets = nullptr;

        const auto CommitMerge = [&]
        {
            if (PassesToMerge.Num())
            {
                const FRDGPassHandle FirstPassHandle = PassesToMerge[0];
                const FRDGPassHandle LastPassHandle = PassesToMerge.Last();
                
                // 주어진 Pass 간격은 단일 렌더링 Pass로 병합됩니다: [B, X, X, X, X, E], 시작 Pass(B)와 종료 Pass(E)는 각각 BeginRenderPass/EndRenderPass를 호출합니다.
 				// 또한 begin은 전체 병합 간격의 모든 프롤로그 장벽을 처리하고 end는 모든 테일게이트 장벽을 처리하므로 렌더링 채널 내의 자원 변환을 피하고 자원 변환을 보다 효율적으로 일괄 처리할 수 있습니다.
 				// 병합 세트로부터 패스 간의 의존성을 필터링하는 작업이 순회 중에 완료되었다고 가정합니다.
                
                // (B)는 병합된 시퀀스의 첫 번째 패스입니다.
                {
                    FRDGPass* Pass = Passes[FirstPassHandle];
                    Pass->bSkipRenderPassEnd = 1;
                    Pass->EpilogueBarrierPass = LastPassHandle;
                }

                // (X)는 중간 패스를 의미합니다.
                for (int32 PassIndex = 1, PassCount = PassesToMerge.Num() - 1; PassIndex < PassCount; ++PassIndex)
                {
                    const FRDGPassHandle PassHandle = PassesToMerge[PassIndex];
                    FRDGPass* Pass = Passes[PassHandle];
                    Pass->bSkipRenderPassBegin = 1;
                    Pass->bSkipRenderPassEnd = 1;
                    Pass->PrologueBarrierPass = FirstPassHandle;
                    Pass->EpilogueBarrierPass = LastPassHandle;
                }

                // (E)는 병합된 시퀀스의 최종 패스입니다.
                {
                    FRDGPass* Pass = Passes[LastPassHandle];
                    Pass->bSkipRenderPassBegin = 1;
                    Pass->PrologueBarrierPass = FirstPassHandle;
                }

            #if STATS
                GRDGStatRenderPassMergeCount += PassesToMerge.Num();
            #endif
            }
            PassesToMerge.Reset();
            PrevPass = nullptr;
            PrevRenderTargets = nullptr;
        };

        // 모든 래스터 패스를 반복하고 동일한 RT를 가진 모든 패스를 동일한 렌더링 패스로 병합합니다.
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            // 잘린 패스를 건너뜁니다.
            if (PassesToCull[PassHandle])
            {
                continue;
            }

            // 처리되는 것은 래스터 패스입니다.
            if (PassesOnRaster[PassHandle])
            {
                FRDGPass* NextPass = Passes[PassHandle];

                // 사용자 제어 렌더링 패스용 패스는 다른 패스와 병합할 수 없으며, 래스터 UAV용 패스도 잠재적인 상호 의존성으로 인해 병합할 수 없습니다.
                if (EnumHasAnyFlags(NextPass->GetFlags(), ERDGPassFlags::SkipRenderPass) || NextPass->bUAVAccess)
                {
                    CommitMerge();
                    continue;
                }

                // 그래픽 포크 패스는 이전 래스터 패스와 병합할 수 없습니다.
                if (NextPass->bGraphicsFork)
                {
                    CommitMerge();
                }

                const FRenderTargetBindingSlots& RenderTargets = NextPass->GetParameters().GetRenderTargets();

                if (PrevPass)
                {
                    // RT를 비교하여 병합이 가능한지 확인합니다.
                    if (PrevRenderTargets->CanMergeBefore(RenderTargets)
                    #if WITH_MGPU
                        && PrevPass->GPUMask == NextPass->GPUMask
                    #endif
                        )
                    {
                        // 가능하면 PassesToMerge 목록에 Pass를 추가합니다.
                        if (!PassesToMerge.Num())
                        {
                            PassesToMerge.Add(PrevPass->GetHandle());
                        }
                        PassesToMerge.Add(PassHandle);
                    }
                    else
                    {
                        CommitMerge();
                    }
                }

                PrevPass = NextPass;
                PrevRenderTargets = &RenderTargets;
            }
            else if (!PassesOnAsyncCompute[PassHandle])
            {
                // 그래픽 파이프라인에서 래스터 패스가 아닌 패스를 사용하면 RT 병합이 무효화됩니다.
                CommitMerge();
            }
        }

        CommitMerge();
    }
}

위의 코드를 보면 RDG 컴파일 중 로직이 매우 복잡하고 많은 단계가 있으며, 생산자 및 소비자 종속성 구성, 패스의 자르기, 리소스 수명 주기 조정, 패스의 자르기, 패스의 리소스 변환 및 배리어 처리, 패스의 종속성 및 참조의 비동기 계산 처리, 포크 및 병합된 패스 노드 찾기 및 생성, 병합 등의 과정을 연속적으로 거치면서 특히 동일한 렌더링 타깃인 모든 래스터화된 패스 병합 및 기타 단계.
 
위의 코드에는 아래에 분석된 몇 가지 중요한 인터페이스도 포함되어 있습니다:

// 패스 종속성을 추가하고, 소비자 핸들의 프로듀서 목록에 프로듀서 핸들을 추가합니다.
void FRDGBuilder::AddPassDependency(FRDGPassHandle ProducerHandle, FRDGPassHandle ConsumerHandle)
{
    FRDGPass* Consumer = Passes[ConsumerHandle];

    auto& Producers = Consumer->Producers;
    if (Producers.Find(ProducerHandle) == INDEX_NONE)
    {
        Producers.Add(ProducerHandle);
    }
};

// 하위 리소스로 초기화됩니다.
template <typename ElementType, typename AllocatorType>
inline void InitAsSubresources(TRDGTextureSubresourceArray<ElementType, AllocatorType>& SubresourceArray, const FRDGTextureSubresourceLayout& Layout, const ElementType& Element = {})
{
    const uint32 SubresourceCount = Layout.GetSubresourceCount();
    SubresourceArray.SetNum(SubresourceCount, false);
    for (uint32 SubresourceIndex = 0; SubresourceIndex < SubresourceCount; ++SubresourceIndex)
    {
        SubresourceArray[SubresourceIndex] = Element;
    }
}

// 전체 리소스로 초기화됩니다.
template <typename ElementType, typename AllocatorType>
FORCEINLINE void InitAsWholeResource(TRDGTextureSubresourceArray<ElementType, AllocatorType>& SubresourceArray, const ElementType& Element = {})
{
    SubresourceArray.SetNum(1, false);
    SubresourceArray[0] = Element;
}

// 하위 리소스 할당.
FRDGSubresourceState* FRDGBuilder::AllocSubresource(const FRDGSubresourceState& Other)
{
    FRDGSubresourceState* State = Allocator.AllocPOD<FRDGSubresourceState>();
    *State = Other;
    return State;
}

11.3.4 FRDGBuilder::Execute

앞서 언급한 패스(AddPass) 수집과 렌더링 맵 컴파일이 끝나면 이제 렌더링 맵을 실행할 차례이며, 이 작업은 FRDGBuilder::Execute가 수행합니다:

void FRDGBuilder::Execute()
{
    SCOPED_NAMED_EVENT(FRDGBuilder_Execute, FColor::Emerald);

    // 컴파일하기 전에 다이어그램 끝에 에필로그 패스를 만듭니다.
    EpiloguePass = Passes.Allocate<FRDGSentinelPass>(Allocator, RDG_EVENT_NAME("Graph Epilogue"));
    SetupEmptyPass(EpiloguePass);

    const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle();
    const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle();
    FRDGPassHandle LastUntrackedPassHandle = ProloguePassHandle;

    // 비즉시 모드.
    if (!GRDGImmediateMode)
    {
        // 실행 전에 컴파일하려면 섹션 11.3.3을 참조하세요.
        Compile();

        {
            SCOPE_CYCLE_COUNTER(STAT_RDG_CollectResourcesTime);

            // 패스 리소스 모음.
            for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
            {
                if (!PassesToCull[PassHandle])
                {
                    CollectPassResources(PassHandle);
                }
            }

            // 텍스처 추출을 종료합니다.
            for (const auto& Query : ExtractedTextures)
            {
                EndResourceRHI(EpiloguePassHandle, Query.Key, 1);
            }

            // 버퍼 추출을 종료합니다.
            for (const auto& Query : ExtractedBuffers)
            {
                EndResourceRHI(EpiloguePassHandle, Query.Key, 1);
            }
        }

        // 수집 패스 장벽 .
        {
            SCOPE_CYCLE_COUNTER(STAT_RDG_CollectBarriersTime);

            for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
            {
                if (!PassesToCull[PassHandle])
                {
                    CollectPassBarriers(PassHandle, LastUntrackedPassHandle);
                }
            }
        }
    }

    // 모든 텍스처를 반복하여 각 텍스처에 종료 전환을 추가합니다.
    for (FRDGTextureHandle TextureHandle = Textures.Begin(); TextureHandle != Textures.End(); ++TextureHandle)
    {
        FRDGTextureRef Texture = Textures[TextureHandle];

        if (Texture->GetRHIUnchecked())
        {
            AddEpilogueTransition(Texture, LastUntrackedPassHandle);
            Texture->Finalize();
        }
    }

    // 모든 버퍼를 반복하여 각 버퍼에 에필로그 전환을 추가합니다.
    for (FRDGBufferHandle BufferHandle = Buffers.Begin(); BufferHandle != Buffers.End(); ++BufferHandle)
    {
        FRDGBufferRef Buffer = Buffers[BufferHandle];

        if (Buffer->GetRHIUnchecked())
        {
            AddEpilogueTransition(Buffer, LastUntrackedPassHandle);
            Buffer->Finalize();
        }
    }

    // 패스를 실행합니다.
    if (!GRDGImmediateMode)
    {
        QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_Execute_Passes);

        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            // 크롭되지 않은 패스를 수행합니다.
            if (!PassesToCull[PassHandle])
            {
                ExecutePass(Passes[PassHandle]);
            }
        }
    }
    else
    {
        ExecutePass(EpiloguePass);
    }

    RHICmdList.SetGlobalUniformBuffers({});

#if WITH_MGPU
    (......)
#endif

    // 텍스처 추출을 수행합니다.
    for (const auto& Query : ExtractedTextures)
    {
        *Query.Value = Query.Key->PooledRenderTarget;
    }

    // 버퍼 추출을 수행합니다.
    for (const auto& Query : ExtractedBuffers)
    {
        *Query.Value = Query.Key->PooledBuffer;
    }

  
    Clear();
}

실행 프로세스에는 다음과 같은 로직이 있는 ExecutePass 인터페이스가 포함됩니다:
 

void FRDGBuilder::ExecutePass(FRDGPass* Pass)
{
    QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_ExecutePass);
    SCOPED_GPU_MASK(RHICmdList, Pass->GPUMask);
    IF_RDG_CPU_SCOPES(CPUScopeStacks.BeginExecutePass(Pass));

    // GPU 범위 사용 .
#if RDG_GPU_SCOPES
    const bool bUsePassEventScope = Pass != EpiloguePass && Pass != ProloguePass;
    if (bUsePassEventScope)
    {
        GPUScopeStacks.BeginExecutePass(Pass);
    }
#endif

#if WITH_MGPU
    if (!bWaitedForTemporalEffect && NameForTemporalEffect != NAME_None)
    {
        RHICmdList.WaitForTemporalEffect(NameForTemporalEffect);
        bWaitedForTemporalEffect = true;
    }
#endif

    // 패스가 실행되는 순서: 1.프롤로그 -> 2.패스 본문 -> 3.에필로그.
    // 전체 프로세스는 지정된 파이프라인의 명령 목록을 사용하여 실행됩니다.
    FRHIComputeCommandList& RHICmdListPass = (Pass->GetPipeline() == ERHIPipeline::AsyncCompute)
        ? static_cast<FRHIComputeCommandList&>(RHICmdListAsyncCompute)
        : RHICmdList;

    // 1. 프롤로그 실행
    ExecutePassPrologue(RHICmdListPass, Pass);

    // 2. 합격 과목 실행
    Pass->Execute(RHICmdListPass);

    // 3. 에필로그 구현
    ExecutePassEpilogue(RHICmdListPass, Pass);

#if RDG_GPU_SCOPES
    if (bUsePassEventScope)
    {
        GPUScopeStacks.EndExecutePass(Pass);
    }
#endif

    // 비동기 계산이 완료되면 즉시 디스패치합니다.
    if (Pass->bAsyncComputeEnd)
    {
        FRHIAsyncComputeCommandListImmediate::ImmediateDispatch(RHICmdListAsyncCompute);
    }

    // 디버그 모드에 있고 비동기적으로 계산하지 않는 경우 명령을 제출하고 GPU에 새로 고친 다음 GPU가 처리를 완료할 때까지 기다립니다.
    if (GRDGDebugFlushGPU && !GRDGAsyncCompute)
    {
        RHICmdList.SubmitCommandsAndFlushGPU();
        RHICmdList.BlockUntilGPUIdle();
    }
}

执行Pass主要有3个步骤:1. prologue、2. pass主体、3. epilogue,它们的执行逻辑如下:

// 1. prologue
void FRDGBuilder::ExecutePassPrologue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass)
{
    // 장벽을 시작하기 위한 서문을 제출합니다.
    if (Pass->PrologueBarriersToBegin)
    {
        Pass->PrologueBarriersToBegin->Submit(RHICmdListPass);
    }

    // 시퀀스 종료 장벽의 커밋.
    if (Pass->PrologueBarriersToEnd)
    {
        Pass->PrologueBarriersToEnd->Submit(RHICmdListPass);
    }

    // 액세스 확인을 통해 RDG 리소스에서 GetRHI를 호출할 수 있으므로 유니티 버퍼는 처음 사용할 때 초기화됩니다.
    Pass->GetParameters().EnumerateUniformBuffers([&](FRDGUniformBufferRef UniformBuffer)
    {
        BeginResourceRHI(UniformBuffer);
    });

    // 예산의 비동기 계산을 설정합니다.
    if (Pass->GetPipeline() == ERHIPipeline::AsyncCompute)
    {
        RHICmdListPass.SetAsyncComputeBudget(Pass->AsyncComputeBudget);
    }

    const ERDGPassFlags PassFlags = Pass->GetFlags();

    if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster))
    {
        if (!EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassBegin())
        {
            // 명령 대기열의 BeginRenderPass 인터페이스를 호출합니다.
            static_cast<FRHICommandList&>(RHICmdListPass).BeginRenderPass(Pass->GetParameters().GetRenderPassInfo(), Pass->GetName());
        }
    }
}

// 2. 통과 제목
void FRDGPass::Execute(FRHIComputeCommandList& RHICmdList)
{
    QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGPass_Execute);
    // 통합 버퍼 설정하기.
    RHICmdList.SetGlobalUniformBuffers(ParameterStruct.GetGlobalUniformBuffers());
    // 패스 구현을 실행합니다.
    ExecuteImpl(RHICmdList);
}

void TRDGLambdaPass::ExecuteImpl(FRHIComputeCommandList& RHICmdList) override
{
    // 람다를 실행합니다.
    ExecuteLambda(static_cast<TRHICommandList&>(RHICmdList));
}

// 3. epilogue
void FRDGBuilder::ExecutePassEpilogue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass)
{
    QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_ExecutePassEpilogue);

    const ERDGPassFlags PassFlags = Pass->GetFlags();

    // 명령 큐의 EndRenderPass를 호출합니다.
    if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster) && !EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassEnd())
    {
        static_cast<FRHICommandList&>(RHICmdListPass).EndRenderPass();
    }

    // 리소스 전환 면제.
    for (FRHITexture* Texture : Pass->TexturesToDiscard)
    {
        RHIDiscardTransientResource(Texture);
    }

    // 전환 리소스를 확보하세요.
    for (FRHITexture* Texture : Pass->TexturesToAcquire)
    {
        RHIAcquireTransientResource(Texture);
    }

    const FRDGParameterStruct PassParameters = Pass->GetParameters();

    // 그래픽 파이프라인의 테일 사운드 배리어로 사용하기 위해 제출되었습니다.
    if (Pass->EpilogueBarriersToBeginForGraphics)
    {
        Pass->EpilogueBarriersToBeginForGraphics->Submit(RHICmdListPass);
    }

    // 비동기식 컴퓨팅을 위한 테일 사운드 배리어 제출.
    if (Pass->EpilogueBarriersToBeginForAsyncCompute)
    {
        Pass->EpilogueBarriersToBeginForAsyncCompute->Submit(RHICmdListPass);
    }
}

위의 내용을 보면 실행 중에 모든 패스가 먼저 컴파일된 다음 패스의 서문, 본문, 후속 작업이 순차적으로 실행되는 것을 알 수 있는데, 이는 명령 큐의 BeginRenderPass, ExecuteRenderCode, EndRenderPass를 그 사이에 분산시키는 것과 같습니다.패스 실행의 본문은 실제로 매우 간단합니다: 호출되는 것은 패스의 Lambda의 인스턴스로, 사용된 명령 큐의 인스턴스를 전달합니다.
 
구현의 마지막 단계는 정리입니다(아래 분석 참조):

void FRDGBuilder::Clear()
{
    // 외부 리소스 청산.
    ExternalTextures.Empty();
    ExternalBuffers.Empty();
    // 추출된 리소스를 정리합니다.
    ExtractedTextures.Empty();
    ExtractedBuffers.Empty();
    // 주제 데이터 정리.
    Passes.Clear();
    Views.Clear();
    Textures.Clear();
    Buffers.Clear();
    // 통합 버퍼와 할당기를 정리합니다.
    UniformBuffers.Clear();
    Allocator.ReleaseAll();
}

11.3.5 RDG 메커니즘 요약

UE의 RDG 시스템은 기본적으로 렌더링 스레드에서 실행되며, 동일한 RT를 가진 RDG 패스는 병합되지만 병렬로 실행되는 것이 아니라 직렬로 실행되는 것을 의미합니다. 일반적인 경우 각 패스 실행의 끝은 즉시 커밋되지 않고 GPU가 완료될 때까지 기다리지만, 디버그 모드이고 비동기 연산인 경우에는 커밋됩니다.

FRDGBuilder의 전역적으로 고유한 인스턴스는 없으며, 일반적으로 특정 수명 주기 동안 패스를 수집, 컴파일 및 실행하는 전체 프로세스를 완료하는 로컬 변수로 선언됩니다. FRDGBuilder의 인스턴스를 선언하는 모듈은 디스턴스 필드, 렌더 텍스처, 씬 렌더러, 씬 캐처, 레이 트레이싱, 포스트 프로세싱, 헤어, 가상 텍스처 등입니다.
FRDGBuilder의 실행 주기는 수집패스, 컴파일패스, 실행패스, 정리의 4단계로 나눌 수 있습니다.

CollectPass 단계는 주로 RHI 렌더링 명령을 생성할 수 있는 렌더링 모듈의 모든 패스(Lambda)를 수집하는 단계로 수집 후 바로 실행되지 않고 지연되며, AddPass 단계는 먼저 FRDGPass의 인스턴스를 생성하여 패스 목록에 추가한 후 SetupPass를 실행하는 과정으로 주로 다음과 같은 과정을 거칩니다. 텍스처 및 버퍼 상태, 레퍼런스, 종속성, 태그 등을 처리합니다.
패스 컴파일 단계는 더 복잡하고 많은 단계로 이루어져 있습니다. 주로 생산자 및 소비자 종속성 구성, 패스 자르기 등 다양한 유형의 마커 결정, 리소스 수명 주기 조정, 패스 자르기, 패스 리소스 변환 및 배리어 처리, 비동기적으로 계산된 패스의 종속성 및 참조 처리, 포크 및 병합된 패스 노드 찾기 및 생성, 래스터화된 모든 패스를 동일한 특정 렌더링 대상으로 병합하는 단계 등이 포함됩니다. 컴파일 단계에서는 먼저 컴파일을 실행한 다음 컴파일 결과에 따라 모든 적격 패스를 실행합니다.

패스 실행 단계는 먼저 컴파일을 실행한 다음 컴파일 결과에 따라 모든 적격 패스를 실행합니다. 전주곡, 본문을 실행하기 위해 하나의 패스를 실행하는 것, 명령 큐 BeginRenderPass의 실행, 패스(람다) 렌더링 코드의 본문을 실행하는 것, 패스 본문의 실행에 해당하는 EndRenderPass를 실행하는 것, 패스 본문을 실행하는 것 등입니다. 이 프로세스는 해당 패스의 람다 인스턴스에 대한 호출이라는 점에서 간결합니다.
마지막으로 정리 단계가 있는데, 이 단계에서는 FRDGBuilder 인스턴스 내의 모든 데이터와 메모리를 정리하거나 초기화합니다.

FRDGBuilder를 실행하는 동안 RHICommandList를 직접 사용할 때와 비교하여 FRDGBuilder의 기능과 최적화는 다음과 같습니다:

  • RDG 패스가 참조하는 모든 리소스는 외부에 등록되어 있더라도 RDG에 의해 할당되거나 관리되어야 하며, RDG 기간 동안 보장된 수명 주기를 가져야 합니다. RDG는 교차 및 병합 패스 동안 리소스의 수명 주기를 지연시키고, 소진되어 참조되지 않으면 해제 및 재사용하는 방식으로 자동으로 리소스의 수명 주기를 관리합니다.
  • 리소스는 즉각적인 응답으로 할당되는 것이 아니라 처음 사용할 때 할당되거나 생성됩니다.
  • 서브 리소스의 개념을 가지고, 합리적인 레이아웃을 통해 큰 리소스 블록으로 통합하고, 하나의 서브 리소스를 다른 리소스로 디스패치할 수 있으며, 서브 리소스 뷰(View)와 별칭(Aliase)을 자동으로 생성하고, 향후 렌더링 패스에서 생성된 리소스 별칭을 생성할 수도 있습니다. 이를 통해 리소스의 할당, 해제 및 재사용을 효과적으로 관리하고, 전체 메모리 사용 공간과 메모리 조각화를 줄이며, CPU 및 GPU IO를 줄이고, 메모리 사용 효율성을 개선할 수 있습니다.
  • RDG 패스를 FRDGBuilder의 인스턴스로 관리하여 패스를 자동으로 정렬, 참조, 포크, 병합하고 패스의 리소스 참조 및 종속성을 처리하며 쓸모없는 패스와 리소스를 정리합니다.RDG는 또한 그래픽스 패스와 비동기 컴퓨트 패스 간의 종속성과 참조를 올바르게 처리하고 DAG 그래프에 따라 순서를 지정하고 리소스 교차 사용 및 상태 전환을 올바르게 처리합니다.
  • RDG는 RDG 패스가 동일한 렌더 텍스처를 사용하는 경우 RDG 패스의 렌더링을 병합할 수 있습니다. 이렇게 하면 RHI 레이어에서 Begin/EndRenderPass 호출 횟수가 줄어들고 RHI 렌더링 명령 및 리소스 상태 전환 횟수가 줄어듭니다.
  • RDG는 패스 간의 리소스 종속성, 장벽 및 상태 전환을 자동으로 처리하여 유효하지 않은 상태 전환(예: 읽기에서 읽기, 쓰기에서 쓰기)을 폐기하고 리소스 상태를 병합 및 일괄 전환하여 렌더링 명령어 수를 더욱 줄일 수 있습니다.
  • RDG 패스의 실행은 렌더링 스레드에서 발생하며, 태스크그래프를 사용하여 병렬로 실행되는 것이 아니라 직렬로 실행됩니다.
  • 이론적으로는 병렬 실행이 가능하지만 이는 추측에 불과하며 실제로 실행 가능한지 여부는 실제로 검증해야 합니다.
  • RDG는 풍부한 디버깅 모드와 정보, 즉시 실행 모드를 지원하여 개발자가 문제를 빠르게 찾아내어 버그 검토의 시간과 어려움을 줄여줍니다.

물론 FRDGBuilder에는 몇 가지 부작용이 있습니다:

  • 렌더링 시스템에 개념 및 캡슐화 계층을 추가하면 렌더링 계층의 복잡성이 증가하고 학습 비용이 증가합니다.
  • 개발 복잡성이 증가하고, 일부 버그는 실행이 지연되어 즉각적인 피드백을 받을 수 없습니다.
  • 특정 패스 또는 리소스의 사용 기간이 추가로 연장될 수 있습니다.

프로스트바이트 비동기 연산 시퀀스입니다. SSAO, SSAO 필터의 패스를 비동기 대기열에 넣으면 원시 AO의 텍스처를 쓰고 읽으며, 동기화 시점 이전에 종료되더라도 원시 AO의 수명 주기는 여전히 동기화 시점까지 연장됩니다.
 

11.4 RDG 개발

이 장에서는 UE용 RDG 시스템을 사용하는 방법을 설명합니다.

11.4.1 RDG 리소스 만들기

RDG 리소스(텍스처, 버퍼, UAV, SRV 등)를 생성하기 위한 샘플 코드는 아래와 같습니다:

// ---- RDG 텍스처 데모 제작하기 ----
// RDG 텍스처 설명 생성
FRDGTextureDesc TextureDesc = Input.Texture->Desc;
TextureDesc.Reset();
TextureDesc.Format = PF_FloatRGBA;
TextureDesc.ClearValue = FClearValueBinding::None;
TextureDesc.Flags &= ~TexCreate_DepthStencilTargetable;
TextureDesc.Flags |= TexCreate_RenderTargetable;
TextureDesc.Extent = OutputViewport.Extent;
// RDG 텍스처를 생성합니다.
FRDGTextureRef MyRDGTexture = GraphBuilder.CreateTexture(TextureDesc, TEXT("MyRDGTexture"));

// ---- RDG 텍스처 UAV 데모 제작하기 ----
FRDGTextureUAVRef MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);

// ---- RDG 텍스처 SRV 데모 제작하기 ----
FRDGTextureSRVRef MyRDGTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateWithPixelFormat(MyRDGTexture, PF_FloatRGBA));

텍스처와 같은 리소스를 생성하려면 리소스를 생성하기 전에 해당 리소스에 대한 디스크립터를 생성해야 하며, 리소스에 대한 UAV 및 SRV를 생성할 때 이전에 생성한 리소스를 인스턴스로 전달하여 재사용의 목적을 달성할 수 있습니다. SRV를 생성하려면 리소스 인스턴스를 디스크립터의 파라미터로 사용해야 하며, 디스크립터가 생성된 후에 SRV가 생성됩니다.

위의 코드는 텍스처 관련 리소스 생성을 예로 들었으며 버퍼 생성도 비슷하므로 더 이상 예시를 들지 않겠습니다.

11.4.2 외부 리소스 등록

이전 섹션의 리소스는 RDG에서 만들고 관리하며, 리소스의 수명 주기 역시 RDG에서 관리합니다. RDG에서 생성하지 않은 리소스가 이미 있는 경우 RDG에서 사용할 수 있나요? 대답은 '예'입니다. FRDGBuilder::RegisterExternalXXX 인터페이스를 통해 외부 리소스를 RDG 시스템에 등록할 수 있습니다. 다음은 텍스처를 등록하는 예제입니다:

// RDG 외부에 RHI 리소스를 만듭니다.
FRHIResourceCreateInfo CreateInfo;
FTexture2DRHIRef MyRHITexture = RHICreateTexture2D(1024, 768, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);

// 외부에서 생성한 RHI 리소스를 RDG 리소스로 등록합니다.
FRDGTextureRef MyExternalRDGTexture = GraphBuilder.RegisterExternalTexture(MyRHITexture);

RDG는 외부에 등록된 리소스의 수명 주기를 제어 및 관리할 수 없으며, RDG를 사용하는 동안 외부 리소스의 수명 주기가 정상 상태인지 확인해야 하며, 그렇지 않으면 예외 또는 프로그램 충돌이 발생할 수 있습니다.
 
RDG 리소스에서 RHI 리소스의 인스턴스를 가져오려면 다음 코드에 도달합니다:

FRHITexture* MyRHITexture = MyRDGTexture.GetRHI();

범례는 RHI 리소스와 RDG 리소스 간의 변환 관계를 표시하는 데 사용됩니다:
 

위의 코드는 텍스처와 관련된 리소스를 등록하는 예시이며 버퍼를 등록하는 것도 비슷합니다.

11.4.3 리소스 추출

이전 장에서 RDG 메커니즘에 대해 설명한 것처럼 RDG는 패스를 수집한 후 즉시 실행되는 것이 아니라 지연 실행(자원이 늦게 생성되거나 할당되는 경우 포함)되기 때문에 렌더링된 자원을 특정 변수에 할당하려는 경우 즉시 실행 모드를 사용할 수 없고 지연 실행 모드를 적용해야 하는 문제가 발생합니다. 지연 실행에 맞게 조정된 리소스 추출은 다음 인터페이스를 통해 이루어집니다:

  • FRDGBuilder::QueueTextureExtraction
  • FRDGBuilder::QueueBufferExtraction

사용 예는 다음과 같습니다:

// RDG 텍스처를 생성합니다.
FRDGTextureRef MyRDGTexture;

FRDGTextureDesc MyTextureDesc = FRDGTextureDesc::Create2D(OutputExtent, HistoryPixelFormat, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_UAV);

MyRDGTexture = GraphBuilder.CreateTexture(MyTextureDesc, "MyRDGTexture", ERDGTextureFlags::MultiFrame);

// UAV를 생성하고 Pass의 셰이더 파라미터로 사용합니다.
(......)
PassParameters->MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);
(......)

// 이미지를 MyRDGTextureUAV에 렌더링하기 위해 패스를 추가합니다.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("MyCustomPass", ...), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(8, 8));

// 리소스를 검색할 대기열을 입력합니다.
TRefCountPtr<IPooledRenderTarget>* OutputRT;
GraphBuilder.QueueTextureExtraction(MyRDGTexture, &OutputRT);

// 추출된 OutputRT에 대해 후속 작업을 수행합니다.
(......)

그러나 패스, 리소스 생성 및 추출이 지연되므로 추출된 리소스는 다음 프레임에서 사용할 수 있도록 반환할 수만 있다는 점에 유의해야 합니다.
 

이 프레임에서 추출된 리소스를 사용하려면 특별한 매개변수 없는 패스를 추가하여 작업할 수 있을까요? 왜 그럴까요?

11.4.4 增加Pass

전체 RDG 시스템의 실행 단위는 RDG 패스이며, 그 종속성, 참조, 입력 및 출력은 FRDGBuilder::AddPass를 통해 이루어지며, 그 예는 다음과 같습니다:

// Pass에 대한 셰이더 파라미터를 생성합니다.
FMyPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FMyPS::FParameters>();
PassParameters->InputTexture = InputTexture;
PassParameters->RenderTargets = FRenderTargetBinding(InputTexture, InputTextureLoadAction);
PassParameters->InputSampler = BilinearSampler;

// 셰이더 처리하기.
TShaderMapRef<FScreenPassVS> VertexShader(View.ShaderMap);
TShaderMapRef<FMyPS> PixelShader(View.ShaderMap);

const FScreenPassPipelineState PipelineState(VertexShader, PixelShader, AdditiveBlendState);

// RDG 패스를 추가합니다.
GraphBuilder.AddPass(
    RDG_EVENT_NAME("MyRDGPass"),
    PassParameters,
    ERDGPassFlags::Raster,
    // 패스용 람다
    [PixelShader, PassParameters, PipelineState] (FRHICommandListImmediate& RHICmdList)
    {
        // 뷰포트를 설정합니다.
        RHICmdList.SetViewport(0, 0, 0.0f, 1024, 768, 1.0f);

        // PSO를 설정합니다.
        SetScreenPassPipelineState(RHICmdList, PipelineState);

        // 셰이더 파라미터를 설정합니다.
        SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);

        // 직사각형 영역을 그립니다.
        DrawRectangle(RHICmdList, 0, 0, 1024, 768, 0, 0, 1.0f, 1.0f, FIntPoint(1024, 768), FIntPoint(1024, 768), PipelineState.VertexShader, EDRF_Default);
    });

RDG 시스템에 추가된 패스는 기존 그래픽스 패스, 컴퓨트 셰이더 또는 파라미터 없는 패스일 수 있으며, RDG 패스와 RHI 패스는 일대일 대응이 없으며 여러 개의 RDG 패스를 병합하여 하나의 RHI 패스로 실행할 수 있습니다. 이전 섹션 11.3.4를 참조하세요. 
FRDGBuilder::Execute

11.4.5 FRDGBuilder 생성

FRDGBuilder를 생성하고 사용하는 코드는 아래와 같이 매우 간단합니다:

void RenderMyStuff(FRHICommandListImmediate& RHICmdList)
{
    // ---- FRDGBuilder의 로컬 객체를 생성합니다 ----
    FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("GraphBuilder_RenderMyStuff"));
    
    (......)
    
    // ---- 추가 패스 ----
    
    GraphBuilder.AddPass(...);
    
    (......)
    
    GraphBuilder.AddPass(...);
    
    (......)
    
    // ---- 리소스 추출 추가 ----
    
    GraphBuilder.QueueTextureExtraction(...);
    
    (......)
    
    // ---- 执行FRDGBuilder ----
    
    GraphBuilder.Execute();
}

특히 주목해야 할 점은 FRDGBuilder 인스턴스는 보통 현지화되어 있으며, UE 시스템에는 씬 렌더러, 포스트 프로세싱, 레이 트레이싱 등과 같은 보다 독립적인 모듈에 주로 사용되는 FRDGBuilder 인스턴스가 여러 개 있다는 점입니다.

FRDGBuilder 실행은 실제로 패스 수집, 패스 컴파일, 패스 실행의 세 단계로 이루어지지만, FRDGBuilder::Execute에는 이미 패스 컴파일과 실행이 포함되어 있으므로 더 이상 FRDGBuilder::Compile 인터페이스를 디스플레이 방식으로 호출할 필요가 없습니다.

11.4.6 RDG 디버깅

RDG 시스템에는 다음과 같은 이름과 설명을 가진 여러 콘솔 명령이 존재합니다:

콘솔 변수 설명
r.RDG.AsyncCompute 비동기 컴퓨팅 정책을 제어합니다: 0-비활성화, 1-비동기 컴퓨팅에 플래그 지정 사용 패스(기본값), 2-컴퓨팅 명령 목록을 사용하는 모든 컴퓨팅 채널 사용.
r.RDG.Breakpoint 특정 조건이 충족될 때 디버거 중단점 위치에 대한 중단점. 0 비활성화, 1~4가지 특수 디버깅 모드.
r.RDG.ClobberResources 할당된 시간에 지정된 정리 색상으로 모든 렌더 타깃과 텍스처/버퍼 UAV를 지웁니다. 디버깅용.
r.RDG.CullPasses 쓸모없는 패스를 자르기 위해 RDG를 활성화할지 여부: 0 비활성화, 1 활성화(기본값)。
r.RDG.Debug 연결 및 실행 중에 발견된 비효율성에 대한 경고를 출력할 수 있습니다.
r.RDG.Debug.FlushGPU 각 패스 실행 후 GPU에 대한 명령어 새로 고침을 활성화합니다. 설정 시 비동기 계산을 비활성화합니다(r.RDG.AsyncCompute=0).
r.RDG.Debug.GraphFilter 특정 디버깅 이벤트를 특정 그래프로 필터링하세요.
r.RDG.Debug.PassFilter 특정 디버깅 이벤트를 특정 패스로 필터링합니다.
r.RDG.Debug.ResourceFilter 특정 디버깅 이벤트를 특정 리소스로 필터링합니다.
r.RDG.DumpGraph 0 - 비활성화, 1 - 생산자, 소비자 패스 종속성 표시, 2 - 리소스 상태 및 전환 표시, 3 - 그래프 겹침, 비동기 계산 표시, 4 - 여러 시각적 로그를 디스크에 덤프합니다.
r.RDG.ExtendResourceLifetimes RDG는 리소스 라이프사이클을 그래프의 전체 길이로 확장합니다. 메모리 사용량이 증가합니다.
r.RDG.ImmediateMode 패스가 생성되면 실행하세요.연결 코드의 호출 스택은 패스의 람다에서 크래시가 발생할 때 유용합니다.
r.RDG.MergeRenderPasses 그래픽은 동일한 연속 렌더 패스를 단일 렌더 패스로 병합합니다. 0 비활성화, 1 활성화(기본값).
r.RDG.OverlapUAVs RDG는 필요할 때 UAV의 작업과 겹쳐집니다. 비활성화하면 UAV 배리어가 항상 삽입됩니다.。
r.RDG.TransitionLog 콘솔로 변환된 출력 리소스.
r.RDG.VerboseCSVStats 0 - 그래픽 실행에 대한 CSV 프로필을 생성하고, 1 - 그래픽 실행의 각 단계에 대한 CSV 파일을 생성합니다.

위에 나열된 RDG 콘솔 외에도 RDG 시스템이 실행될 때 유용한 정보를 표시하는 여러 명령이 있습니다.

vis는 유효한 모든 텍스처를 나열하며, 입력 시 아래와 같은 정보를 표시할 수 있습니다:

VisualizeTexture/Vis <CheckpointName> [<Mode>] [PIP/UV0/UV1/UV2] [BMP] [FRAC/SAT] [FULL]:
Mode (examples):
  RGB      = RGB in range 0..1 (default)
  *8       = RGB * 8
  A        = alpha channel in range 0..1
  R        = red channel in range 0..1
  G        = green channel in range 0..1
  B        = blue channel in range 0..1
  A*16     = Alpha * 16
  RGB/2    = RGB / 2
SubResource:
  MIP5     = Mip level 5 (0 is default)
  INDEX5   = Array Element 5 (0 is default)
InputMapping:
  PIP      = like UV1 but as picture in picture with normal rendering  (default)
  UV0      = UV in left top
  UV1      = full texture
  UV2      = pixel perfect centered
Flags:
  BMP      = save out bitmap to the screenshots folder (not on console, normalized)
STENCIL    = Stencil normally displayed in alpha channel of depth.  This option is used for BMP to get a stencil only BMP.
  FRAC     = use frac() in shader (default)
  SAT      = use saturate() in shader
  FULLLIST = show full list, otherwise we hide some textures in the printout
  BYNAME   = sort list by name
  BYSIZE   = show list by size
TextureId:
  0        = <off>
LogConsoleResponse:     13 = (2D 1x1 PF_DepthStencil)             DepthDummy 1 KB
LogConsoleResponse:     18 = (2D 976x492 PF_FloatRGBA RT)         SceneColor 3752 KB
LogConsoleResponse:     19 = (2D 128x32 PF_G16R16)                PreintegratedGF 16 KB
LogConsoleResponse:     23 = (2D 64x64 PF_FloatRGBA VRam)         LTCMat 32 KB VRamInKB(Start/Size):<NONE>
LogConsoleResponse:     24 = (2D 64x64 PF_G16R16F VRam)           LTCAmp 16 KB VRamInKB(Start/Size):<NONE>
LogConsoleResponse:     26 = (2D 976x492 PF_FloatRGBA UAV)        SSRTemporalAA 3752 KB
LogConsoleResponse:     27 = (2D 976x492 PF_FloatR11G11B10 RT UAV) SSGITemporalAccumulation0 1876 KB
LogConsoleResponse:     29 = (2D 976x492 PF_R32_UINT RT UAV)      DenoiserMetadata0 1876 KB
LogConsoleResponse:     30 = (2D 976x492 PF_FloatRGBA RT UAV VRam) SceneColorDeferred 3752 KB VRamInKB(Start/Size):<NONE>
LogConsoleResponse:     31 = (2D 976x492 PF_DepthStencil VRam)    SceneDepthZ 2345 KB VRamInKB(Start/Size):<NONE>
LogConsoleResponse:     37 = (3D 64x64x16 PF_FloatRGBA UAV)       HairLUT 512 KB
LogConsoleResponse:     38 = (3D 64x64x16 PF_FloatRGBA UAV)       HairLUT 512 KB
LogConsoleResponse:     39 = (2D 64x64 PF_R32_FLOAT UAV)          HairCoverageLUT 16 KB
LogConsoleResponse:     47 = (2D 98x64 PF_A16B16G16R16)           SSProfiles 49 KB
LogConsoleResponse:     48 = (2D 256x64 PF_FloatRGBA RT)          AtmosphereTransmittance 128 KB
LogConsoleResponse:     49 = (2D 64x16 PF_FloatRGBA RT)           AtmosphereIrradiance 8 KB
LogConsoleResponse:     50 = (2D 64x16 PF_FloatRGBA RT)           AtmosphereDeltaE 8 KB
LogConsoleResponse:     51 = (3D 256x128x2 PF_FloatRGBA RT)       AtmosphereInscatter 512 KB
LogConsoleResponse:     52 = (3D 256x128x2 PF_FloatRGBA RT)       AtmosphereDeltaSR 512 KB
LogConsoleResponse:     53 = (3D 256x128x2 PF_FloatRGBA RT)       AtmosphereDeltaSM 512 KB
LogConsoleResponse:     54 = (3D 256x128x2 PF_FloatRGBA RT)       AtmosphereDeltaJ 512 KB
LogConsoleResponse:     55 = (2D 1x1 PF_A32B32G32R32F RT UAV)     EyeAdaptation 1 KB
LogConsoleResponse:     56 = (3D 32x32x32 PF_A2B10G10R10 RT VRam) CombineLUTs 128 KB VRamInKB(Start/Size):<NONE>
LogConsoleResponse:     68 = (2D 976x492 PF_R8G8 RT UAV)          SSGITemporalAccumulation1 938 KB
LogConsoleResponse:     89 = (2D 976x246 PF_R32_UINT RT UAV)      QuadOverdrawBuffer 938 KB
LogConsoleResponse:     91 = (2D 976x492 PF_FloatRGBA RT UAV)     LightAccumulation 3752 KB
LogConsoleResponse:     92 = (Cube[2] 128 PF_FloatRGBA)           ReflectionEnvs 2048 KB
LogConsoleResponse:     93 = (3D 64x64x64 PF_FloatRGBA RT UAV)    TranslucentVolumeDir2 2048 KB
LogConsoleResponse:     95 = (2D 1x1 PF_A32B32G32R32F RT UAV)     EyeAdaptation 1 KB
LogConsoleResponse:     96 = (3D 64x64x64 PF_FloatRGBA RT UAV)    TranslucentVolume2 2048 KB
LogConsoleResponse:     97 = (3D 64x64x64 PF_FloatRGBA RT UAV)    TranslucentVolumeDir1 2048 KB
LogConsoleResponse:     98 = (3D 64x64x64 PF_FloatRGBA RT UAV)    TranslucentVolume1 2048 KB
LogConsoleResponse:     99 = (3D 64x64x64 PF_FloatRGBA RT UAV)    TranslucentVolumeDir0 2048 KB
LogConsoleResponse:    101 = (3D 64x64x64 PF_FloatRGBA RT UAV)    TranslucentVolume0 2048 KB
LogConsoleResponse:    102 = (2D 976x492 PF_G8 RT UAV)            ScreenSpaceAO 469 KB
LogConsoleResponse:    106 = (2D 488x246 PF_DepthStencil)         SmallDepthZ 1173 KB
LogConsoleResponse:    107 = (2D 1x1 PF_A32B32G32R32F RT UAV)     EyeAdaptation 1 KB
LogConsoleResponse: CheckpointName (what was rendered this frame, use <Name>@<Number> to get intermediate versions):
LogConsoleResponse: Pool: 43/112 MB (referenced/allocated)

 

11.5 요약

이 글에서는 주로 UE RDG의 기본 개념, 메서드, 렌더링 프로세스 및 주요 메커니즘에 대해 설명하여 독자들이 RDG에 대한 전반적인 이해를 돕고, 더 자세한 기술적 세부 사항과 원칙은 UE 소스 코드를 읽고 살펴볼 필요가 있습니다. 이 글에서 다루지 않은 RDG 세부 사항은 공식 RDG 101: 크래시 강좌를 통해 보충할 수 있습니다.

11.5.1 이 게시물에 대한 반성

관례대로 이 게시물에서는 RDG에 대한 이해와 깊이를 더하기 위해 몇 가지 작은 성찰을 제시합니다:

  • RDG의 단계는 무엇인가요? 각 단계의 기능은 무엇인가요? 각 단계의 특징은 무엇인가요?
  • RDG의 리소스와 RHI의 리소스의 차이점과 연결점은 무엇인가요? 어떻게 서로 변환할 수 있나요?
  • RDG를 사용하여 사용자 지정 CS 및 PS 드로잉 코드를 구현합니다.

 

참고 문헌

  • Unreal Engine Source
  • Rendering and Graphics
  • Materials
  • Graphics Programming
  • Render Dependency Graph
  • FrameGraph: Extensible Rendering Architecture in Frostbite
  • RDG 101: A Crash Cours
  • Frostbite Rendering Architecture and Real-time Procedural Shading & Texturing Techniques

원문
 

剖析虚幻渲染体系(11)- RDG - 0向往0 - 博客园

11.1 本篇概述 RDG全称是Rendering Dependency Graph,意为渲染依赖性图表,是UE4.22开始引进的全新的渲染子系统,基于有向无环图(Directed Acyclic Graph,DAG)的调度系统,用于执行渲染管线的整帧优化。 它

www.cnblogs.com


엮어 읽기.

[번역] 언리얼 엔진 RDG 소스 코드 분석

역자의 말. 여전히 언리얼 엔진의 이슈를 탐색 하고 렌더링 일부를 수정 하고 관리하고 있지만 RDG 에는 더 많은 이해가 필요 합니다. 그래서 읽고 탐구 해 볼만한 기사를 찾아 공유해 보고자 해

techartnomad.tistory.com

 

[주석번역] RDG 101 A Crash Course

역자 주. 요 몇일동안은 아트팀을 직접 지원하는 셰이더 함수작업을 했습니다. 엔진 소스를 수정해야 할 필요가 없는 수요 부터 우선순위를 올려서 작업중이죠. 최대한 엔진 소스를 고치지 않는

techartnomad.tistory.com