루트 서명 만들기

루트 서명은 중첩 구조를 포함하는 복합 데이터 구조입니다. 루트 서명은 아래 데이터 구조 정의(멤버 초기화에 도움이 되는 메서드 포함)를 사용하여 프로그래밍 방식으로 정의할 수 있습니다. 또는 컴파일러가 레이아웃이 셰이더와 호환되는지 초기에 유효성을 검사하는 장점이 있는 HLSL(High Level Shading Language)로 작성할 수 있습니다.

루트 서명을 만들기 위한 API는 아래에 나온 레이아웃 설명의 직렬화된(자체 포함, 포인터 프리) 버전을 사용합니다. C++ 데이터 구조에서 이 직렬화된 버전을 생성하는 메서드가 제공되지만, 직렬화된 루트 서명 정의를 얻는 또 다른 방법은 루트 서명으로 컴파일된 셰이더에서 검색하는 것입니다.

루트 서명 설명자 및 데이터에 대한 드라이버 최적화를 활용하려면 루트 서명 버전 1.1을 참조하세요.

설명자 테이블 바인딩 형식

열거형 D3D12_DESCRIPTOR_RANGE_TYPE 설명자 테이블 레이아웃 정의의 일부로 참조할 수 있는 설명자의 형식을 정의합니다.

예를 들어 설명자 테이블의 일부에 100개의 SRV가 있는 경우 해당 범위를 100이 아닌 하나의 항목으로 선언할 수 있도록 하는 범위입니다. 따라서 설명자 테이블 정의는 범위의 컬렉션입니다.

typedef enum D3D12_DESCRIPTOR_RANGE_TYPE
{
  D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
  D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
  D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
} D3D12_DESCRIPTOR_RANGE_TYPE;

설명자 범위

D3D12_DESCRIPTOR_RANGE 구조체는 설명자 테이블 내에서 지정된 형식의 설명자 범위(예: SRV)를 정의합니다.

매크로는 D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND 일반적으로 D3D12_DESCRIPTOR_RANGE 매개 변수에 OffsetInDescriptorsFromTableStart 사용할 수 있습니다. 이는 설명자 테이블에서 이전 설명자 범위 뒤에 정의되는 설명자 범위를 추가하는 것을 의미합니다. 애플리케이션이 설명자의 별칭을 지정하거나 어떤 이유로 슬롯을 건너뛰려는 경우 원하는 오프셋으로 OffsetInDescriptorsFromTableStart를 설정할 수 있습니다. 서로 다른 유형의 범위를 겹치게 정의해서는 안 됩니다.

, , RegisterSpaceNumDescriptorsBaseShaderRegister및 의 RangeType조합으로 지정된 셰이더 레지스터 집합은 공통 D3D12_SHADER_VISIBILITY 있는 루트 서명의 선언 간에 충돌하거나 겹칠 수 없습니다(아래 셰이더 표시 여부 섹션 참조).

설명자 테이블 레이아웃

D3D12_ROOT_DESCRIPTOR_TABLE 구조체는 설명자 테이블의 레이아웃을 설명자 힙의 지정된 오프셋에서 시작하는 설명자 범위의 컬렉션으로 선언합니다. 샘플러는 CBV/UAV/SRV와 동일한 설명자 테이블에서 허용되지 않습니다.

이 구조는 루트 서명 슬롯 형식이 D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE로 설정된 경우 사용됩니다.

그래픽(CBV, SRV UAV, 샘플러) 설명자 테이블을 설정하려면 ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable을 사용합니다.

컴퓨팅 설명자 테이블을 설정하려면 ID3D12GraphicsCommandList::SetComputeRootDescriptorTable을 사용합니다.

루트 상수

D3D12_ROOT_CONSTANTS 구조체는 셰이더에 하나의 상수 버퍼로 표시되는 루트 서명에서 상수를 인라인으로 선언합니다.

이 구조는 루트 서명 슬롯 형식이 D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS로 설정된 경우 사용됩니다.

루트 설명자

D3D12_ROOT_DESCRIPTOR 구조체는 루트 서명에서 설명자(셰이더에 표시됨)를 인라인으로 선언합니다.

