Direct3D 12 렌더링 패스

렌더링 패스 기능은 Windows 10, 버전 1809(10.0; 빌드 17763) Direct3D 12 렌더링 패스의 개념을 소개합니다. 렌더링 패스는 명령 목록에 기록하는 명령의 하위 집합으로 구성됩니다.

각 렌더링 패스가 시작되고 끝나는 위치를 선언하려면 ID3D12GraphicsCommandList4::BeginRenderPassEndRenderPass에 대한 호출 내에서 전달하는 에 속하는 명령을 중첩합니다. 따라서 모든 명령 목록에는 0개, 1개 이상의 렌더링 패스가 포함됩니다.

시나리오

렌더링 패스는 다른 기술 중에서 Tile-Based TBDR(지연 렌더링)을 기반으로 하는 경우 렌더러의 성능을 향상시킬 수 있습니다. 보다 구체적으로, 이 기술은 애플리케이션이 리소스 렌더링 순서 요구 사항 및 데이터 종속성을 더 잘 식별할 수 있도록 하여 오프칩 메모리 간 메모리 트래픽을 줄임으로써 렌더러가 GPU 효율성을 개선하는 데 도움이 됩니다.

렌더링 패스 기능을 활용하기 위해 명시적으로 작성된 디스플레이 드라이버가 최상의 결과를 제공합니다. 그러나 렌더링 패스 API는 기존 드라이버에서도 실행할 수 있습니다(성능 향상이 반드시 있는 것은 아님).

렌더링 패스가 값을 제공하도록 설계된 시나리오입니다.

애플리케이션이 Tile-Based TBDR(지연 렌더링) 아키텍처에서 기본 메모리의 불필요한 로드/저장소를 방지하도록 허용

렌더링 패스의 가치 제안 중 하나는 렌더링 작업 집합에 대한 애플리케이션의 데이터 종속성을 나타내는 중앙 위치를 제공한다는 것입니다. 이러한 데이터 종속성을 통해 디스플레이 드라이버는 바인딩/장벽 시간에 이 데이터를 검사하고 메모리에서/기본 리소스 로드/저장을 최소화하는 지침을 실행할 수 있습니다.

TBDR 아키텍처가 렌더링 패스에서 온칩 캐시의 기회적으로 영구 리소스를 유지하도록 허용합니다(별도의 명령 목록에서도).

참고

특히 이 시나리오는 여러 명령 목록에서 동일한 렌더링 대상에 쓰는 경우로 제한됩니다.

일반적인 렌더링 패턴은 렌더링 명령이 병렬로 생성되더라도 애플리케이션이 여러 명령 목록에서 동일한 렌더링 대상에 직렬로 렌더링하는 것입니다. 이 시나리오에서 렌더링 패스를 사용하면 디스플레이 드라이버가 명령 목록 경계에서 기본 메모리에 대한 플러시를 방지할 수 있는 이러한 방식으로 이러한 패스를 결합할 수 있습니다(애플리케이션이 즉시 성공하는 명령 목록에서 렌더링을 다시 시작할 것임을 알고 있기 때문에).

애플리케이션의 책임

렌더링 패스 기능이 있더라도 Direct3D 12 런타임이나 디스플레이 드라이버는 로드 및 저장소의 순서를 변경/방지할 수 있는 기회를 추론할 책임이 없습니다. 렌더링 패스 기능을 올바르게 활용하려면 애플리케이션에 이러한 책임이 있습니다.

  • 작업에 대한 데이터/순서 지정 종속성을 올바르게 식별합니다.
  • 플러시를 최소화하는 방식으로 제출을 정렬합니다(따라서 _PRESERVE 플래그 사용을 최소화).
  • 리소스 장벽을 올바르게 사용하고 리소스 상태를 추적합니다.
  • 불필요한 복사본/지우기 방지 이러한 경고를 식별하기 위해 Windows의 PIX 도구에서 자동화된 성능 경고를 사용할 수 있습니다.

렌더링 패스 기능 사용

렌더링 패스란?

렌더링 패스는 이러한 요소에 의해 정의됩니다.

  • 렌더링 패스 기간 동안 고정된 출력 바인딩 집합입니다. 이러한 바인딩은 하나 이상의 RTV(렌더링 대상 뷰) 및/또는 DSV(깊이 스텐실 뷰)에 대한 것입니다.
  • 출력 바인딩 집합을 대상으로 하는 GPU 작업 목록입니다.
  • 렌더링 패스가 대상으로 하는 모든 출력 바인딩에 대한 로드/저장소 종속성을 설명하는 메타데이터입니다.

