Skip to content

언리얼 렌더링 시스템 분석(11) - RDG

🌐 원문 링크: 剖析虚幻渲染体系(11)- RDG (cnblogs.com/timlly)
📅 원문 발행일: 2021-09-02 | ✍️ 저자: Timlly (00 / )

💡 시리즈 분류: Unreal Engine Rendering국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료


FrameGraph는 프레임에 사용되는 모든 정보를 포함하는 렌더 패스 및 리소스를 상위 수준으로 표현한 것입니다. 패스 간의 순서 및 종속 관계를 지정할 수 있습니다. 다음 그림은 예입니다.

Frost 엔진은 프레임 그래프를 사용하여 디퍼드 렌더링(Deferred Shading) 시퀀스 및 종속성 그래프를 구현합니다.

UE의 RDG는 프레임 그래프를 기반으로 커스터마이징되어 구현되었다고 해도 과언이 아닙니다. UE4.26에서는 RDG가 널리 대중화되었습니다. 장면 렌더링, 사후 처리, 조명 추적 등을 포함한 모듈은 모두 RHI 명령을 직접 호출하는 대신 RDG를 사용합니다.

이 문서에서는 UE RDG의 다음 내용을 주로 설명합니다.

  • RDG의 기본 개념과 종류.

  • RDG를 사용하는 방법.

  • RDG의 내부 메커니즘과 원리.

11.2 RDG 기본 사항

이 장에서는 먼저 RDG에 관련된 주요 유형, 개념, 인터페이스 등에 대해 설명합니다.

11.2.1 RDG 기본형

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

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

// RDG Pass타입/유형 .
enum class ERDGPassFlags : uint8
{
    None = 0,                 // 을(를) 활용하여 파라미터 의 AddPass함수 .
    Raster = 1 << 0,        // Pass을(를) 활용하여 래스터라이제이션 .
    Compute = 1 << 1,        // Pass을(를) 활용하여 compute.
    AsyncCompute = 1 << 2,    // Pass비동기 계산/산출(Calculate)을(를) 활용하여 compute
    Copy = 1 << 3,            // Pass을(를) 활용하여 .
    NeverCull = 1 << 4,     // 클리핑/클램핑(Clipping)최적화 , 을(를) 활용하여 pass.
    SkipRenderPass = 1 << 5,        // 무시/스킵(Ignore)BeginRenderPass/EndRenderPass, 을(를) 활용하여 호출(Call). 오직 Raster바인딩(Bind)유효(Valid). 비활성화(Disable)Pass.
    UntrackedAccess = 1 << 6,        // Pass의 RHI리소스 ,리소스 까지 RDG 내에서 ,그러나 리소스 현재(Current)상태 . 스케줄링 채널/패스(Pass)의 。레이턴시 까지 pass실행(Execute)。리소스 pass실행(Execute) 내에서 상태 。성능 의 배리어 。 및 AsyncCompute。
    Readback = Copy | NeverCull,    // Pass을(를) 활용하여 ,그러나 기록/쓰기(Write)리소스 (staging resource).

    CommandMask = Raster | Compute | AsyncCompute | Copy, // ,테이블 pass의 RHI의 타입/유형 .
    ScopeMask = NeverCull | UntrackedAccess // 에 의해 을(를) 활용하여 을(를) 활용하여 의
};

// Buffer플래그 .
enum class ERDGBufferFlags : uint8
{
    None = 0, // 플래그 .
    MultiFrame = 1 << 0 // .
};

// 텍스처(Texture)플래그 .
enum class ERDGTextureFlags : uint8
{
    None = 0,
    MultiFrame = 1 << 0, // .
    MaintainCompression = 1 << 1, // 텍스처(Texture)압축해제/언패킹(Unpacking)데이터 .
};

// UAV플래그 .
enum class ERDGUnorderedAccessViewFlags : uint8
{
    None = 0,
    SkipBarrier = 1 << 0 // 무시/스킵(Ignore)배리어 .
};

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

// 뷰(View)타입/유형 .
enum class ERDGViewType : uint8
{
    TextureUAV, // 텍스처(Texture)UAV(을(를) 활용하여 기록/쓰기(Write)데이터 )
    TextureSRV, // 텍스처(Texture)SRV(을(를) 활용하여 로드/읽기(Read)데이터 )
    BufferUAV,  // 버퍼 UAV(을(를) 활용하여 기록/쓰기(Write)데이터 )
    BufferSRV,  // 버퍼 SRV(을(를) 활용하여 로드/읽기(Read)데이터 )
    MAX
};

// 을(를) 활용하여 생성(Create)뷰(View)텍스처(Texture)데이터
enum class ERDGTextureMetaDataAccess : uint8
{
    None = 0,             // 기본값(Default)압축/패킹(Packing)을(를) 활용하여 .
    CompressedSurface,     // 압축/패킹(Packing)을(를) 활용하여 .
    Depth,      // 뎁스 기본값(Default)압축/패킹(Packing)을(를) 활용하여 .
    Stencil, // 스텐실 기본값(Default)압축/패킹(Packing)을(를) 활용하여 .
    HTile,      // HTile.
    FMask,   // FMask.
    CMask    // CMask.
};

// 의 C++객체/오브젝트 할당(Allocate), 을(를) 활용하여 MemStack할당(Allocate) 및 소멸 .
class FRDGAllocator final
{
public:
    FRDGAllocator();
    ~FRDGAllocator();

    // 할당(Allocate)메모리 .
    FORCEINLINE void* Alloc(uint32 SizeInBytes, uint32 AlignInBytes)
    {
        return MemStack.Alloc(SizeInBytes, AlignInBytes);
    }
    // 할당(Allocate)POD메모리 함수 .
    template <typename PODType>
    FORCEINLINE PODType* AllocPOD()
    {
        return reinterpret_cast<PODType*>(Alloc(sizeof(PODType), alignof(PODType)));
    }
    // 의 C++객체/오브젝트 할당(Allocate).
    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++객체/오브젝트 할당(Allocate). (, 을(를) 활용하여 )
    template <typename ObjectType, typename... TArgs>
    FORCEINLINE ObjectType* AllocNoDestruct(TArgs&&... Args)
    {
        return new (MemStack) ObjectType(Forward<TArgs&&>(Args)...);
    }

    // 해제(Release)할당(Allocate)의 메모리 .
    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;
    };

    // 할당(Allocate).
    FMemStackBase MemStack;
    // 할당(Allocate)의 객체/오브젝트 .
    TArray<FTrackedAlloc*, SceneRenderingAllocator> TrackedAllocs;
};

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

// 을(를) 활용하여 의 리소스 .
extern RENDERCORE_API void ClearUnusedGraphResourcesImpl(const FShaderParameterBindings& ShaderBindings, ...);
(......)

// 텍스처(Texture), 을(를) 활용하여 인스턴스 .
FRDGTextureRef RegisterExternalTextureWithFallback(FRDGBuilder& GraphBuilder, ...);
inline FRDGTextureRef TryRegisterExternalTexture(FRDGBuilder& GraphBuilder, ...);
inline FRDGBufferRef TryRegisterExternalBuffer(FRDGBuilder& GraphBuilder, ...);

// 계산/산출(Calculate)셰이딩 의 .
struct RENDERCORE_API FComputeShaderUtils
{
    // 의 크기 로 8x8,GCN개 wave,Nvidia개 warp.
    static constexpr int32 kGolden2DGroupSize = 8;
    static FIntVector GetGroupCount(const int32 ThreadCount, const int32 GroupSize);

    // 디스패치 계산/산출(Calculate)셰이딩 까지 RHI커맨드 리스트 , 파라미터 .
    template<typename TShaderClass>
    static void Dispatch(FRHIComputeCommandList& RHICmdList, const TShaderRef<TShaderClass>& ComputeShader, const typename TShaderClass::FParameters& Parameters, FIntVector GroupCount);
    // 디스패치 의 계산/산출(Calculate)셰이딩 까지 RHI커맨드 리스트 , 파라미터 .
    template<typename TShaderClass>
    static void DispatchIndirect(FRHIComputeCommandList& RHICmdList, const TShaderRef<TShaderClass>& ComputeShader, const typename TShaderClass::FParameters& Parameters, FRHIVertexBuffer* IndirectArgsBuffer, uint32 IndirectArgOffset);
    // 디스패치 계산/산출(Calculate)셰이딩 까지 render graph builder, 파라미터 .
    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);
};

// 추가(Add)텍스처(Texture)Pass.
void AddCopyTexturePass(FRDGBuilder& GraphBuilder, FRDGTextureRef InputTexture, FRDGTextureRef OutputTexture, const FRHICopyTextureInfo& CopyInfo);
(......)

// 추가(Add)까지 의 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);
(......)

// 추가(Add)텍스처(Texture)의 Pass.
void AddEnqueueCopyPass(FRDGBuilder& GraphBuilder, FRHIGPUTextureReadback* Readback, FRDGTextureRef SourceTexture, FResolveRect Rect = FResolveRect());
// 추가(Add)버퍼 의 Pass.
void AddEnqueueCopyPass(FRDGBuilder& GraphBuilder, FRHIGPUBufferReadback* Readback, FRDGBufferRef SourceBuffer, uint32 NumBytes);

// 생성(Create)리소스 .
FRDGBufferRef CreateStructuredBuffer(FRDGBuilder& GraphBuilder, ...);
FRDGBufferRef CreateVertexBuffer(FRDGBuilder& GraphBuilder, ...);

// 파라미터 의 Pass추가(Add).
template <typename ExecuteLambdaType>
void AddPass(FRDGBuilder& GraphBuilder, FRDGEventName&& Name, ExecuteLambdaType&& ExecuteLambda);
template <typename ExecuteLambdaType>
void AddPass(FRDGBuilder& GraphBuilder, ExecuteLambdaType&& ExecuteLambda);

// Pass
void AddBeginUAVOverlapPass(FRDGBuilder& GraphBuilder);
void AddEndUAVOverlapPass(FRDGBuilder& GraphBuilder);