이 구조는 루트 서명 슬롯 형식이 D3D12_ROOT_PARAMETER_TYPE_CBV, D3D12_ROOT_PARAMETER_TYPE_SRV 또는 D3D12_ROOT_PARAMETER_TYPE_UAV로 설정된 경우 사용됩니다.

셰이더 표시 여부

D3D12_ROOT_PARAMETER 셰이더 표시 유형 매개 변수로 설정된 D3D12_SHADER_VISIBILITY 열거형의 멤버는 지정된 루트 서명 슬롯의 내용을 보는 셰이더를 결정합니다. 컴퓨팅은 항상 _ALL 사용합니다(활성 단계가 하나만 있기 때문에). 그래픽은 선택할 수 있지만 _ALL 사용하는 경우 모든 셰이더 단계에서 루트 서명 슬롯에 바인딩된 모든 것을 볼 수 있습니다.

셰이더 표시 여부는 겹쳐진 네임스페이스를 사용하여 셰이더 스테이지마다 서로 다른 바인딩이 표시되도록 작성한 셰이더에 유용합니다. 예를 들어 꼭짓점 셰이더는 다음을 선언할 수 있습니다.

Texture2D foo : register(t0);

또한 픽셀 셰이더도 다음을 선언할 수 있습니다.

Texture2D bar : register(t0);

애플리케이션이 t0 VISIBILITY_ALL 루트 서명 바인딩을 만드는 경우 두 셰이더 모두 동일한 텍스처를 볼 수 있습니다. 셰이더가 실제로 각 셰이더에 서로 다른 텍스처를 표시하도록 정의하는 경우 VISIBILITY_VERTEX 및 _PIXEL 사용하여 2개의 루트 서명 슬롯을 정의할 수 있습니다. 루트 서명 슬롯의 표시 여부에 상관 없이 하나의 고정된 최대 루트 서명 크기에 대해서는 비용이 항상 동일합니다(SlotType에 따른 비용).

일부 D3D11 하드웨어는 스테이지당 최대 바인딩 크기만 지원할 수 있으므로 로우 엔드 D3D11 하드웨어에서 SHADER_VISIBILITY 루트 레이아웃에서 설명자 테이블의 크기를 확인할 때도 사용됩니다. 이러한 제한 사항은 낮은 계층의 하드웨어에서 실행될 때에만 적용되며 더 최신 하드웨어는 전혀 제한하지 않습니다.

루트 서명에 네임스페이스(셰이더에 대한 레지스터 바인딩)에서 서로 겹치는 여러 설명자 테이블이 정의되어 있고 그 중 하나가 표시 여부를 위해 _ALL 지정하는 경우 레이아웃이 잘못되었습니다(만들기가 실패).

루트 서명 정의

D3D12_ROOT_SIGNATURE_DESC 구조체에는 설명자 테이블과 인라인 상수, D3D12_ROOT_PARAMETER 구조체에 의해 정의된 각 슬롯 형식 및 열거형 D3D12_ROOT_PARAMETER_TYPE 포함될 수 있습니다.

루트 서명 슬롯을 시작하려면 ID3D12GraphicsCommandListSetComputeRoot***SetGraphicsRoot*** 메서드를 참조하세요.

정적 샘플러가 D3D12_STATIC_SAMPLER 구조를 사용하여 루트 서명에 설명되어 있습니다.

여러 플래그는 특정 셰이더의 액세스를 루트 서명으로 제한합니다. D3D12_ROOT_SIGNATURE_FLAGS 참조하세요.

루트 서명 데이터 구조 직렬화/역직렬화

이 섹션에서 설명하는 설명은 D3D12Core.dll에서 내보냈으며 루트 서명 데이터 구조를 직렬화 및 역직렬화하는 방법을 제공합니다.

직렬화된 형식은 루트 서명을 만들 때 API에 전달되는 형식입니다. 루트 서명이 포함된 셰이더가 작성된 경우(해당 기능이 추가된 경우), 직렬화된 루트 서명이 컴파일된 셰이더에 이미 포함되어 있습니다.

