리소스 바인딩 개요
DirectX 12의 리소스 바인딩을 이해하는 키는 설명자, 설명자 테이블, 설명자 힙 및 루트 서명의 개념입니다.
리소스 및 그래픽 파이프라인
셰이더 리소스(예: 질감, 상수 테이블, 이미지, 버퍼 등)는 셰이더 파이프라인에 직접 바인딩되지 않고 설명자(descriptor)를 통해 참조됩니다. 설명자는 하나의 리소스에 대한 정보를 포함하는 작은 개체입니다.
설명자는 그룹화되어 설명자 테이블(descriptor table)을 구성합니다. 각 설명자 테이블은 리소스 유형 중 한 가지 범위에 대한 정보를 저장합니다. 리소스 유형은 다양합니다. 가장 일반적인 리소스는 다음과 같습니다.
- CBVs(상수 버퍼 보기)
- UAV(순서가 지정되지 않은 액세스 뷰)
- SRV(셰이더 리소스 뷰)
- 샘플러
SRV, UAV 및 CBV 설명자는 동일한 설명자 테이블로 결합될 수 있습니다.
그래픽 및 컴퓨팅 파이프라인은 인덱스로 설명자 테이블을 참조하여 리소스에 대한 액세스 권한을 얻습니다.
설명자 테이블은 설명자 힙(descriptor heap)에 저장됩니다. 설명자 힙은 렌더링될 하나 이상의 프레임에 대해 설명자 테이블의 모든 설명자를 포함합니다. 모든 리소스는 사용자 모드 힙에 저장됩니다.
또 다른 개념은 루트 서명(root signature) 개념입니다. 루트 서명은 셰이더가 액세스해야 하는 리소스를 찾는 데 사용되는 애플리케이션에서 정의된 바인딩 규칙입니다. 루트 서명은 다음을 저장할 수 있습니다.
- 설명자 테이블의 레이아웃이 미리 정의된 설명자 힙의 설명자 테이블에 대한 인덱스.
- 상수, 따라서 앱은 설명자와 설명자 테이블을 거치지 않고도 사용자 정의 상수(루트 상수(root constants)라고 함)를 셰이더에 직접 바인딩할 수 있습니다.
- 그리기마다 변경되는 CBV(상수 버퍼 보기)와 같은 루트 서명 내부에 직접 포함된 매우 적은 수의 설명자. 따라서 애플리케이션 해당 설명자를 설명자 힙에 넣을 필요가 없습니다.
다시 말해, 루트 서명은 그리기마다 변경되는 소량의 데이터에 적합한 성능 최적화를 제공합니다.
바인딩을 위한 Direct3D 12 디자인은 메모리 관리, 개체 수명 관리, 상태 추적 및 메모리 동기화와 같은 다른 작업과 구분됩니다(Direct3D 11 바인딩 모델과의 차이점 참조). Direct3D 12 바인딩은 낮은 오버 헤드로 설계되었으며 가장 자주 발생하는 API 호출에 최적화되어 있습니다. 최솟값에서 최댓값 하드웨어까지 확장이 가능하며, 그래픽 엔진 프로그래밍에 대한 오래된(보다 선형적인 Direct3D 11 파이프라인) 방식에서 새로운(보다 강력한 병렬식) 방식으로 확장이 가능합니다.
리소스 유형 및 뷰
리소스 유형은 Direct3D 11과 같습니다, 즉:
- Texture1D 및 Texture1DArray
- Texture2D 및 Texture2DArray, Texture2DMS, Texture2DMSArray
- Texture3D
- 버퍼(형식화, 구조화 및 원시)
리소스 뷰는 유사하지만 Direct3D 11과 약간 다르며, 꼭짓점 및 인덱스 버퍼 뷰가 추가되었습니다.
- CBV(상수 버퍼 보기)
- UAV(순서가 지정되지 않은 액세스 뷰)
- SRV(셰이더 리소스 뷰)
- 샘플러
- RTV(렌더링 대상 뷰)
- DSV(깊이 스텐실 뷰)
- IBV(인덱스 버퍼 뷰)
- VBV(꼭짓점 버퍼 뷰)
- SOV(스트림 출력 뷰)
이러한 뷰 중 처음 4개만 실제로 셰이더에 보입니다. 셰이더 표시 설명자 힙 및 셰이더 비표시 설명자 힙을 참조하세요.
리소스 바인딩 제어 흐름
루트 서명, 루트 설명자, 루트 상수, 설명자 테이블 및 설명자 힙에만 중점을 두면, 앱에 대한 렌더링 논리의 흐름은 다음과 유사해야 합니다.
- 하나 이상의 루트 서명 개체를 애플리케이션에 필요한 모든 바인딩 구성마다 하나씩 만듭니다.
- 함께 사용될 루트 서명 개체를 사용하여 셰이더 및 파이프라인 상태를 만듭니다.
- 렌더링의 각 프레임에 대한 모든 SRV, UAV 및 CBV 설명자를 포함할 하나의(또는 필요한 경우 더 많은) 설명자 힙을 만듭니다.
- 많은 설명자에서 재사용될 설명자 세트에 대해 가능한 경우 설명자로 설명자 힙을 초기화합니다.
- 렌더링할 프레임마다:
- 각 명령 목록에 대해:
- 사용할 현재 루트 서명을 설정합니다(렌더링 중에 필요할 경우 변경, 거의 필요하지 않음).
- 새 뷰(예: 월드/뷰 프로젝션)에 대한 루트 서명의 상수 및/또는 루트 서명 설명자를 업데이트합니다.
- 그릴 각 항목에 대해:
- 개체별 렌더링에 필요한대로 설명자 힙에 새로운 설명자를 정의합니다. 셰이더 표시 설명자 힙의 경우, 앱은 아직 진행 중일 수 있는 렌더링에 의해 참조되지 않는 설명자 힙 공간을 사용해야 합니다(예: 렌더링 중에 설명자 힙을 통해 선형으로 공간 할당).
- 설명자 힙의 필수 영역에 대한 포인터로 루스 서명을 업데이트합니다. 예를 들어 하나의 설명자 테이블은 이전에 초기화된 정적(변경되지 않음) 설명자를 가리킬 수 있고, 또 다른 설명자 테이블은 현재 렌더링을 위해 구성된 동적 설명자를 가리킬 수 있습니다.
- 항목별 렌더링을 위해 루트 서명의 상수 및/또는 루트 서명 설명자를 업데이트합니다.
- 그릴(변경이 필요한 경우에만) 항목의 파이프라인 상태를 현재 바인딩된 루트 서명과 호환되도록 설정합니다.
- 그리기
- 반복(다음 항목)
- 반복(다음 명령 목록)
- 엄밀히 말해, GPU가 더 이상 사용되지 않는 메모리로 끝나면 릴리즈될 수 있습니다. 설명자를 사용하는 추가 렌더링이 제출되지 않으면 해당 설명자의 참조를 삭제할 필요가 없습니다. 따라서, 후속 렌더링이 설명자 힙의 다른 영역을 가리키거나 유효한 설명자로 부실 설명자를 덮어써서 설명자 힙 공간을 다시 사용할 수 있습니다.
- 각 명령 목록에 대해:
- 반복(다음 프레임)
다른 설명자 형식, RTV(렌더링 대상 뷰), DSV(깊이 스텐실 뷰), IBV(인덱스 버퍼 뷰), VBV(꼭짓점 버퍼 뷰) 및 SOV(스트림 출력 뷰)는 다르게 관리됩니다. 명령 목록을 기록하는 동안 드라이버는 각 그리기에 바인딩된 설명자 세트의 버전 관리를 처리합니다(하드웨어/드라이버에 의해 루트 서명 바인딩이 버전 관리되는 방식과 유사). 이것은 애플리케이션이 힙을 통해 수동으로 할당해야 하는 셰이더 표시 설명자 힙의 콘텐츠와 다르며, 그리기 간에 다른 설명자를 참조하기 때문입니다. 셰이더 표시 힙 콘텐츠의 버전 관리는 애플리케이션에 맡깁니다. 이렇게 하면 변경되지 않은 설명자 재사용 또는 설명자의 큰 정적 세트 사용 및 섀이더 인덱싱(예: 재질 ID)을 사용하여 설명자 힙에서 사용할 설명자 선택 또는 서로 다른 설명자 세트에 맞는 기술 조합 사용과 같은 작업을 애플리케이션이 수행할 수 있기 때문입니다. 다른 설명자 유형(RTV, DSV, IBV, VBV, SOV)에 대해 이러한 유형의 유연성을 처리하도록 하드웨어가 장착되어 있지 않습니다.
하위 할당
Direct3D 12에서는 애플리케이션은 메모리 관리에 대해 낮은 수준의 제어 기능을 가지고 있습니다. Direct3D 11을 포함한 이전 버전의 Direct3D에서는 리소스당 하나의 할당이 있습니다. Direct3D 12에서는 앱이 API를 사용하여 다른 단일 개체에 필요한 것보다 큰 메모리 블록을 할당할 수 있습니다. 이것이 완료되면 앱은 해당하는 큰 메모리 블록의 섹션을 가리키는 설명자를 만들 수 있습니다. 이렇게 무엇을 어디에(큰 블록 내부의 더 작은 블록) 배치할지 결정하는 과정을 하위 할당이라고 합니다. 앱이 이 작업을 수행할 수 있도록 설정하면 컴퓨팅과 메모리를 효율적으로 사용하는 이점을 누릴 수 있습니다. 예를 들어, 리소스 이름 변경이 더 이상 사용되지 않습니다. 이 대신, 명령 목록에 특정 리소스를 사용해야 하는 명령 목록 실행을 펜싱하여 앱은 펜스를 사용하여 특정 리소스가 사용되는 시기와 사용되지 않는 시기를 결정할 수 있습니다.
리소스 해제
파이프라인에 바인딩된 메모리를 해제하기 전에 GPU가 끝나야 합니다.
프레임 렌더링을 기다리는 것은 아마도 GPU가 완료되었는지 확인하는 가장 열악한 방법입니다. 보다 미세한 단위에서, 펜스를 다시 사용할 수 있습니다. 명령이 완료를 추적할 명령 목록으로 기록되면 바로 뒤에 펜스를 삽입합니다. 그런 다음, 펜스로 다양한 동기화 작업을 수행할 수 있습니다. 지정된 펜스가 GPU를 통과할 때까지 기다리는 새로운 작업(명령 목록)을 제출합니다. 이 작업은 GPU가 완료되기 전에 모든 것을 나타내거나 펜스가 전달될 때(앱이 중지 중인 스레드로 대기 중일 수 있음) CPU 이벤트가 발생하도록 요청할 수 있습니다. Direct3D 11에서는 EnqueueSetEvent
()입니다.