(......)

11.2.2 RDG 리소스

RDG 리소스는 RHI 리소스를 직접 사용하지 않고 RHI 리소스 참조를 래핑한 다음 다양한 유형의 리소스를 별도로 캡슐화하고 추가 정보를 추가합니다. 일부 RDG 정의는 다음과 같습니다.

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

class FRDGResource
{
public:
    // 함수 .
    FRDGResource(const FRDGResource&) = delete;
    virtual ~FRDGResource() = default;

    //////////////////////////////////////////////////////////////////////////
    // 의 인터페이스 오직 RDG의 Pass실행(Execute)호출(Call).

    // 플래그 리소스 는 을(를) 활용하여 , , 이면 .
#if RDG_ENABLE_DEBUG
    virtual void MarkResourceAsUsed();
#else
    inline  void MarkResourceAsUsed() {}
#endif
    // 페치/가져오기(Fetch)RDG의 RHI리소스 을(를) 활용하여 .
    FRHIResource* GetRHI() const
    {
        ValidateRHIAccess();
        return ResourceRHI;
    }

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

protected:
    FRDGResource(const TCHAR* InName);

    // 리소스 할당(Allocate)로 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:
        // 리소스 는 pass의 lambda을(를) 활용하여 ,로써 검사/감지(Detect)pass의 리소스 .
        bool bIsActuallyUsedByPass = false;
        // Pass실행(Execute), 레벨 의 RHI는 .
        bool bAllowRHIAccess = false;
        // 만약 로 true,이면 리소스 까지 ,는 로써 개 ,을(를) 활용하여 코드 까지 RDG.
        bool bPassthrough = false;
    } DebugData;
#endif
};

class FRDGUniformBuffer : public FRDGResource
{
public:
    // 페치/가져오기(Fetch)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;
    // 글로벌 바인딩(Bind) 또는 로컬 바인딩(Bind).
    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;

    (......)
};

// 에 의해 할당(Allocate)의 렌더링(Render)리소스 。을(를) 활용하여 의 리소스 (예: 뷰(View))
class FRDGParentResource : public FRDGResource
{
public:
    // 리소스 타입/유형 .
    const ERDGParentResourceType Type;
    bool IsExternal() const;

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

    // 는 리소스 .
    uint8 bExternal : 1;
    // 는 의 리소스 .
    uint8 bExtracted : 1;
    // 리소스 는 acquire / discard.
    uint8 bTransient : 1;
    // 는 의 할당(Allocate)의 .
    uint8 bLastOwner : 1;
    // 클리핑/클램핑(Clipping).
    uint8 bCulled : 1;
    // 는 비동기 계산/산출(Calculate)Pass을(를) 활용하여 .
    uint8 bUsedByAsyncComputePass : 1;

private:
    // 을(를) 활용하여 개수 .
    uint16 ReferenceCount = 0;
    // 을(를) 활용하여 할당(Allocate)의 리소스 의 및 상태 (만약 )
    ERHIAccess AccessInitial = ERHIAccess::Unknown;
    ERHIAccess AccessFinal = ERHIAccess::Unknown;

    FRDGPassHandle AcquirePass;
    FRDGPassHandle FirstPass;
    FRDGPassHandle LastPass;

    (......)
};

// 생성(Create)렌더링(Render)텍스처(Texture)의 정보 .
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;
    // 픽셀(Pixel).
    EPixelFormat Format = PF_Unknown;
    // 텍스처(Texture)x 및 y 내에서 의 범위
    FIntPoint Extent = FIntPoint(1, 1);
    // 3D텍스처(Texture)의 뎁스 .
    uint16 Depth = 1;
    uint16 ArraySize = 1;
    // 텍스처(Texture)레벨 단계 .
    uint8 NumMips = 1;
    // 샘플 수.
    uint8 NumSamples = 1;
};

// 의 RTRDG텍스처(Texture).
inline FRDGTextureDesc Translate(const FPooledRenderTargetDesc& InDesc, ERenderTargetTexture InTexture = ERenderTargetTexture::Targetable);
// RDG텍스처(Texture)의 RT.
inline FPooledRenderTargetDesc Translate(const FRDGTextureDesc& InDesc);

// 의 텍스처(Texture).
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);

    // 초기화(Initialize)캐싱(Cache)의 UAV.
    void InitViews(const FUnorderedAccessViewRHIRef& FirstMipUAV);
    void Finalize();
    void Reset();

    // 의 RHI텍스처(Texture).
    FRHITexture* Texture = nullptr;
    // 의 텍스처(Texture)객체/오브젝트 .
    FRDGTexture* Owner = nullptr;
    // 리소스 .
    FRDGTextureSubresourceLayout Layout;
    // 리소스 상태 .
    FRDGTextureSubresourceState State;

    // 로 RHI텍스처(Texture)캐싱(Cache)의 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텍스처(Texture).
class RENDERCORE_API FRDGTexture final : public FRDGParentResource
{
public:
    // 로 까지 RDG의 Pass생성(Create)개 을(를) 활용하여 을(를) 활용하여 RDG파라미터 패딩/채우기 RHI버퍼 의 텍스처(Texture).
    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;

    // 을(를) 활용하여 리소스 변환(Transform/Convert)의 .
    FRDGTextureSubresourceLayout Layout;
    // 실행(Execute)PooledTexture할당(Allocate)의 개 텍스처(Texture).
    FRDGTextureHandle NextOwner;
    // 까지 의 .
    FRDGTextureHandle Handle;

    // 텍스처(Texture).
    IPooledRenderTarget* PooledRenderTarget = nullptr;
    FRDGPooledTexture* PooledTexture = nullptr;
    // 로부터 텍스처(Texture)캐싱(Cache)의 상태
    FRDGTextureSubresourceState* State = nullptr;
    // 을(를) 활용하여 유효(Valid), 
    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;
};

// 렌더링(Render)의 버퍼 .
class RENDERCORE_API FRDGBuffer final : public FRDGParentResource
{
public:
    const FRDGBufferDesc Desc;
    const ERDGBufferFlags Flags;

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

    // 페치/가져오기(Fetch)RHI리소스 .
    FRHIVertexBuffer* GetIndirectRHICallBuffer() const
    FRHIVertexBuffer* GetRHIVertexBuffer() const
    FRHIStructuredBuffer* GetRHIStructuredBuffer() const

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

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

    // 설정(Set)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 리소스는 기본적으로 캡슐화 및 래핑되어 RHI 리소스를 추가로 제어 및 관리하고, 수명 주기, 참조 관계, 디버깅 정보 등을 정확하게 제어하고, 렌더링 성능을 향상시키기 위해 이를 더욱 최적화하고 맞춤화합니다.

11.2.3 RDG 패스

RDG Pass 모듈에는 장벽, 리소스 변환 및 RDGPass와 같은 개념이 포함됩니다.

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

// 을(를) 활용하여 테이블 RHI 내에서 일시 중지 의 리소스 변환(Transform/Convert)의 반투명(Translucent)데이터 구조체 .
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();

    // 페치/가져오기(Fetch)의 할당(Allocate)해상도/크기 .
    static uint64 GetTotalAllocationSize()
    // 페치/가져오기(Fetch)정렬 .
    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();
        }
    }
    // 변환(Transform/Convert)리소스 , RHI변환(Transform/Convert) 및 할당(Allocate)의 메모리 .
    inline void Cleanup() const;

    mutable int8 State;

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

#if ENABLE_RHI_VALIDATION
    // 배리어/펜스 .
    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;
    // Graphics 또는 AsyncCompute
    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();

    // 추가(Add)리소스 변환(Transform/Convert)까지 회 .
    void AddTransition(FRDGParentResourceRef Resource, const FRHITransitionInfo& Info);

    const FRHITransition* GetTransition() const;
    bool IsTransitionValid() const;
    void SetUseCrossPipelineFence();
    // 배리어 /리소스 변환(Transform/Convert).
    void Submit(FRHIComputeCommandList& RHICmdList);

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

    // 저장(Store)의 리소스 변환(Transform/Convert), 처리(Process)null.
    const FRHITransition* Transition = nullptr;
    // 실행(Execute)의 비동기 리소스 변환(Transform/Convert)배열 .
    TArray<FRHITransitionInfo, TInlineAllocator<1, SceneRenderingAllocator>> Transitions;

#if RDG_ENABLE_DEBUG
    // 및 Transitions배열 매칭 의 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);
    // 처리(Process), 로써 개 .
    void AddDependency(FRDGBarrierBatchBegin* BeginBatch);
    // 리소스 변환(Transform/Convert).
    void Submit(FRHIComputeCommandList& RHICmdList);

