DEV Community

Seongcheol Jeon
Seongcheol Jeon

Posted on

Render Dependency Graph (RDG)

RDG를 더욱 이해할 수 있도록 UnrealEngine 문서를 Clone 및 정리하여 작성하는 글이다.

렌더 종속성 그래프 (Render Denpendency Graph, RDG)

RDG란, 렌더 명령을 컴파일하고 실행할 수 있도록 그래프 데이터 구조체로 기록하는 즉시 모드 API.

렌더 그래프 또는 RDG라고도 하는 렌더 종속성 그래프(Render Dependency Graph)는 렌더 명령을 컴파일하고 실행할 수 있도록 그래프 데이터 구조체로 기록하는 즉시 모드 어플리케이션 프로그래밍 인터페이스(API)이다.
RDG는 오류에 취약한 연산을 자동화하여 고수준 렌더링 코드를 단순화하며, 그래프를 탐색해 메모리 사용을 최적화하고 CPUGPU의 렌더 패스를 *병렬화한다.

RDG의 대표적인 기능은 아래와 같다.

  • 비동기 컴퓨트 펜스 예약
  • 최적의 수명과 메모리 Aliasing을 통한 일시적 리소스 할당
  • 분할 베리어를 사용한 서브리소스 전환으로 레이턴시를 숨기고 GPU 오버랩 향상
  • 병렬 명령 목록 기록
  • 그래프의 미사용 리소스 및 패스 컬링
  • API 사용 및 리소스 의존성 유효성 검사
  • RDG 인사이트에서 그래프 구조체 및 메모리 수명 시각화

렌더 그래프 API는 Deffered 렌더러와 모바일 렌더러 및 관련 플러그인에 맞춰 전화되었다. 특히 위에서 설명한 고급 기능이 필요한 경우, 모든 고수준 렌더링 코드는 RDG로 작성해야 한다.

셰이더 파라미터 구조체

RDG는 셰이더 파라미터 구조체 시스템을 향한 익스텐션을 통해 그래프 의존성을 표현한다.

HLSL 소스 파일의 셰이더 입력:

float2 ViewportSize;
float4 Hello;
float World;
float3 FooBarArray[16];

Texture2D BlueNoiseTexture;
SamplerState BlueNoiseSampler;

Texture2D SceneColorTexture;
SamplerState SceneColorSampler;

RWTexture2D<float4> SceneColorOutput;
Enter fullscreen mode Exit fullscreen mode

이 셰이더 파라미터는 플랫 C++ 데이터 구조체로도 표현할 수 있다.

이상적인 C++ 버전:

struct FMyShaderParameters
{
    FVector2D ViewportSize;
    FVector4 Hello;
    float World;
    FVector FooBarArray[16];

    FRHITexture* BlueNoiseTexture = nullptr;
    FRHISamplerState* BlueNoiseSampler = nullptr;

    FRHITexture* SceneColorTexture = nullptr;
    FRFISamplerState* SceneColorSampler = nullptr;

    FRHIUnorderedAccessView* SceneColorOutput = nullptr;
};
Enter fullscreen mode Exit fullscreen mode

셰이더 파라미터 구조체는 이를 위해 일련의 선언 매크로를 사용한다.

셰이더 파라미터 구조체:

BEGIN_SHADER_PARAMETER_STRUCT(FMyShaderParameters, /* MODULE_API_TAG */)
    SHADER_PARAMETER(FVector2D, ViewportSize)
    SHADER_PARAMETER(FVector4, Hello)
    SHADER_PARAMETER(float, World)
    SHADER_PARAMETER_ARRAY(FVector, FooBarArray, [16])

    SHADER_PARAMETER_TEXTURE(Texture2D, BlueNoiseTexture)
    SHADER_PARAMETER_SAMPLER(SamplerState, BlueNoiseSampler)

    SHADER_PARAMETER_TEXTURE(Texture2D, SceneColorTexture)
    SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorSampler)

    SHADER_PARAMETER_TEXTURE(RWTexture2D, SceneColorOutput)
END_SHADER_PARAMETER_STRUCT()
Enter fullscreen mode Exit fullscreen mode

이러한 매크로는 동일한 플랫 C++ 데이터 구조체와 컴파일 시간 반영 메타데이터를 생성한다. 이 데이터는 구조체의 스태틱 멤버로 액세스 가능하다.

컴파일 시간 반영 데타데이터:

const FShaderParametersMetadata* ParameterMetadata = FMyShaderParameters::FTypeInfo::GetStructMetadata();
Enter fullscreen mode Exit fullscreen mode