출력 바인딩 선언

렌더링 패스가 시작될 때 렌더링 대상 및/또는 깊이/스텐실 버퍼에 대한 바인딩을 선언합니다. 렌더링 대상에 바인딩하는 것은 선택 사항이며 깊이/스텐실 버퍼에 바인딩하는 것은 선택 사항입니다. 그러나 둘 중 하나 이상에 바인딩해야 하며 아래 코드 예제에서는 둘 다에 바인딩합니다.

ID3D12GraphicsCommandList4::BeginRenderPass 호출에서 이러한 바인딩을 선언합니다.

void render_passes(::ID3D12GraphicsCommandList4 * pIGCL4,
    D3D12_CPU_DESCRIPTOR_HANDLE const& rtvCPUDescriptorHandle,
    D3D12_CPU_DESCRIPTOR_HANDLE const& dsvCPUDescriptorHandle)
{
    const float clearColor4[]{ 0.f, 0.f, 0.f, 0.f };
    CD3DX12_CLEAR_VALUE clearValue{ DXGI_FORMAT_R32G32B32_FLOAT, clearColor4 };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessClear{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, { clearValue } };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessPreserve{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {} };
    D3D12_RENDER_PASS_RENDER_TARGET_DESC renderPassRenderTargetDesc{ rtvCPUDescriptorHandle, renderPassBeginningAccessClear, renderPassEndingAccessPreserve };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessNoAccess{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessNoAccess{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_DEPTH_STENCIL_DESC renderPassDepthStencilDesc{ dsvCPUDescriptorHandle, renderPassBeginningAccessNoAccess, renderPassBeginningAccessNoAccess, renderPassEndingAccessNoAccess, renderPassEndingAccessNoAccess };

    pIGCL4->BeginRenderPass(1, &renderPassRenderTargetDesc, &renderPassDepthStencilDesc, D3D12_RENDER_PASS_FLAG_NONE);
    // Record command list.
    pIGCL4->EndRenderPass();
    // Begin/End further render passes and then execute the command list(s).
}

D3D12_RENDER_PASS_RENDER_TARGET_DESC 구조체의 첫 번째 필드를 하나 이상의 RTV(렌더링 대상 뷰)에 해당하는 CPU 설명자 핸들로 설정합니다. 마찬가지로 D3D12_RENDER_PASS_DEPTH_STENCIL_DESC 깊이 스텐실 뷰(DSV)에 해당하는 CPU 설명자 핸들을 포함합니다. 이러한 CPU 설명자 핸들은 ID3D12GraphicsCommandList::OMSetRenderTargets에 전달하는 것과 동일합니다. 그리고 OMSetRenderTargets와 마찬가지로 CPU 설명자는 BeginRenderPass를 호출할 때 해당 (CPU 설명자) 힙에서 스냅됩니다.

RTV 및 DSV는 렌더링 패스에 상속되지 않습니다. 오히려 설정해야 합니다. BeginRenderPass에서 선언된 RTV 및 DSV도 명령 목록으로 전파되지 않습니다. 대신 렌더링 패스 다음에 정의되지 않은 상태입니다.

렌더링 패스 및 워크로드

렌더링 패스를 중첩할 수 없으며 둘 이상의 명령 목록에 걸쳐 있는 렌더링 패스가 있을 수 없습니다(단일 명령 목록에 기록하는 동안 시작 및 종료해야 합니다). 렌더링 패스의 효율적인 다중 스레드 생성을 사용하도록 설계된 최적화는 아래 의 렌더링 패스 플래그 섹션에서 설명합니다.

렌더링 패스 내에서 수행하는 쓰기는 후속 렌더링 단계까지 읽을 없습니다. 이는 렌더링 패스 내에서 일부 유형의 장벽을 배제합니다(예: 현재 바인딩된 렌더링 대상에서 RENDER_TARGETSHADER_RESOURCE 장벽). 자세한 내용은 아래의 렌더링 패스 및 리소스 장벽 섹션을 참조하세요.

방금 언급한 쓰기-읽기 제약 조건의 한 가지 예외에는 깊이 테스트 및 렌더링 대상 혼합의 일부로 발생하는 암시적 읽기가 포함됩니다. 따라서 이러한 API는 렌더링 패스 내에서 허용되지 않습니다(코어 런타임은 기록 중에 호출되는 경우 명령 목록을 제거함).

렌더링 패스 및 리소스 장벽

동일한 렌더링 패스 내에서 발생한 쓰기를 읽거나 사용할 수 없습니다. 특정 장벽은 이 제약 조건을 준수하지 않습니다(예: 현재 바인딩된 렌더링 대상의 D3D12_RESOURCE_STATE_RENDER_TARGET 에서 *_SHADER_RESOURCE ). 디버그 계층은 해당 효과에 오류가 발생합니다. 그러나 현재 렌더링 패스 외부에서 작성된 렌더링 대상에 대한 동일한 장벽은 현재 렌더링 패스 시작보다 먼저 쓰기가 완료되기 때문에 적합합니다. 이와 관련하여 디스플레이 드라이버가 수행할 수 있는 특정 최적화에 대해 아는 것이 도움이 될 수 있습니다. 규칙적인 워크로드가 제공되면 디스플레이 드라이버가 렌더링 패스에서 발생하는 모든 장벽을 렌더링 패스의 시작 부분으로 이동할 수 있습니다. 그곳에서 병합할 수 있으며 타일링/비닝 작업을 방해하지 않습니다. 현재 렌더링 단계가 시작되기 전에 모든 쓰기가 완료된 경우 유효한 최적화입니다.

다음은 미리 Direct3D 12 스타일 리소스 바인딩 디자인이 있는 렌더링 엔진이 있다고 가정하여 리소스가 바인딩되는 방식에 따라 수요에 대한 장벽을 수행하는 보다 완전한 드라이버 최적화 예제입니다. 프레임의 끝 부분에 UAV(순서가 지정되지 않은 액세스 뷰)에 쓸 때(다음 프레임에서 사용) 엔진은 프레임이 끝날 때 리소스를 D3D12_RESOURCE_STATE_UNORDERED_ACCESS 상태로 둘 수 있습니다. 다음 프레임에서 엔진이 리소스를 SRV(셰이더 리소스 뷰)로 바인딩할 때 리소스가 올바른 상태가 아니라는 것을 알게 되며 D3D12_RESOURCE_STATE_UNORDERED_ACCESS D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE 장벽이 발생합니다. 렌더링 패스 내에서 해당 장벽이 발생하는 경우 모든 쓰기가 이 현재 렌더링 패스 외부에서 이미 발생했다고 가정할 때 디스플레이 드라이버가 정당화되며, 따라서 디스플레이 드라이버가 렌더링 패스의 시작 부분까지 장벽을 이동할 수 있습니다(최적화가 제공되는 위치). 다시 말하지만, 코드가 이 섹션과 마지막 섹션에 설명된 쓰기-읽기 제약 조건을 준수하는 한 유효합니다.

다음은 준수 장벽의 예입니다.

  • D3D12_RESOURCE_STATE_INDIRECT_ARGUMENTD3D12_RESOURCE_STATE_UNORDERED_ACCESS.
  • *_SHADER_RESOURCED3D12_RESOURCE_STATE_COPY_DEST.

그리고 이들은 비준수 장벽의 예입니다.

  • 현재 바인딩된 RTV/DSV의 모든 읽기 상태로 D3D12_RESOURCE_STATE_RENDER_TARGET.
  • 현재 바인딩된 RTV/DSV의 모든 읽기 상태로 D3D12_RESOURCE_STATE_DEPTH_WRITE.
  • 모든 별칭 장벽.
  • UAV(순서가 지정되지 않은 액세스 뷰) 장벽입니다. 

리소스 액세스 선언

BeginRenderPass 시간에는 해당 패스 내에서 RTV 및/또는 DSV로 사용되는 모든 리소스를 선언할 뿐만 아니라 시작 및 종료 액세스 특성도 지정해야 합니다. 위의 출력 바인딩 선언 섹션의 코드 예제에서 볼 수 있듯이 D3D12_RENDER_PASS_RENDER_TARGET_DESCD3D12_RENDER_PASS_DEPTH_STENCIL_DESC 구조로 이 작업을 수행합니다.

자세한 내용은 D3D12_RENDER_PASS_BEGINNING_ACCESSD3D12_RENDER_PASS_ENDING_ACCESS 구조와 D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPED3D12_RENDER_PASS_ENDING_ACCESS_TYPE 열거형을 참조하세요.

렌더링 패스 플래그

BeginRenderPass에 전달된 마지막 매개 변수는 렌더링 패스 플래그(D3D12_RENDER_PASS_FLAGS 열거형의 값)입니다.

enum D3D12_RENDER_PASS_FLAGS
{
    D3D12_RENDER_PASS_FLAG_NONE = 0,
    D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES = 0x1,
    D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS = 0x2,
    D3D12_RENDER_PASS_FLAG_RESUMING_PASS = 0x4
};

렌더링 패스 내에서 UAV 쓰기

UAV(순서가 지정되지 않은 액세스 뷰) 쓰기는 렌더링 패스 내에서 허용되지만, 필요한 경우 디스플레이 드라이버가 타일링을 옵트아웃할 수 있도록 D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES 지정하여 렌더링 패스 내에서 UAV 쓰기를 실행한다는 것을 구체적으로 나타내야 합니다.

UAV 액세스는 위에서 설명한 쓰기-읽기 제약 조건을 따라야 합니다(렌더링 패스의 쓰기는 후속 렌더링 단계까지 읽을 수 없음). UAV 장벽은 렌더링 패스 내에서 허용되지 않습니다.

UAV 바인딩(루트 테이블 또는 루트 설명자를 통해)은 렌더링 패스로 상속되며 렌더링 패스에서 전파됩니다.

Suspending-passes 및 resuming-passs

전체 렌더링 패스를 suspending-pass 및/또는 resuming-pass로 나타낼 수 있습니다. suspending-followed-by-a-resuming 쌍은 패스 간에 동일한 보기/액세스 플래그를 가져야 하며 일시 중단 렌더링 패스와 다시 시작 렌더링 패스 간에 중간 GPU 작업(예: 그리기, 디스패치, 삭제, 지우기, 복사, 업데이트-타일 매핑, 쓰기 버퍼-직접 실행, 쿼리, 쿼리 확인)이 없을 수 있습니다.

의도된 사용 사례는 다중 스레드 렌더링으로, 네 개의 명령 목록(각각 자체 렌더링 패스가 있는)이 동일한 렌더링 대상을 대상으로 지정할 수 있습니다. 명령 목록에서 렌더링 패스가 일시 중단/다시 시작되면 ID3D12CommandQueue::ExecuteCommandLists에 대한 동일한 호출에서 명령 목록을 실행해야 합니다.

렌더링 패스는 다시 시작 및 일시 중단될 수 있습니다. 방금 제공된 다중 스레드 예제에서 명령 목록 2와 3은 각각 1과 2에서 다시 시작됩니다. 그리고 동시에 2와 3은 각각 3과 4로 일시 중단됩니다.

렌더링 패스 기능 지원에 대한 쿼리

ID3D12Device::CheckFeatureSupport를 호출하여 디바이스 드라이버 및/또는 하드웨어가 렌더링 패스를 효율적으로 지원하는 정도를 쿼리할 수 있습니다.

D3D12_RENDER_PASS_TIER get_render_passes_tier(::ID3D12Device * pIDevice)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupport{};
    winrt::check_hresult(
        pIDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupport, sizeof(featureSupport))
    );
    return featureSupport.RenderPassesTier;
}
...
    D3D12_RENDER_PASS_TIER renderPassesTier{ get_render_passes_tier(pIDevice) };

런타임의 매핑 논리로 인해 렌더링 패스는 항상 작동합니다. 그러나 기능 지원에 따라 항상 혜택을 제공하는 것은 아닙니다. 위의 코드 예제와 유사한 코드를 사용하여 렌더링 패스로 명령을 실행할 가치가 있는지 여부와 이점이 아닌 경우(즉, 런타임이 기존 API 화면에 매핑하는 경우)를 결정할 수 있습니다. D3D11On12)를 사용하는 경우 이 검사 수행하는 것이 특히 중요합니다.

세 가지 지원 계층에 대한 설명은 D3D12_RENDER_PASS_TIER 열거형을 참조하세요.