private:
    // 로써 의 변환(Transform/Convert).
    TArray<FRDGBarrierBatchBegin*, TInlineAllocator<1, SceneRenderingAllocator>> Dependencies;
};

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

    // 채널/패스(Pass)데이터 인터페이스 .
    const TCHAR* GetName() const;
    FORCEINLINE const FRDGEventName& GetEventName() const;
    FORCEINLINE ERDGPassFlags GetFlags() const;
    FORCEINLINE ERHIPipeline GetPipeline() const;
    // RDG Pass파라미터 .
    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;
    // Pass.
    FRDGPassHandle GetGraphicsForkPass() const;
    // Pass.
    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

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

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

    // 실행(Execute).
    void Execute(FRHIComputeCommandList& RHICmdList);

    // Pass데이터 .
    const FRDGEventName Name;
    const FRDGParameterStruct ParameterStruct;
    const ERDGPassFlags Flags;
    const ERHIPipeline Pipeline;
    FRDGPassHandle Handle;

    // Pass플래그 .
    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;

    // (AsyncCompute)Graphics pass,채널/패스(Pass)는 비동기 계산/산출(Calculate)의 fork / join.
    FRDGPassHandle GraphicsForkPass;
    FRDGPassHandle GraphicsJoinPass;

    // 처리(Process)채널/패스(Pass)의 /배리어 의 채널/패스(Pass).
    FRDGPassHandle PrologueBarrierPass;
    FRDGPassHandle EpilogueBarrierPass;

    // Pass리스트 .
    FRDGPassHandleArray Producers;

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

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

    // 텍스처(Texture)/버퍼 매핑 까지 Pass 내에서 을(를) 활용하여 의 정보 。  
    TSortedMap<FRDGTexture*, FTextureState, SceneRenderingAllocator> TextureStates;
    TSortedMap<FRDGBuffer*, FBufferState, SceneRenderingAllocator> BufferStates;
    // 실행(Execute)Pass,의 Pass파라미터 리스트 .
    TArray<FRDGPass*, TInlineAllocator<1, SceneRenderingAllocator>> ResourcesToBegin;
    TArray<FRDGPass*, TInlineAllocator<1, SceneRenderingAllocator>> ResourcesToEnd;
    // acquire**,***페치/가져오기(Fetch)의 텍스처(Texture)리스트 . 페치/가져오기(Fetch)을(를) 활용하여 할당(Allocate)의 텍스처(Texture).
    TArray<FRHITexture*, SceneRenderingAllocator> TexturesToAcquire;
    // Pass**,(acquires)**,의 텍스처(Texture)리스트 . 을(를) 활용하여 플래그 로 (transient)의 텍스처(Texture),텍스처(Texture)의 개 (alia)을(를) 활용하여 로 (를 위해 지원 의 까지 을(를) 활용하여 또는 반환(Return)).
    TArray<FRHITexture*, SceneRenderingAllocator> TexturesToDiscard;

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

    EAsyncComputeBudget AsyncComputeBudget = EAsyncComputeBudget::EAll_4;
};

// RDG Pass Lambda실행(Execute)함수 .
template <typename ParameterStructType, typename ExecuteLambdaType>
class TRDGLambdaPass : public FRDGPass
{
    (......)

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

private:
    // 실행(Execute)구현 .
    void ExecuteImpl(FRHIComputeCommandList& RHICmdList) override
    {
        check(!kSupportsRaster || RHICmdList.IsImmediate());
        // 호출(Call)Lambda인스턴스 .
        ExecuteLambda(static_cast<TRHICommandList&>(RHICmdList));
    }

    Lambda实例.
    ExecuteLambdaType ExecuteLambda;
};

// Lambda의 Pass.
template <typename ExecuteLambdaType>
class TRDGEmptyLambdaPass : public TRDGLambdaPass<FEmptyShaderParameters, ExecuteLambdaType>
{
public:
    TRDGEmptyLambdaPass(FRDGEventName&& InName, ERDGPassFlags InPassFlags, ExecuteLambdaType&& InExecuteLambda);

private:
    FEmptyShaderParameters EmptyShaderParameters;
};

// 을(를) 활용하여 /Pass.
class FRDGSentinelPass final : public FRDGPass
{
public:
    FRDGSentinelPass(FRDGEventName&& Name);

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

위의 내용은 RDG Pass가 상대적으로 복잡하며 소비자, 생산자, 변환 종속성, 다양한 리소스 상태 등의 데이터 및 처리와 관련된 RDG 시스템의 핵심 유형 중 하나임을 보여줍니다. RDG Pass에는 다음과 같은 유형이 있습니다.

클래스 다이어그램-v2 FRDGPass <|-- TRDGLambdaPass FRDGPass <|-- FRDGSentinelPass

RDG 패스와 렌더링 패스 간에는 일대일 대응이 없습니다. 여러 RDG 패스를 하나의 렌더링 패스로 병합할 수 있습니다. 자세한 내용은 다음 장을 참조하세요. RDG Pass의 가장 복잡한 측면은 멀티스레딩 처리, 리소스 상태 변환 및 종속성 처리이지만 지금은 이 섹션에서 다루지 않고 후속 장에서 자세히 설명합니다.

11.2.4 FRDGBuilder

cpp
FRDGBuilder는 RDG 시스템의 핵심이자 엔진입니다. 그것은 또한 큰 가정부입니다. 렌더링 패스 및 매개변수 수집, 패스 및 데이터 컴파일, 리소스 종속성 처리, 다양한 데이터 클리핑 및 최적화, 실행 인터페이스 제공을 담당합니다. 그 진술은 다음과 같다:
cpp
class RENDERCORE_API FRDGBuilder
{
public:
    FRDGBuilder(FRHICommandListImmediate& InRHICmdList, FRDGEventName InName = {}, const char* UnaccountedCSVStat = kDefaultUnaccountedCSVStat);
    FRDGBuilder(const FRDGBuilder&) = delete;

    // 탐색/조회(Find/Lookup)텍스처(Texture), 까지 반환(Return)null.
    FRDGTextureRef FindExternalTexture(FRHITexture* Texture) const;
    FRDGTextureRef FindExternalTexture(IPooledRenderTarget* ExternalPooledTexture, ERenderTargetTexture Texture) const;

    // RT까지 RDG, 로써 RDG. RTRHI텍스처(Texture): MSAA 및 MSAA.
    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);

    // 리소스 생성(Create)인터페이스 .
    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);

    // 할당(Allocate)메모리 , 메모리 에 의해 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();
    
    // 추가(Add)파라미터 및 Lambda의 Pass.
    template <typename ParameterStructType, typename ExecuteLambdaType>
    FRDGPassRef AddPass(FRDGEventName&& Name, const ParameterStructType* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);
    // 추가(Add)파라미터 오직 Lambda의 Pass.
    template <typename ExecuteLambdaType>
    FRDGPassRef AddPass(FRDGEventName&& Name, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);

    // Builder실행(Execute), 텍스처(Texture)까지 의 . RDG생성(Create)의 리소스 , GPU리소스 의 ,까지 실행(Execute),패딩/채우기 . 만약 ,텍스처(Texture)변환(Transform/Convert)로 AccessFinal상태 , 이면 변환(Transform/Convert)로 kDefaultAccessFinal상태 .
    void QueueTextureExtraction(FRDGTextureRef Texture, TRefCountPtr<IPooledRenderTarget>* OutPooledTexturePtr);
    void QueueTextureExtraction(FRDGTextureRef Texture, TRefCountPtr<IPooledRenderTarget>* OutPooledTexturePtr, ERHIAccess AccessFinal);

    // Builder실행(Execute), 버퍼 까지 의 .
    void QueueBufferExtraction(FRDGBufferRef Buffer, TRefCountPtr<FRDGPooledBuffer>* OutPooledBufferPtr);
    void QueueBufferExtraction(FRDGBufferRef Buffer, TRefCountPtr<FRDGPooledBuffer>* OutPooledBufferPtr, ERHIAccess AccessFinal);

    // 할당(Allocate)리소스 . 오직 RDG생성(Create)의 리소스 , 즉, 할당(Allocate)레벨 리소스 , 유효(Valid)까지 리소스 . 추가(Add)메모리 ,그러나 을(를) 활용하여 GetPooled{Texture, Buffer} 내에서 의 리소스 . 을(를) 활용하여 코드 까지 RDG.
    void PreallocateTexture(FRDGTextureRef Texture);
    void PreallocateBuffer(FRDGBufferRef Buffer);

    // 즉, 페치/가져오기(Fetch)레벨 리소스 , 오직 을(를) 활용하여 또는 할당(Allocate)의 리소스 .
    const TRefCountPtr<IPooledRenderTarget>& GetPooledTexture(FRDGTextureRef Texture) const;
    const TRefCountPtr<FRDGPooledBuffer>& GetPooledBuffer(FRDGBufferRef Buffer) const;

    // 설정(Set)실행(Execute)의 상태 .
    void SetTextureAccessFinal(FRDGTextureRef Texture, ERHIAccess Access);
    void SetBufferAccessFinal(FRDGBufferRef Buffer, ERHIAccess Access);

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

    // 실행(Execute)Pass,렌더링(Render)(RHI RenderPasses)의 설정(Set),리소스 변환(Transform/Convert) 및 텍스처(Texture).
    void Execute();

    // 렌더링(Render)리소스 의 갱신(Update).
    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;

    // 클리핑/클램핑(Clipping)의 Pass.
    FRDGPassBitArray PassesToCull;
    // 파라미터 의 Pass.
    FRDGPassBitArray PassesWithEmptyParameters;

    // 리소스 까지 의 렌더링(Render),로써 데이터 .
    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;
    // 을(를) 활용하여 내에서 의 텍스처(Texture)상태 , 저장(Save)로써 할당(Allocate).
    FRDGTextureTransientSubresourceStateIndirect ScratchTextureState;
    EAsyncComputeBudget AsyncComputeBudgetScope = EAsyncComputeBudget::EAll_4;

    // .
    void Compile();
    // .
    void Clear();

    // 리소스 변환(Transform/Convert).
    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);

    // 리소스 변환(Transform/Convert).
    void EndResourceRHI(FRDGPassHandle, FRDGTexture* Texture, uint32 ReferenceCount);
    void EndResourceRHI(FRDGPassHandle, FRDGBuffer* Buffer, uint32 ReferenceCount);

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

    // Pass.
    void ExecutePassPrologue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass);
    void ExecutePassEpilogue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass);

    // 리소스 및 배리어 .
    void CollectPassResources(FRDGPassHandle PassHandle);
    void CollectPassBarriers(FRDGPassHandle PassHandle, FRDGPassHandle& LastUntrackedPassHandle);

    // 추가(Add)Pass.
    void AddPassDependency(FRDGPassHandle ProducerHandle, FRDGPassHandle ConsumerHandle);

    // 추가(Add)변환(Transform/Convert).
    void AddEpilogueTransition(FRDGTextureRef Texture, FRDGPassHandle LastUntrackedPassHandle);
    void AddEpilogueTransition(FRDGBufferRef Buffer, FRDGPassHandle LastUntrackedPassHandle);

    // 추가(Add)변환(Transform/Convert).
    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);

    // 페치/가져오기(Fetch)렌더링(Render)Pass정보 .
    FRHIRenderPassInfo GetRenderPassInfo(const FRDGPass* Pass) const;
    // 할당(Allocate)리소스 .
    FRDGSubresourceState* AllocSubresource(const FRDGSubresourceState& Other);

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