애플리케이션이 D3D12_ROOT_SIGNATURE_DESC 데이터 구조를 프로시저 방식으로 생성하는 경우 D3D12SerializeRootSignature를 사용하여 직렬화된 형식을 만들어야 합니다. 해당 출력은 ID3D12Device::CreateRootSignature로 전달될 수 있습니다.

애플리케이션에 이미 직렬화된 루트 서명이 있거나, 루트 서명을 포함하고 레이아웃 정의를 프로그래밍 방식으로 검색하려는 컴파일된 셰이더가 있는 경우(“리플렉션”으로 알려져 있음) D3D12CreateRootSignatureDeserializer를 호출할 수 있습니다. 이렇게 하면 역직렬화된 D3D12_ROOT_SIGNATURE_DESC 데이터 구조를 반환하는 메서드가 포함된 ID3D12RootSignatureDeserializer 인터페이스가 생성됩니다. 인터페이스는 역직렬화된 데이터 구조의 수명을 소유합니다.

루트 서명 생성 API

ID3D12Device::CreateRootSignature API는 루트 서명의 직렬화된 버전을 사용합니다.

파이프라인 상태 개체의 루트 서명

파이프라인 상태를 만드는 메서드(ID3D12Device::CreateGraphicsPipelineStateID3D12Device::CreateComputePipelineState )는 선택적 ID3D12RootSignature 인터페이스를 입력 매개 변수로 사용합니다( D3D12_GRAPHICS_PIPELINE_STATE_DESC 구조에 저장됨). 그러면 셰이더에 이미 있는 모든 루트 서명이 재정의됩니다.

루트 서명이 파이프라인 상태 생성 메서드 중 하나에 전달되면, 이 루트 서명은 호환성을 위해 PSO의 모든 셰이더에 대해 유효성이 검사되고 모든 셰이더와 함께 사용하도록 드라이버에 부여됩니다. 만약 셰이더에 다른 루트 서명이 있는 경우 API에서 전달된 루트 서명으로 대체됩니다. 루트 서명이 전달되지 않으면, 전달된 모든 셰이더에 루트 서명이 있어야 하며 일치해야 합니다. 이 서명은 드라이버로 전달됩니다. 명령 목록 또는 번들에서 PSO를 설정해도 루트 서명은 변경되지 않습니다. 이는 SetGraphicsRootSignatureSetComputeRootSignature 메서드에 의해 수행됩니다. 그리기(그래픽)/디스패치(컴퓨팅)가 호출될 때까지 애플리케이션은 현재 PSO가 현재 루트 서명과 일치하는지 확인해야 하며, 그렇지 않은 경우 동작이 정의되지 않습니다.

버전 1.1 루트 서명을 정의하기 위한 코드

아래 예제에서는 루트 서명을 다음 형식으로 만드는 방법을 보여줍니다.

RootParameterIndex 콘텐츠
[0] 루트 상수: { b2 } (1 CBV)
 [1] 설명자 테이블: { t2-t7, u0-u3 } (6 SRVs + 4 UAVs)
[2] Root CBV: { b0 } (1 CBV, 정적 데이터)
[3] 설명자 테이블: { s0-s1 } (2개 샘플러)
[4] 설명자 테이블: { t8 - unbounded } (SRV의 바인딩되지 않은 #, 휘발성 설명자)
[5] 설명자 테이블: { (t0, space1) - unbounded } (SRV의 바인딩되지 않은 #, 휘발성 설명자)
[6] 설명자 테이블: { b1 } (1 CBV, 정적 데이터)

 

대체로 루트 서명 대부분을 사용하는 경우 루트 서명을 너무 자주 전환해야 하는 것보다 더 나을 수 있습니다. 애플리케이션은 루트 서명의 항목을 가장 자주 변경되는 항목에서 가장 적게 변경되는 항목으로 정렬해야 합니다. 앱이 바인딩을 루트 서명의 일부로 변경하는 경우, 드라이버는 루트 서명 상태의 일부 또는 전부를 복사해야 할 수 있습니다. 이는 많은 상태 변경에 걸쳐 크게 증가하는 경우 적지 않은 비용이 될 수 있습니다.

또한 루트 서명은 셰이더 레지스터 s3에서 이방성 텍스처 필터링을 수행하는 정적 샘플러를 정의하게 됩니다.

이 루트 서명이 바인딩된 후 설명자 테이블, 루트 CBV 및 상수를 [0..6] 매개 변수 공간에 할당할 수 있습니다. 예를 들어 설명자 테이블(설명자 힙의 범위)은 각 루트 매개 변수 [1] 및 [3..6]에 바인딩할 수 있습니다.

CD3DX12_DESCRIPTOR_RANGE1 DescRange[6];

DescRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV,6,2); // t2-t7
DescRange[1].Init(D3D12_DESCRIPTOR_RANGE_UAV,4,0); // u0-u3
DescRange[2].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER,2,0); // s0-s1
DescRange[3].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,8, 0,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); // t8-unbounded
DescRange[4].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,0,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); 
                                                            // (t0,space1)-unbounded