이 메타데이터는 파라미터를 RHI에 다이내믹하게 바인딩하는 데 필요한 구조체의 런타임 트래버셜을 활성화한다. 각 멤버에 대해 확인할 수 있는 정보에는 Name, C++ type, HLSL type, Byte Offset 등이 있다.
RDG는 이 메타데이터를 사용해 패스 파라미터를 탐색한다.

Shader Binding

각 셰이더 파라미터 구조체는 FShader와 짝을 이루어 RHI 명령 목록을 제출하는데 필요한 바인딩을 생성한다.

바인딩을 생성하려면 FShader 파생 클래스에 FParameters 유형으로 파라미터 구조체를 선언한다.

이 작업은 inline으로 정의하거나 typedef 디렉티브를 사용하여 수행할 수 있다. 그 다음, SHADER_USE_PARAMETER_STRUCT 매크로를 사용해 바인딩을 등록할 클래스의 생성자를 만든다.

First Shader Class

class FMyShaderCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FMyShaderCS);

    // 이 FShader 인스턴스와 함께 FParameter 바인딩을 등록할 생성자를 만든다.
    SHADER_USE_PARAMETER_STRUCT(FMyShaderCS, FGlobalShader);

    // 인라인 정의나 디렉티브로 셰이더에 FParameters 유형을 할당한다.
    using FParameters = FMyShaderParameters;
};
Enter fullscreen mode Exit fullscreen mode

RHI 명령 목록에 셰이더 파라미터를 바인딩하려면 구조체를 인스턴스화하고 데이터를 채운 다음 SetShaderParameters 유틸리티 함수를 호출한다.

파라미터 할당하기:

TShaderMapRef<FMyShaderCS> ComputeShader(View.ShaderMap);
RHICmdList.SetComputeShader(ComputeShader.GetComputeShader());

FMyShaderCS::FParameters ShaderParameters;

// 파라미터 할당
ShaderParameters.ViewportSize = View.ViewRect.Size();
ShaderParameters.World = 1.0f;
ShaderParameters.FooBarArray[4] = FVector(1.0f, 0.5f, 0.5f);

// 파라미터 제출
SetShaderParameters(RHICmdList, ComputeShader, ComputeShader.GetComputeShader(), ShaderParameters);

RHICmdList.DispatchComputeShader(GroupCount.X, GroupCount.Y, GroupCount.Z);

Enter fullscreen mode Exit fullscreen mode

Uniform Buffers

유니폼 버퍼(Uniform Buffers)는 파라미터를 RHI 리소스로 그룹화한다. 이 리소스는 그 자체로 셰이더 파라미터로서 바인딩된다. 각 유니폼 버퍼는 HLSL에서 글로벌 네임스페이스를 정의한다.
유니폼 버퍼는 BEGIN_UNIFORM_BUFFER_STRUCTEND_UNIFORM_BUFFER_STRUCT 매크로를 사용해 선언한다.

유니폼 버퍼 정의하기:

BEGIN_UNIFORM_BUFFER_STRUCT(FSceneTextureUniformParameters, RENDERER_API)
    SHADER_PARAMETER_TEXTURE(Texture2D, SceneColorTexture)
    SHADER_PARAMETER_TEXTURE(SamplerState, SceneColorTextureSampler)
    SHADER_PARAMETER_TEXTURE(Texture2D, SceneDepthTexture)
    SHADER_PARAMETER_TEXTURE(SamplerState, SceneDepthTextureSampler)

    // ...
END_UNIFORM_BUFFER_STRUCT()
Enter fullscreen mode Exit fullscreen mode

C++ 소스 파일에서 IMPLEMENT_UNIFORM_BUFFER_STRUCT를 사용해 셰이더 시스템으로 유니폼 버퍼 정의를 등록하고 HLSL 정의를 생성한다.

유니폼 버퍼 구현하기:

IMPLEMENT_UNIFORM_BUFFER_STRUCT(FSceneTextureUniformParameters, "SceneTextureStruct")
Enter fullscreen mode Exit fullscreen mode

유니폼 버퍼 파라미터는 컴파일된 셰이더에 의해 자동으로 생성되며, UniformBuffer.Member 구문으로 액세스할 수 있다.

HLSL의 유니폼 버퍼:

// 유니폼 버퍼 선언을 포함한 파일 생성. Common.ush에 의해 자동으로 포함됨.
#include "/Engine/Generated/GeneratedUniformBuffers.ush"

// 유니폼 버퍼 멤버를 구조체처럼 참조한다.
Texture2DSample(SceneTexturesStruct.SceneColorTexture, SceneTexturesStruct.SceneColorTextureSampler);
Enter fullscreen mode Exit fullscreen mode