RDG 시스템의 드라이버로서 FRDGBuilder는 데이터 저장, 상태 전환 처리, 리소스 수명주기 및 장벽 자동 관리, 유효하지 않은 리소스 정리, Pass 수집, 컴파일, 실행, 텍스처 또는 버퍼 추출 및 기타 기능을 담당합니다. 내부 실행 메커니즘은 상대적으로 복잡하므로 후속 장에서 자세히 분석할 것입니다.

11.3 RDG 메커니즘

이 섹션에서는 주로 RDG의 작동 메커니즘, 프로세스 및 원리뿐만 아니라 렌더링의 장점과 특징에 대해 설명합니다.

일부 학생이 RDG 사용법만 배우고 싶다면 이 장을 건너뛰고 11.4 RDG 개발을 직접 읽어보세요.

11.3.1 RDG 메커니즘 개요

Pass로 설계된 Lambda 범위를 설정하고 지연된 실행을 사용하여 RHI에 GPU 명령을 내리는 렌더링 종속성 그래프 프레임워크(Rendering 종속성 그래프 프레임워크). FRDGBuider::AddPass()를 통해 생성됩니다. 패스를 생성할 때 셰이더 매개변수가 필요합니다. 모든 셰이더 매개변수가 될 수 있지만 프레임워크는 그래픽 리소스 렌더링에 가장 관심이 있습니다.

모든 Pass 매개변수를 보유하는 구조는 Lambda 실행이 지연되므로 올바른 수명을 보장하기 위해 FRDGBuilder::AllocParameters()를 사용하여 할당되어야 합니다.

cpp
FRDGBuilder::CreateTexture() 또는 FRDGBuilder::CreateBuffer()로 생성된 렌더링 리소스는 리소스 설명자만 기록합니다. 자원이 필요할 때 차트에 따라 할당됩니다. 렌더 그래프는 리소스의 수명 주기를 추적하고 나머지 패스가 더 이상 메모리를 참조하지 않을 때 메모리를 해제하고 재사용합니다.

패스에서 사용하는 모든 렌더링 리소스는 FRDGBuider::AddPass()에서 제공하는 Pass 매개변수에 있어야 합니다. 렌더링에서는 각 패스에서 어떤 리소스가 사용되는지 알아야 하기 때문입니다.

Pass가 실행될 때만 리소스 할당이 보장됩니다. 따라서 이에 대한 액세스는 FRDGBuider::AddPass()를 사용하여 생성된 패스의 Lambda 범위 내에서만 수행되어야 합니다. Pass에서 사용하는 일부 리소스를 나열하지 않으면 문제가 발생할 수 있습니다.

패스에 필요한 것보다 더 많은 그래프 리소스를 매개변수에서 참조하지 않는 것이 중요합니다. 이렇게 하면 리소스의 수명 주기에 대한 그래프 정보가 인위적으로 증가하기 때문입니다. 이로 인해 메모리 사용량이 증가하거나 패스 실행이 겹치는 것을 방지할 수 있습니다. 예를 들어 ClearUnusedGraphResources()는 셰이더에서 사용되지 않은 리소스 참조를 자동으로 지울 수 있습니다. 패스에서 리소스가 사용되지 않으면 경고가 발행됩니다.

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

레거시 코드로 생성된 풀링된 리소스 텍스처 FPooledRenderTarget은 FRDGBuider::RegisterExternalTexture()를 사용하여 렌더 그래프에서 사용할 수 있습니다.

패스 종속성에 대한 정보를 사용하면 실행 시 메모리 부족 우선순위 지정 또는 패스 GPU 실행 동시성 우선순위 지정과 같은 다양한 하드웨어 대상의 우선순위를 지정할 수 있습니다. 따라서 Pass의 실행 순서를 보장할 수 없습니다. Pass의 실행 순서는 즉시 모드가 GPU에서 작업을 수행하는 것처럼 중간 리소스에서 작업이 수행되도록 보장합니다.

렌더 그래프 패스는 외부 데이터 구조의 상태를 수정해서는 안 됩니다. 패스가 실행되는 순서에 따라 극단적인 경우가 발생할 수 있기 때문입니다. FRDGBuider::QueueTextureExtraction()은 실행에서 살아남은 렌더링 리소스(예: 뷰포트 백 버퍼, TAA 기록...)를 추출하는 데 사용해야 합니다. 가져올 예정인 리소스를 생성하거나 외부 텍스처를 수정하는 데 유용하지 않다는 것이 감지되면 패스는 경고를 실행하지 않을 수도 있습니다.

강력한 기술적인 이유(예: VR을 위해 한 번에 여러 보기의 입체 렌더링)가 없는 한 동일한 패스에서 여러 리소스에 대한 여러 작업을 번들로 묶지 마세요. 이로 인해 작업 집합에 더 많은 종속성이 생성되고 단일 작업에는 이러한 종속성의 하위 집합만 필요할 수 있습니다. 스케줄러는 이 중 일부를 다른 GPU 작업과 겹칠 수 있습니다. 이는 또한 할당된 임시 리소스를 장기간 동안 유지하여 잠재적으로 프레임 전반에 걸쳐 최대 메모리 압력 피크를 증가시킬 수 있습니다.

AddPass()는 람다 범위에 실행이 지연될 것으로 예상하지만 이것이 우리가 이를 작성해야 한다는 의미는 아닙니다. 대부분 상황의 요구 사항은 더 간단한 도구 상자(FComputeShaderUtils, FPixelShaderUtils 등)를 사용하여 충족할 수 있습니다.

11.3.2 FRDGBuilder::AddPass

cpp
FRDGBuilder::AddPass는 Pass 매개변수와 Lambda를 포함하는 Pass를 RDG 시스템에 추가하는 것입니다. 구체적인 논리는 다음과 같습니다.
cpp
// 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>;

    (......)

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

    // 추가(Add)Pass리스트 .
    Passes.Insert(Pass);
    // 설정(Set)Pass.
    SetupPass(Pass);
    
    return Pass;
}

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

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

    bool bPassUAVAccess = false;

    // ----처리(Process)텍스처(Texture)상태 ----
    
    Pass->TextureStates.Reserve(PassParameters.GetTextureParameterCount() + (PassParameters.HasRenderTargets() ? (MaxSimultaneousRenderTargets + 1) : 0));
    // 순회(Traverse/Iterate)텍스처(Texture), 개 텍스처(Texture)실행(Execute)상태 /데이터 /을(를) 활용하여 처리(Process).
    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);
    });

    // ----처리(Process)버퍼 상태 ----
    
    Pass->BufferStates.Reserve(PassParameters.GetBufferParameterCount());
    // 순회(Traverse/Iterate)버퍼 , 개 버퍼 실행(Execute)상태 /데이터 /을(를) 활용하여 처리(Process).
    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);

    // Graphics파이프라인 , Pass로써 /Pass의 리소스 . 비동기 계산/산출(Calculate)이면 .
    if (PassPipeline == ERHIPipeline::Graphics && !bEmptyParameters)
    {
        Pass->ResourcesToBegin.Add(Pass);
        Pass->ResourcesToEnd.Add(Pass);
    }

    // 설정(Set)Pass.
    SetupPassInternal(Pass, PassHandle, PassPipeline);
}

계속해서 SetupPassInternal을 구문 분석해 보겠습니다.

cpp
void FRDGBuilder::SetupPassInternal(FRDGPass* Pass, FRDGPassHandle PassHandle, ERHIPipeline PassPipeline)
{
    // 설정(Set)Pass로 .
    Pass->GraphicsJoinPass = PassHandle;
    Pass->GraphicsForkPass = PassHandle;
    Pass->PrologueBarrierPass = PassHandle;
    Pass->EpilogueBarrierPass = PassHandle;

    (......)

    // 만약 는 즉, Pass, 
    if (GRDGImmediateMode && Pass != EpiloguePass)
    {
        // merge상태 pass상태 ,이므로 .
        
        // 텍스처(Texture)의 Merge상태 .
        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);
                }
            }
        }
        // 버퍼 의 Merge상태 .
        for (auto& BufferPair : Pass->BufferStates)
        {
            auto& PassState = BufferPair.Value;
            PassState.MergeState = &PassState.State;
            PassState.MergeState->SetPass(PassHandle);
        }

        FRDGPassHandle LastUntrackedPassHandle = GetProloguePassHandle();
        // Pass리소스 .
        CollectPassResources(PassHandle);
        // Pass배리어 .
        CollectPassBarriers(PassHandle, LastUntrackedPassHandle);
        // 실행(Execute)Pass.
        ExecutePass(Pass);
    }
}

요약하면 AddPass는 들어오는 매개변수를 기반으로 RDG 패스의 인스턴스를 구성한 다음 패스의 텍스처와 버퍼 데이터를 설정하고 내부적으로 설정된 패스의 종속 패스 및 기타 핸들을 사용합니다. Immediate 모드인 경우 텍스처와 버퍼의 Merge 상태를 Pass 상태로 리디렉션하고 직접 실행합니다.

11.3.3 FRDGBuilder::컴파일