DescRange[5].Init(D3D12_DESCRIPTOR_RANGE_CBV,1,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC); // b1

CD3DX12_ROOT_PARAMETER1 RP[7];

RP[0].InitAsConstants(3,2); // 3 constants at b2
RP[1].InitAsDescriptorTable(2,&DescRange[0]); // 2 ranges t2-t7 and u0-u3
RP[2].InitAsConstantBufferView(0, 0, 
                               D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC); // b0
RP[3].InitAsDescriptorTable(1,&DescRange[2]); // s0-s1
RP[4].InitAsDescriptorTable(1,&DescRange[3]); // t8-unbounded
RP[5].InitAsDescriptorTable(1,&DescRange[4]); // (t0,space1)-unbounded
RP[6].InitAsDescriptorTable(1,&DescRange[5]); // b1

CD3DX12_STATIC_SAMPLER StaticSamplers[1];
StaticSamplers[0].Init(3, D3D12_FILTER_ANISOTROPIC); // s3
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC RootSig(7,RP,1,StaticSamplers);
ID3DBlob* pSerializedRootSig;
CheckHR(D3D12SerializeVersionedRootSignature(&RootSig,pSerializedRootSig)); 

ID3D12RootSignature* pRootSignature;
hr = CheckHR(pDevice->CreateRootSignature(
    pSerializedRootSig->GetBufferPointer(),pSerializedRootSig->GetBufferSize(),
    __uuidof(ID3D12RootSignature),
    &pRootSignature));

다음 코드는 위의 루트 서명이 그래픽 명령 목록에서 어떻게 사용되는지를 보여줍니다.

InitializeMyDescriptorHeapContentsAheadOfTime(); // for simplicity of the 
                                                 // example
CreatePipelineStatesAhreadOfTime(pRootSignature); // The root signature is passed into 
                                     // shader / pipeline state creation
...

ID3D12DescriptorHeap* pHeaps[2] = {pCommonHeap, pSamplerHeap};
pGraphicsCommandList->SetDescriptorHeaps(2,pHeaps);
pGraphicsCommandList->SetGraphicsRootSignature(pRootSignature);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        6,heapOffsetForMoreData,DescRange[5].NumDescriptors);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(5,heapOffsetForMisc,5000); 
pGraphicsCommandList->SetGraphicsRootDescriptorTable(4,heapOffsetForTerrain,20000);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        3,heapOffsetForSamplers,DescRange[2].NumDescriptors);
pGraphicsCommandList->SetComputeRootConstantBufferView(2,pDynamicCBHeap,&CBVDesc);

MY_PER_DRAW_STUFF stuff;
InitMyPerDrawStuff(&stuff);
pGraphicsCommandList->SetGraphicsRoot32BitConstants(
                        0,RTSlot[0].Constants.Num32BitValues,&stuff,0);

SetMyRTVAndOtherMiscBindings();

for(UINT i = 0; i < numObjects; i++)
{
    pGraphicsCommandList->SetPipelineState(PSO[i]);
    pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                    1,heapOffsetForFooAndBar[i],DescRange[1].NumDescriptors);
    pGraphicsCommandList->SetGraphicsRoot32BitConstant(0,i,drawIDOffset);
    SetMyIndexBuffers(i);
    pGraphicsCommandList->DrawIndexedInstanced(...);
}

루트 서명

HLSL의 루트 서명 지정

루트 서명 사용