이제 SHADER_PARAMETER_STRUCT_REF 매크로를 사용해 부모 셰이더 파라미터 구조체에서 유니폼 버퍼를 파라미터로 포함시킬 수 있다.

SHADER_PARAMETER_STRUCT_REF

BEGIN_SHADER_PARAMETER_STRUCT(FParameters,)
    // ...

    // 레퍼런스 카운트된 TUniformBufferRef<FSceneTexturesUniformParameters> 인스턴스를 정의한다.
    SHADER_PARAMETER_STRUCT_REF(FSceneTextureUniformParameters, SceneTextures)
END_SHADER_PARAMETER_STRUCT()
Enter fullscreen mode Exit fullscreen mode

Static Bindings

셰이더 파라미터는 각 셰이더에 고유하게 바인딩된다. vertex, pixel 등의 각 셰이더 단계에는 별도의 셰이더가 필요하다. 셰이더는 RHI 명령 목록에서 Set{Graphics, Compute}PipelineState를 사용해 Pipeline State Object (PSO)로 함께 바인딩된다.

명령 목록에서 파이프라인 스테이트를 바인딩하면, 모든 셰이더 바인딩이 무효화된다.

PSO를 설정한 다음에 모든 셰이더 파라미터를 바인딩해야 한다. 예를 들어 PSO를 공유하는 일반적인 DrawCall의 명령 흐름을 살펴보자.

  • PSO A 설정
  • 각 DrawCall에 대해 아래 작업 수행
    • vertex shader parameter 설정
    • pixel shader parameter 설정
    • draw
  • PSO B 설정
  • 각 DrawCall에 대해 아래 작업 수행
    • vertex shader parameter 설정
    • pixel shader parameter 설정
    • draw

이 접근법의 문제는 렌더러의 mesh draw 명령이 여러 pass와 view 간에 cache가 공유된다는 점이다.
frame마다 각 pass/view 조합별로 각기 다른 draw 명령 세트를 생성해야 한다면, 매우 비효율적일 것이다. 하지만 mesh draw 명령은 pass/view uniform buffer 리소스를 알아야 올바르게 binding을 수행할 수 있다. 이 문제를 해결하기 위해 uniform buffer에는 static binding model이 사용된다.

static binding으로 선언한 uniform buffer는 개별 셰이더의 고유 슬롯이 아니라 RHI 명령 목록과 직접 연결된 static slot에 바인딩된다. 셰이더가 uniform buffer를 요청하면 명령 목록은 static slot에서 직접 바인딩을 가져온다. 이렇게 하면 바인딩이 PSO 빈도가 아닌 pass 빈도로 이루어진다.

아래는 위의 예시와 동일하지만 셰이더 입력을 static uniform buffer에서 가져온다는 차이가 있다.

  • static uniform buffer 설정
  • PSO A 설정
  • 각 draw call에 대해 아래 작업 수행
    • draw
  • PSO B 설정
  • 각 draw call에 대해 아래 작업 수행
    • draw

이 모델을 사용하면 각 draw call이 명령 목록에서 셰이더 바인딩을 상속할 수 있다.

Static Uniform Buffers 정의하기

static binding으로 uniform buffer를 정의하려면,
IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT 매크로를 사용한다. 추가 슬롯을 선언해야 한다. 이는 IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT 매크로에 지정된다.

여러 static uniform buffer 정의가 동일한 static slot을 참조할 수 있지만, binding할 수 있는 정의는 한 번에 하나뿐이다. 가급적 엔진 내 총 슬롯 수를 줄일 수 있도록 슬롯을 재사용하는 것이 좋다.

Static Uniform Buffers:

// 이름으로 unique static slot을 정의한다.
IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(SceneTextures);

// SceneTextures slot에 static 바인딩하여 SceneTexturesStruct uniform buffer를 정의한다.
IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FSceneTextureUniformParameters, "SceneTexturesStruct", SceneTextures);

// 동일한 static slot으로 MobileSceneTextures uniform buffer를 정의한다.
IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FMobileSceneTextureUniformParameters, "MobileSceneTexturesStruct", SceneTextures);
Enter fullscreen mode Exit fullscreen mode

RHICmdList.SetStaticUniformBuffers 메서드를 사용해 static uniform buffer를 binding한다. RDG는 각 pass를 실행하기 전에 자동으로 static uniform buffer를 명령 목록에 binding한다. 모든 static uniform buffer는 pass parameter struct에 포함되어야 한다.

... continue...

Top comments (0)