cpp
FRDGBuilder의 컴파일 로직은 매우 복잡하며 다음과 같이 많은 처리와 최적화가 필요합니다.
cpp
void FRDGBuilder::Compile()
{
    uint32 RasterPassCount = 0;
    uint32 AsyncComputePassCount = 0;

    // Pass플래그 .
    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;
    };

    // 내에서 /,의 데이터 배열 ,로써 검색 조건 의 Pass의 캐싱(Cache).
    // 검색 을(를) 활용하여 . 의 RHI출력(e.g. SHADER_PARAMETER_{BUFFER, TEXTURE}_UAV)의 Pass클리핑/클램핑(Clipping), 기록/쓰기(Write)리소스 의 Pass.
    // 리소스 까지 (epilogue)Pass,Pass는 의 。 및 는 Pass,。
    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Culling_Dependencies, FColor::Emerald);

        // 추가(Add)클리핑/클램핑(Clipping).
        const auto AddCullingDependency = [&](FRDGPassHandle& ProducerHandle, FRDGPassHandle PassHandle, ERHIAccess Access)
        {
            if (Access != ERHIAccess::Unknown)
            {
                if (ProducerHandle.IsValid())
                {
                    // 추가(Add)Pass.
                    AddPassDependency(ProducerHandle, PassHandle);
                }

                // 만약 , 이면 저장(Store)의 .
                if (IsWritableAccess(Access))
                {
                    ProducerHandle = PassHandle;
                }
            }
        };

        // 순회(Traverse/Iterate)Pass, 처리(Process)개 Pass의 텍스처(Texture) 및 버퍼 상태 .
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            FRDGPass* Pass = Passes[PassHandle];

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

            // 처리(Process)Pass의 텍스처(Texture)상태 .
            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);

                // 배열 및 pass상태 배열 .
                if (bWholeProducers && !bWholePassState)
                {
                    InitAsSubresources(LastProducers, Texture->Layout);
                }

                // 추가(Add)클리핑/클램핑(Clipping).
                for (uint32 Index = 0, Count = LastProducers.Num(); Index < Count; ++Index)
                {
                    AddCullingDependency(LastProducers[Index], PassHandle, PassState[bWholePassState ? 0 : Index].Access);
                }

                bUntrackedOutputs |= Texture->bExternal;
            }

            // 처리(Process)Pass의 버퍼 상태 .
            for (auto& BufferPair : Pass->BufferStates)
            {
                FRDGBufferRef Buffer = BufferPair.Key;
                AddCullingDependency(Buffer->LastProducer, PassHandle, BufferPair.Value.State.Access);
                bUntrackedOutputs |= Buffer->bExternal;
            }

            // 처리(Process)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;
        }

        // prologue/epilogue설정(Set)로 , 리소스 의 /추출/내보내기(Export).
        PassesWithUntrackedOutputs[ProloguePassHandle] = true;
        PassesWithUntrackedOutputs[EpiloguePassHandle] = true;

        // 처리(Process)텍스처(Texture)의 클리핑/클램핑(Clipping).
        for (const auto& Query : ExtractedTextures)
        {
            FRDGTextureRef Texture = Query.Key;
            for (FRDGPassHandle& ProducerHandle : Texture->LastProducers)
            {
                AddCullingDependency(ProducerHandle, EpiloguePassHandle, Texture->AccessFinal);
            }
            Texture->ReferenceCount++;
        }

        // 처리(Process)버퍼 의 클리핑/클램핑(Clipping).
        for (const auto& Query : ExtractedBuffers)
        {
            FRDGBufferRef Buffer = Query.Key;
            AddCullingDependency(Buffer->LastProducer, EpiloguePassHandle, Buffer->AccessFinal);
            Buffer->ReferenceCount++;
        }
    }

    // -------- 처리(Process)Pass클리핑/클램핑(Clipping) --------
    
    if (GRDGCullPasses)
    {
        TArray<FRDGPassHandle, TInlineAllocator<32, SceneRenderingAllocator>> PassStack;
        // Pass초기화(Initialize)로 컬링 .
        PassesToCull.Init(true, Passes.Num());

        // Pass의 리스트 , 조건 의 는 의 출력 또는 플래그 로 컬링 의 Pass.
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            if (PassesWithUntrackedOutputs[PassHandle] || PassesToNeverCull[PassHandle])
            {
                PassStack.Add(PassHandle);
            }
        }

        // 루프 의 순회(Traverse/Iterate), 을(를) 활용하여 뎁스 검색 , 플래그 개 의 Pass포인트 로 클리핑/클램핑(Clipping).
        while (PassStack.Num())
        {
            const FRDGPassHandle PassHandle = PassStack.Pop();

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

            #if STATS
                --GRDGStatPassCullCount;
            #endif
            }
        }
    }
    else // 활성화(Enable)Pass클리핑/클램핑(Clipping), Pass초기화(Initialize)로 클리핑/클램핑(Clipping).
    {
        PassesToCull.Init(false, Passes.Num());
    }
    
    // -------- 처리(Process)Pass배리어 --------

    // 순회(Traverse/Iterate)의 ,로 개 리소스 배리어 , 는 의 , 예: read-to-read。
    // RDG을(를) 활용하여 의 ,실행(Execute)변환(Transform/Convert). 
    // 는 개 의 단계/스텝 。상태 회 및 회 의 Pass. Pass의 을(를) 활용하여 누적(Accumulate)까지 개 리소스 . 
    // 필수: 컬링 ,이므로 컬링 의 Pass을(를) 활용하여 .

    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Barriers, FColor::Emerald);

        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            // 클리핑/클램핑(Clipping) 또는 파라미터 의 Pass.
            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))
                {
                    // 파이프라인 、의 상태 개 의 pass.
                    if (ResourceMergeState && ResourceMergeState->Pipeline != PassState.Pipeline)
                    {
                        AddPassDependency(ResourceMergeState->LastPass, PassHandle);
                    }

                    // 할당(Allocate)개 의 일시 중지 의 상태 ,할당(Allocate)pass상태 .
                    ResourceMergeState = AllocSubresource(PassState);
                    ResourceMergeState->SetPass(PassHandle);
                }
                else
                {
                    // Pass상태 의 상태 .
                    ResourceMergeState->Access |= PassState.Access;
                    ResourceMergeState->LastPass = PassHandle;
                }

                PassMergeState = ResourceMergeState;
            };

            const bool bAsyncComputePass = PassesOnAsyncCompute[PassHandle];

            // 페치/가져오기(Fetch)현재(Current)처리(Process)의 Pass인스턴스 .
            FRDGPass* Pass = Passes[PassHandle];

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

                // 추가(Add)을(를) 활용하여 개수 .
                Texture->ReferenceCount += PassState.ReferenceCount;
                Texture->bUsedByAsyncComputePass |= bAsyncComputePass;

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

                // 로 ,/Pass상태 매칭 .
                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]);
                }
            }

            // 처리(Process)현재(Current)Pass의 버퍼 상태 .
            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);
            }
        }
    }

    // 처리(Process)비동기 계산/산출(Calculate)Pass.
    if (AsyncComputePassCount > 0)
    {
        SCOPED_NAMED_EVENT(FRDGBuilder_Compile_AsyncCompute, FColor::Emerald);

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

        // 순회(Traverse/Iterate)실행(Execute)의 Pass,로써 로 개 Pass까지 의 및 의 , 로써 비동기 계산/산출(Calculate)오버랩 검색 .
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            if (PassesToCull[PassHandle] || PassesWithEmptyParameters[PassHandle])
            {
                continue;
            }

            FRDGPass* Pass = Passes[PassHandle];

            // 순회(Traverse/Iterate), 처리(Process) 및 의 을(를) 활용하여 .
            for (FRDGPassHandle ProducerHandle : Pass->GetProducers())
            {
                const FRDGPassHandle ConsumerHandle = PassHandle;

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

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

                // 로 탐색/조회(Find/Lookup)개 의 .
                if (Producer->CrossPipelineConsumer.IsNull() || IsSortedBefore(ConsumerHandle, Producer->CrossPipelineConsumer))
                {
                    Producer->CrossPipelineConsumer = PassHandle;
                    PassesWithCrossPipelineConsumer[ProducerHandle] = true;
                }

                // 로 탐색/조회(Find/Lookup)개 의 .
                if (Consumer->CrossPipelineProducer.IsNull() || IsSortedAfter(ProducerHandle, Consumer->CrossPipelineProducer))
                {
                    Consumer->CrossPipelineProducer = ProducerHandle;
                    PassesWithCrossPipelineProducer[ConsumerHandle] = true;
                }
            }
        }

        // 로 비동기 계산/산출(Calculate)fork / join오버랩 , 을(를) 활용하여 배리어/펜스 및 리소스 할당(Allocate)/. fork/join,비동기 계산/산출(Calculate)Pass할당(Allocate)/해제(Release)의 리소스 을(를) 활용하여 ,이므로 개 는 병렬 의 。,비동기 계산/산출(Calculate)의 리소스 확장 까지 개 비동기 。

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

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

        // 탐색/조회(Find/Lookup).
        const auto FindCrossPipelineProducer = [&](FRDGPassHandle PassHandle)
        {
            FRDGPassHandle LatestProducerHandle = ProloguePassHandle;
            FRDGPassHandle ConsumerHandle = PassHandle;

            // 까지 의 ,로써 개 포인트 . 이므로 로써 을(를) 활용하여 N개 채널/패스(Pass)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;
        };

        // 탐색/조회(Find/Lookup).
        const auto FindCrossPipelineConsumer = [&](FRDGPassHandle PassHandle)
        {
            check(PassHandle != EpiloguePassHandle);

            FRDGPassHandle EarliestConsumerHandle = EpiloguePassHandle;
            FRDGPassHandle ProducerHandle = PassHandle;

            // 까지 개 의 을(를) 활용하여 ,이므로 포인트 。이므로 로써 개 로 N개 ,따라서 오직 개 실행(Execute)의 . 
            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까지 비동기 계산/산출(Calculate)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);
        };

        // 비동기 계산/산출(Calculate)Pass까지 Pass의 내에서 .
        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;

        // 순회(Traverse/Iterate)Pass, 확장 리소스 의 , 처리(Process)Pass 및 비동기 계산/산출(Calculate)Pass의 및 포인트 .
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            if (!PassesOnAsyncCompute[PassHandle] || PassesToCull[PassHandle])
            {
                continue;
            }

            FRDGPass* AsyncComputePass = Passes[PassHandle];

            // 까지 Pass 및 Pass.
            const FRDGPassHandle GraphicsForkPassHandle = FindCrossPipelineProducer(PassHandle);
            const FRDGPassHandle GraphicsJoinPassHandle = FindCrossPipelineConsumer(PassHandle);

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

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

            // 비동기 계산/산출(Calculate) 내에서 을(를) 활용하여 의 리소스 의 까지 fork/joinPass。
            GraphicsForkPass->ResourcesToBegin.Add(AsyncComputePass);
            GraphicsJoinPass->ResourcesToEnd.Add(AsyncComputePass);

            // Pass까지 비동기 계산/산출(Calculate)Pass.
            if (PrevGraphicsForkPass != GraphicsForkPass)
            {
                InsertGraphicsToAsyncComputeFork(GraphicsForkPass, AsyncComputePass);
            }

            // 비동기 계산/산출(Calculate)Pass까지 Pass.
            if (PrevGraphicsJoinPass != GraphicsJoinPass && PrevAsyncComputePass)
            {
                InsertAsyncToGraphicsComputeJoin(PrevAsyncComputePass, PrevGraphicsJoinPass);
            }

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

        // 내에서 의 개 비동기 계산/산출(Calculate)Passepilogue pass.
        if (PrevAsyncComputePass)
        {
            InsertAsyncToGraphicsComputeJoin(PrevAsyncComputePass, EpiloguePass);
            PrevAsyncComputePass->bAsyncComputeEndExecute = 1;
        }
    }

    // 순회(Traverse/Iterate)Pass, RT의 래스터라이제이션 Pass까지 개 RHI렌더링(Render)Pass 내에서 .
    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의 개 의 렌더링(Render)Pass: [B, X, X, X, X, E], Pass(B) 및 Pass(E)호출(Call)BeginRenderPass/EndRenderPass.
                // ,begin처리(Process)개 의 배리어 ,end처리(Process)배리어 , 로써 렌더링(Render)채널/패스(Pass)의 리소스 변환(Transform/Convert),유효(Valid)처리(Process)리소스 변환(Transform/Convert).
                // 순회(Traverse/Iterate)필터링 의 Pass의 . 
                
                // (B)는 시퀀스 의 개 Pass.
                {
                    FRDGPass* Pass = Passes[FirstPassHandle];
                    Pass->bSkipRenderPassEnd = 1;
                    Pass->EpilogueBarrierPass = LastPassHandle;
                }

                // (X)는 내에서 Pass.
                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)는 시퀀스 의 Pass.
                {
                    FRDGPass* Pass = Passes[LastPassHandle];
                    Pass->bSkipRenderPassBegin = 1;
                    Pass->PrologueBarrierPass = FirstPassHandle;
                }

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

        // 순회(Traverse/Iterate)Pass, RT의 Pass까지 개 렌더링(Render)Pass 내에서 .
        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            // 클리핑/클램핑(Clipping)의 Pass.
            if (PassesToCull[PassHandle])
            {
                continue;
            }

            // 는 Pass처리(Process).
            if (PassesOnRaster[PassHandle])
            {
                FRDGPass* NextPass = Passes[PassHandle];

                // 을(를) 활용하여 렌더링(Render)Pass의 Pass 및 Pass,UAV의 Pass에 의해 의 .
                if (EnumHasAnyFlags(NextPass->GetFlags(), ERDGPassFlags::SkipRenderPass) || NextPass->bUAVAccess)
                {
                    CommitMerge();
                    continue;
                }

                // Pass 및 의 Pass.
                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
                        )
                    {
                        // 만약 로써 , 추가(Add)Pass까지 PassesToMerge리스트 .
                        if (!PassesToMerge.Num())
                        {
                            PassesToMerge.Add(PrevPass->GetHandle());
                        }
                        PassesToMerge.Add(PassHandle);
                    }
                    else
                    {
                        CommitMerge();
                    }
                }

                PrevPass = NextPass;
                PrevRenderTargets = &RenderTargets;
            }
            else if (!PassesOnAsyncCompute[PassHandle])
            {
                // 의 PassRT무효(Invalid).
                CommitMerge();
            }
        }

        CommitMerge();
    }
}

위의 코드는 RDG 컴파일 중 로직이 매우 복잡하고 많은 단계로 구성되어 있음을 보여줍니다. 생산자와 소비자 간의 종속 관계 구축, 패스 및 기타 태그 절단 결정, 리소스 수명 주기 조정, 패스 절단, 패스 리소스 변환 및 장벽 처리, 패스 비동기 계산의 종속성 및 참조 관계 처리, 포크 및 병합된 패스 노드 찾기 및 설정, 동일한 특정 렌더링 대상의 래스터라이제이션된 모든 패스 병합 등의 단계를 연속적으로 거쳤습니다.

위의 코드에는 몇 가지 중요한 인터페이스도 포함되어 있으며 아래에서 하나씩 분석됩니다.

cpp
// 추가(Add)Pass, (ProducerHandle)추가(Add)까지 (ConsumerHandle)의 리스트 (Producers) 내에서 .
void FRDGBuilder::AddPassDependency(FRDGPassHandle ProducerHandle, FRDGPassHandle ConsumerHandle)
{
    FRDGPass* Consumer = Passes[ConsumerHandle];

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

// 초기화(Initialize)로 리소스 .
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;
    }
}

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

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

11.3.4 FRDGBuider::실행

앞서 언급한 Pass(AddPass) 수집 및 렌더링 컴파일 후 FRDGBuider::Execute에 의해 수행되는 렌더링이 실행될 수 있습니다.

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

    // ,의 생성(Create)epilogue pass.
    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)
    {
        // 실행(Execute), 11.3.3.
        Compile();

        {
            SCOPE_CYCLE_COUNTER(STAT_RDG_CollectResourcesTime);

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

            // 텍스처(Texture).
            for (const auto& Query : ExtractedTextures)
            {
                EndResourceRHI(EpiloguePassHandle, Query.Key, 1);
            }

            // 버퍼 .
            for (const auto& Query : ExtractedBuffers)
            {
                EndResourceRHI(EpiloguePassHandle, Query.Key, 1);
            }
        }

        // Pass의 배리어 .
        {
            SCOPE_CYCLE_COUNTER(STAT_RDG_CollectBarriersTime);

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

    // 순회(Traverse/Iterate)텍스처(Texture), 개 텍스처(Texture)추가(Add)변환(Transform/Convert).
    for (FRDGTextureHandle TextureHandle = Textures.Begin(); TextureHandle != Textures.End(); ++TextureHandle)
    {
        FRDGTextureRef Texture = Textures[TextureHandle];

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

    // 순회(Traverse/Iterate)버퍼 , 개 버퍼 추가(Add)변환(Transform/Convert).
    for (FRDGBufferHandle BufferHandle = Buffers.Begin(); BufferHandle != Buffers.End(); ++BufferHandle)
    {
        FRDGBufferRef Buffer = Buffers[BufferHandle];

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

    // 실행(Execute)Pass.
    if (!GRDGImmediateMode)
    {
        QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_Execute_Passes);

        for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
        {
            // 실행(Execute)클리핑/클램핑(Clipping)의 Pass.
            if (!PassesToCull[PassHandle])
            {
                ExecutePass(Passes[PassHandle]);
            }
        }
    }
    else
    {
        ExecutePass(EpiloguePass);
    }

    RHICmdList.SetGlobalUniformBuffers({});

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

    // 실행(Execute)텍스처(Texture).
    for (const auto& Query : ExtractedTextures)
    {
        *Query.Value = Query.Key->PooledRenderTarget;
    }

    // 실행(Execute)버퍼 .
    for (const auto& Query : ExtractedBuffers)
    {
        *Query.Value = Query.Key->PooledBuffer;
    }

    // .
    Clear();
}

실행 과정에서 Pass를 실행하기 위한 ExecutePass 인터페이스가 관련되며, 그 로직은 다음과 같습니다.

cpp
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

    // 실행(Execute)pass의 : 1.prologue -> 2.pass -> 3.epilogue.
    // 개 을(를) 활용하여 의 커맨드 리스트 실행(Execute).
    FRHIComputeCommandList& RHICmdListPass = (Pass->GetPipeline() == ERHIPipeline::AsyncCompute)
        ? static_cast<FRHIComputeCommandList&>(RHICmdListAsyncCompute)
        : RHICmdList;

    // 1.실행(Execute)prologue
    ExecutePassPrologue(RHICmdListPass, Pass);

    // 2.실행(Execute)pass
    Pass->Execute(RHICmdListPass);

    // 3.실행(Execute)epilogue
    ExecutePassEpilogue(RHICmdListPass, Pass);

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

    // 비동기 계산/산출(Calculate), 이면 즉, 디스패치 .
    if (Pass->bAsyncComputeEnd)
    {
        FRHIAsyncComputeCommandListImmediate::ImmediateDispatch(RHICmdListAsyncCompute);
    }

    // 만약 는 디버그 모드(Debug Mode)비동기 계산/산출(Calculate),이면 까지 GPU, GPU처리(Process).
    if (GRDGDebugFlushGPU && !GRDGAsyncCompute)
    {
        RHICmdList.SubmitCommandsAndFlushGPU();
        RHICmdList.BlockUntilGPUIdle();
    }
}

Pass를 실행하는 세 가지 주요 단계는 1. 프롤로그, 2. 본문 패스, 3. 에필로그입니다. 실행 논리는 다음과 같습니다.

cpp
// 1. prologue
void FRDGBuilder::ExecutePassPrologue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass)
{
    // 배리어 .
    if (Pass->PrologueBarriersToBegin)
    {
        Pass->PrologueBarriersToBegin->Submit(RHICmdListPass);
    }

    // 배리어 .
    if (Pass->PrologueBarriersToEnd)
    {
        Pass->PrologueBarriersToEnd->Submit(RHICmdListPass);
    }

    // 에 의해 RDG리소스 호출(Call)GetRHI,따라서 회 을(를) 활용하여 초기화(Initialize)버퍼 .
    Pass->GetParameters().EnumerateUniformBuffers([&](FRDGUniformBufferRef UniformBuffer)
    {
        BeginResourceRHI(UniformBuffer);
    });

    // 설정(Set)비동기 계산/산출(Calculate)(Budget).
    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())
        {
            // 호출(Call)커맨드 큐 의 BeginRenderPass인터페이스 .
            static_cast<FRHICommandList&>(RHICmdListPass).BeginRenderPass(Pass->GetParameters().GetRenderPassInfo(), Pass->GetName());
        }
    }
}

// 2. pass
void FRDGPass::Execute(FRHIComputeCommandList& RHICmdList)
{
    QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGPass_Execute);
    // 설정(Set)버퍼 .
    RHICmdList.SetGlobalUniformBuffers(ParameterStruct.GetGlobalUniformBuffers());
    // 실행(Execute)Pass의 구현 .
    ExecuteImpl(RHICmdList);
}

void TRDGLambdaPass::ExecuteImpl(FRHIComputeCommandList& RHICmdList) override
{
    // 실행(Execute)Lambda.
    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();

    // 호출(Call)커맨드 큐 의 EndRenderPass.
    if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster) && !EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassEnd())
    {
        static_cast<FRHICommandList&>(RHICmdListPass).EndRenderPass();
    }

    // 리소스 변환(Transform/Convert).
    for (FRHITexture* Texture : Pass->TexturesToDiscard)
    {
        RHIDiscardTransientResource(Texture);
    }

    // 페치/가져오기(Fetch)(Acquire)변환(Transform/Convert)리소스 .
    for (FRHITexture* Texture : Pass->TexturesToAcquire)
    {
        RHIAcquireTransientResource(Texture);
    }

    const FRDGParameterStruct PassParameters = Pass->GetParameters();

    // 을(를) 활용하여 파이프라인 의 배리어 .
    if (Pass->EpilogueBarriersToBeginForGraphics)
    {
        Pass->EpilogueBarriersToBeginForGraphics->Submit(RHICmdListPass);
    }

    // 을(를) 활용하여 비동기 계산/산출(Calculate)의 배리어 .
    if (Pass->EpilogueBarriersToBeginForAsyncCompute)
    {
        Pass->EpilogueBarriersToBeginForAsyncCompute->Submit(RHICmdListPass);
    }
}

위에서 볼 수 있듯이 실행 중에 모든 패스가 먼저 컴파일된 다음 패스의 프리앰블, 본문 및 후속 작업이 순서대로 실행됩니다. 이는 명령 대기열의 BeginRenderPass를 확산하고 렌더링 코드를 실행하며 EndRenderPass를 실행하는 것과 같습니다. Pass 실행 본문은 실제로 매우 간단합니다. 즉, Pass의 Lambda 인스턴스를 호출하고 사용된 명령 대기열 인스턴스를 전달합니다.

실행의 마지막 단계는 정리입니다. 아래 분석을 참조하세요.

cpp
void FRDGBuilder::Clear()
{
    // 리소스 .
    ExternalTextures.Empty();
    ExternalBuffers.Empty();
    // 리소스 .
    ExtractedTextures.Empty();
    ExtractedBuffers.Empty();
    // 데이터 .
    Passes.Clear();
    Views.Clear();
    Textures.Clear();
    Buffers.Clear();
    // 버퍼 및 할당(Allocate).
    UniformBuffers.Clear();
    Allocator.ReleaseAll();
}

11.3.5 RDG 메커니즘 요약

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

cpp
FRDGBuilder에는 전역적으로 고유한 인스턴스가 없습니다. 일반적으로 로컬 변수로 선언되며 Pass 수집, 컴파일, 실행의 전 과정이 특정 Life Cycle 내에 완료됩니다. FRDGBuider 인스턴스를 선언하는 모듈은 거리 필드, 렌더링 텍스처, 장면 렌더러, 장면 캡처기, 광선 추적, 후처리, 머리카락, 가상 텍스처 등입니다.

FRDGBuilder의 실행 주기는 Pass 수집, Pass 컴파일, Pass 실행 및 정리의 4단계로 나눌 수 있습니다.

Pass 수집 단계에서는 주로 RHI 렌더링 명령을 생성할 수 있는 렌더링 모듈의 모든 Pass(Lambda)를 수집합니다. 수집 후 즉시 실행되지 않고 지연됩니다. AddPass의 단계는 먼저 FRDGPass의 인스턴스를 생성하고 이를 Pass 목록에 추가한 다음 SetupPass를 실행하는 것입니다. SetupPass의 프로세스는 주로 텍스처와 버퍼의 상태, 참조, 종속성 및 태그를 다룹니다.

컴파일 패스 단계는 더 복잡하고 여러 단계로 구성됩니다. 여기에는 주로 생산자와 소비자 간의 종속 관계 구축, 패스 클리핑과 같은 다양한 태그 결정, 리소스 수명 주기 조정, 패스 클리핑, 패스 리소스 변환 및 장벽 처리, 패스 비동기 계산의 종속성 및 참조 관계 처리, 분기 및 병합된 패스 노드 찾기 및 설정, 동일한 특정 렌더링 대상으로 래스터라이제이션된 모든 패스 병합 등이 포함됩니다.

패스 실행 단계에서는 먼저 컴파일이 수행된 다음 컴파일 결과에 따라 적격한 모든 패스가 실행됩니다. 단일 패스를 실행하면 프리앰블, 본문, 후속 작업이 순서대로 실행됩니다. 이는 명령 대기열의 BeginRenderPass 실행, 패스 본문(Lambda) 렌더링 코드 실행, EndRenderPass 실행과 동일합니다. 패스 본문을 실행하는 프로세스는 간단합니다. 즉, 패스의 Lambda 인스턴스를 호출하는 것입니다.

마지막 단계는 FRDGBuider 인스턴스의 모든 데이터와 메모리를 정리하거나 재설정하는 정리 단계입니다.

cpp
FRDGBuilder의 전체 실행 과정에서 RHICommandList를 직접 사용하는 것과 비교했을 때 FRDGBuilder의 기능과 최적화 방법은 다음과 같습니다.

-

RDG Pass에서 참조하는 리소스는 RDG에서 할당하거나 관리해야 합니다. 외부에 등록된 리소스라도 RDG 기간 동안 수명 주기가 보장되어야 합니다. RDG는 리소스의 수명 주기를 자동으로 관리하고, 교차 및 병합 패스 중에 수명 주기를 지연하며, 리소스가 모두 사용되어 참조가 없으면 해제 및 재사용합니다.

-

자원은 즉시 응답하여 할당되는 것이 아니라 처음 사용할 때 할당되거나 생성됩니다.

  • 하위 리소스 개념을 사용하면 합리적인 레이아웃을 통해 이를 대규모 리소스 블록으로 통합할 수 있습니다. 하나의 하위 리소스를 다른 하위 리소스로 전달할 수 있습니다. 또한 하위 리소스의 뷰와 별칭을 자동으로 생성하고 향후 렌더링 단계에서 생성된 리소스 별칭을 생성할 수도 있습니다. 그런 다음 리소스 할당, 해제 및 재사용을 효과적으로 관리하고, 전체 메모리 사용량과 메모리 조각화를 줄이고, CPU 및 GPU IO를 줄이고, 메모리 사용 효율성을 향상시킵니다.

cpp
FRDGBuilder 인스턴스 단위로 RDG 패스를 관리하고, 패스를 자동으로 정렬, 참조, 분기 및 병합하고, 패스 리소스 참조 및 종속성을 처리하고, 쓸모 없는 패스와 리소스를 정리합니다. RDG는 또한 그래픽 패스와 비동기 컴퓨팅 패스 간의 종속성과 참조를 올바르게 처리하고, DAG 그래프에 따라 순서대로 연결하고, 리소스 교차 사용 및 상태 전환을 올바르게 처리할 수 있습니다.

-

RDG 패스가 동일한 렌더링 텍스처를 사용하는 경우 RDG는 RDG 패스의 렌더링을 병합할 수 있습니다. 이를 통해 RHI 레이어의 Begin/EndRenderPass에 대한 호출을 줄이고 RHI 렌더링 명령 및 리소스 상태의 변환을 줄일 수 있습니다.

-

RDG는 패스 간의 리소스 종속성, 장벽 및 상태 전환을 자동으로 처리하고, 유효하지 않은 상태 전환(예: 읽기에서 읽기, 쓰기에서 쓰기)을 포기하고 리소스 상태를 병합 및 일괄 변환하여 렌더링 지침 수를 더욱 줄일 수 있습니다.

-

RDG 패스 실행은 렌더링 스레드에서 발생하며 TaskGraph를 사용하여 병렬로 실행하지 않고 직렬로 실행됩니다.

이론적으로는 병렬로 실행될 수 있지만 이는 단지 추측일 뿐입니다. 실제로 가능한지는 아직 검증이 필요합니다.

  • RDG는 풍부한 디버깅 모드와 정보를 갖추고 있으며 실시간 실행 모드를 지원하고 개발자가 문제를 신속하게 찾을 수 있도록 지원하며 버그 검토에 소요되는 시간과 어려움을 줄여줍니다.

물론 FRDGBuilder에는 몇 가지 부작용도 있습니다.

  • 개념 및 캡슐화 계층이 렌더링 시스템에 추가되어 렌더링 계층의 복잡성이 증가하고 학습 비용이 증가합니다.

  • 개발 복잡성이 증가합니다. 실행 지연으로 인해 일부 버그는 즉각적인 피드백을 받을 수 없습니다.

  • 일부 패스 또는 리소스의 수명 주기가 추가로 연장될 수 있습니다.

Frostbite 비동기 계산 다이어그램에서 발췌. SSAO 및 SSAO 필터의 패스는 비동기 대기열에 배치되며 Raw AO의 텍스처를 쓰고 읽습니다. 동기화 지점 이전에 종료되더라도 Raw AO의 수명 주기는 동기화 지점까지 연장됩니다.

11.4 RDG 개발

본 장에서는 주로 UE의 RDG 시스템 사용법을 설명한다.

11.4.1 RDG 리소스 생성

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

cpp
// ---- 생성(Create)RDG텍스처(Texture) ----
// 생성(Create)RDG텍스처(Texture)
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;
// 생성(Create)RDG텍스처(Texture).
FRDGTextureRef MyRDGTexture = GraphBuilder.CreateTexture(TextureDesc, TEXT("MyRDGTexture"));

// ---- 생성(Create)RDG텍스처(Texture)UAV ----
FRDGTextureUAVRef MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);

// ---- 생성(Create)RDG텍스처(Texture)SRV ----
FRDGTextureSRVRef MyRDGTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateWithPixelFormat(MyRDGTexture, PF_FloatRGBA));

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

위 코드는 텍스처 관련 리소스 생성을 예로 들어 설명합니다. 버퍼 생성도 유사하므로 예제는 제공하지 않습니다.

11.4.2 외부 리소스 등록

앞 절의 리소스는 RDG가 생성하고 관리하며, 리소스의 생명주기도 RDG를 담당한다. RDG에서 생성되지 않은 리소스가 이미 있는 경우 RDG에서 사용할 수 있나요? 대답은 '예'입니다. FRDGBuider::RegisterExternalXXX 인터페이스를 통해 외부 리소스를 RDG 시스템에 등록할 수 있습니다. 다음은 텍스처를 등록하는 예입니다.

hlsl
// RDG생성(Create)RHI리소스 .
FRHIResourceCreateInfo CreateInfo;
FTexture2DRHIRef MyRHITexture = RHICreateTexture2D(1024, 768, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);

// 생성(Create)의 RHI리소스 RDG리소스 .
FRDGTextureRef MyExternalRDGTexture = GraphBuilder.RegisterExternalTexture(MyRHITexture);

RDG는 외부에 등록된 리소스의 수명 주기를 제어하고 관리할 수 없다는 점에 유의해야 합니다. RDG를 사용하는 동안 외부 리소스의 라이프사이클이 정상적인 상태인지 확인해야 합니다. 그렇지 않으면 예외가 발생하거나 프로그램이 충돌할 수도 있습니다.

RDG 리소스에서 RHI 리소스의 인스턴스를 얻으려면 다음 코드를 사용할 수 있습니다.

cpp
FRHITexture* MyRHITexture = MyRDGTexture.GetRHI();

그림을 사용하여 RHI 리소스와 RDG 리소스 간의 변환 관계를 보여줍니다.

그래프 LR A(FRHIResource) -->|FRDGBuilder::RegisterExternalXXX| B(FRDG리소스) B -->|FRDGResource::GetRHI| 에이

위 코드는 텍스처 관련 리소스 등록을 예로 들었고, 버퍼 등록도 비슷합니다.

11.4.3 리소스 추출

이전 장의 RDG 메커니즘에서 언급했듯이 RDG는 Pass를 수집한 후 즉시 실행되지 않고 지연된 방식(지연된 리소스 생성 또는 할당 포함)으로 수행됩니다. 이로 인해 문제가 발생합니다. 렌더링된 리소스를 변수에 할당하려는 경우 즉시 모드를 사용할 수 없으며 지연 실행 모드에 적응해야 합니다. 적응형 지연 실행의 리소스 추출은 다음 인터페이스를 통해 구현됩니다.

-FRDGBuilder::QueueTextureExtraction

-FRDGBuilder::QueueBufferExtraction

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

cpp
// 생성(Create)RDG텍스처(Texture).
FRDGTextureRef MyRDGTexture;

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

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

// 생성(Create)UAV로써 Pass의 shader파라미터 .
(......)
PassParameters->MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);
(......)

// 추가(Add)Pass, 로써 렌더링(Render)까지 MyRDGTextureUAV.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("MyCustomPass", ...), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(8, 8));

// 리소스 .
TRefCountPtr<IPooledRenderTarget>* OutputRT;
GraphBuilder.QueueTextureExtraction(MyRDGTexture, &OutputRT);

// 의 OutputRT.
(......)

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

빠른 생각: 이 프레임에서 추출된 리소스를 사용하려는 경우 추출된 리소스에 대해 작동하기 위해 특별한 매개변수 없는 패스를 추가하는 것이 가능합니까? 왜?

11.4.4 패스 추가

전체 RDG 시스템의 실행 단위는 RDG Pass이며, 해당 종속성, 참조, 입력 및 출력은 모두 FRDGBuider::AddPass를 통해 완료됩니다. 다음은 예입니다.

cpp
// 생성(Create)Pass의 shader파라미터 .
FMyPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FMyPS::FParameters>();
PassParameters->InputTexture = InputTexture;
PassParameters->RenderTargets = FRenderTargetBinding(InputTexture, InputTextureLoadAction);
PassParameters->InputSampler = BilinearSampler;

// 처리(Process)셰이딩 .
TShaderMapRef<FScreenPassVS> VertexShader(View.ShaderMap);
TShaderMapRef<FMyPS> PixelShader(View.ShaderMap);

const FScreenPassPipelineState PipelineState(VertexShader, PixelShader, AdditiveBlendState);

// 추가(Add)RDG Pass.
GraphBuilder.AddPass(
    RDG_EVENT_NAME("MyRDGPass"),
    PassParameters,
    ERDGPassFlags::Raster,
    // Pass의 Lambda
    [PixelShader, PassParameters, PipelineState] (FRHICommandListImmediate& RHICmdList)
    {
        // 설정(Set)뷰포트(Viewport).
        RHICmdList.SetViewport(0, 0, 0.0f, 1024, 768, 1.0f);

        // 설정(Set)PSO.
        SetScreenPassPipelineState(RHICmdList, PipelineState);

        // 설정(Set)셰이딩 파라미터 .
        SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);

        // 드로우/렌더(Draw)모멘트(Moment).
        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 FRDGBuider::Execute를 참조하세요.

11.4.5 FRDGBuider 생성

FRDGBuider를 생성하고 사용하는 코드는 매우 간단하며 다음과 같습니다.

cpp
void RenderMyStuff(FRHICommandListImmediate& RHICmdList)
{
    // ----생성(Create)FRDGBuilder의 로컬 객체/오브젝트 ----
    FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("GraphBuilder_RenderMyStuff"));
    
    (......)
    
    // ----추가(Add)Pass----
    
    GraphBuilder.AddPass(...);
    
    (......)
    
    GraphBuilder.AddPass(...);
    
    (......)
    
    // ----추가(Add)리소스 ----
    
    GraphBuilder.QueueTextureExtraction(...);
    
    (......)
    
    // ---- 실행(Execute)FRDGBuilder ----
    
    GraphBuilder.Execute();
}

특히 FRDGBuilder의 인스턴스는 일반적으로 로컬이라는 점을 지적해야 합니다. UE 시스템에는 FRDGBuilder의 여러 인스턴스가 있으며, 주로 장면 렌더러, 포스트 프로세스, 레이 트레이싱 및 기타 모듈과 같은 상대적으로 독립적인 모듈에 사용됩니다.

실제로 FRDGBuilder 실행에는 Pass 수집, Pass 컴파일, Pass 실행의 세 단계가 있습니다. 그러나 FRDGBuilder::Execute에는 이미 Pass 컴파일 및 실행이 포함되어 있으므로 더 이상 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.디버그 연결 및 실행 중에 발견된 비효율성에 대한 경고를 출력할 수 있습니다.

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 - 그래픽 및 비동기 계산의 중첩 표시.

r.RDG.ExtendResourceLifetimes RDG는 리소스 수명을 그래프의 전체 길이로 연장합니다. 메모리 사용량이 늘어납니다.

r.RDG.ImmediateMode Pass 생성 시 Pass를 실행합니다. 패스의 Lambda에서 충돌이 발생할 때 코드의 호출 스택을 연결하는 것은 매우 유용합니다.

r.RDG.MergeRenderPasses 그래픽은 동일한 연속 렌더 패스를 단일 렌더 패스로 병합합니다. 0-비활성화, 1-활성화(기본값).

r.RDG.OverlapUAV RDG는 필요할 때 UAV의 작업을 겹칩니다. 비활성화하면 UAV 장벽이 항상 삽입됩니다.

r.RDG.전환로그 출력 리소스를 콘솔로 변환합니다.

r.RDG.VerboseCSVStats RDG에 대한 CSV 분석 통계의 세부 수준을 제어합니다. 0 - 그래픽 실행을 위한 CSV 구성 파일을 생성합니다. 1 - 그래픽 실행의 각 단계에 대한 CSV 파일을 생성합니다.

위에 나열된 RDG 콘솔 외에도 RDG 시스템이 실행되는 동안 유용한 정보를 표시할 수 있는 몇 가지 명령도 있습니다.

vis는 유효한 텍스처를 모두 나열합니다. 입력 후 다음 정보가 표시될 수 있습니다.

cpp
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: A Crash Cours를 읽어보세요.

11.5.1 이 기사에 대한 생각

평소와 마찬가지로 이 기사에서는 RDG에 대한 숙달과 이해를 이해하고 심화하는 데 도움이 되는 몇 가지 작은 생각도 정리합니다.

  • RDG의 단계는 어떻게 되나요? 각 단계는 무엇을 합니까? 각각의 특징은 무엇입니까?

  • RDG 리소스와 RHI 리소스의 차이점과 연관성은 무엇인가요? 서로 변환하는 방법은 무엇입니까?

  • RDG를 사용하여 맞춤형 CS, PS 도면 코드를 구현합니다.

특별 지침

  • 모든 참고문헌의 저자에게 감사드립니다. 일부 사진은 참고 자료와 인터넷에서 가져온 것이므로 삭제되었습니다.

  • 이 시리즈의 기사는 저자가 직접 작성한 것이며 블로그에만 게시됩니다. 이 글의 링크를 공유하셔도 좋지만 무단 전재는 허용되지 않습니다!

  • 계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.

  • 계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.

  • 계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.








📚 참고 자료 및 공식 링크 (References)


🔗 연관 지식 베이스 (Wiki & Tools)

Based on the legendary technical series by Timlly (0向往0 / 毛星云)