ルート署名の作成

ルート署名は複雑なデータ構造であり、その中で構造体が入れ子になっています。 これらは、下のデータ構造の定義を使用してプログラムによって定義できます (メンバーの初期化に役立つメソッドを含む)。 別の方法として、高水準シェーディング言語 (HLSL) で作成することもできます。この方法には、レイアウトがシェーダーに対応していることがコンパイラによって早い段階で検証されるという利点があります。

ルート署名を作成するための API は、以下で説明するレイアウト記述をシリアル化したもの (自己完結型、ポインターなし) を受け取ります。 このシリアル化バージョンを C++ のデータ構造から生成するためのメソッドが用意されていますが、シリアル化されたルート署名定義を取得するもう 1 つの方法として、ルート署名付きでコンパイルされたシェーダーからルート署名を取り出すこともできます。

ルート署名の記述子とデータに対するドライバー最適化を利用する場合は、「ルート署名バージョン 1.1」を参照してください。

記述子テーブル バインドの種類

列挙型 D3D12_DESCRIPTOR_RANGE_TYPE は、記述子テーブル レイアウト定義の一部として参照できる記述子の型を定義します。

これは範囲であるため、たとえば記述子テーブルの一部に 100 の SRV がある場合、その範囲は 100 ではなく 1 つのエントリで宣言できます。 したがって、記述子テーブル定義は範囲のコレクションです。

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 を必要なオフセットに設定できます。 異なる種類の範囲が重複するように定義することはできません。

NumDescriptorsBaseShaderRegisterおよび RegisterSpace の組み合わせで指定されたシェーダー レジスタのセットは、共通のRangeTypeD3D12_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構造体は、シェーダーに 1 つの定数バッファーとして表示されるルート署名で定数をインラインで宣言します。

この構造体は、ルート署名のスロットの種類を D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS に設定するときに使用されます。

ルート記述子

D3D12_ROOT_DESCRIPTOR構造体は、ルート署名で記述子 (シェーダーに表示される) をインラインで宣言します。

この構造体は、ルート署名のスロットの種類を D3D12_ROOT_PARAMETER_TYPE_CBVD3D12_ROOT_PARAMETER_TYPE_SRV、または D3D12_ROOT_PARAMETER_TYPE_UAV に設定するときに使用されます。

シェーダーの可視性

D3D12_ROOT_PARAMETERのシェーダー可視性パラメーターに設定D3D12_SHADER_VISIBILITY列挙型のメンバーによって、特定のルート署名スロットの内容を表示するシェーダーが決まります。 コンピューティングでは常に_ALLが使用されます (アクティブなステージは 1 つしかないため)。 グラフィックスは選択できますが、_ALLを使用すると、すべてのシェーダー ステージでルート署名スロットにバインドされているものが表示されます。

シェーダー可視性の用途の 1 つは、作成するシェーダーのシェーダー ステージごとに異なるバインドを期待するように、重複する名前空間を使ってプログラミングする場合です。 たとえば、頂点シェーダーで次のように宣言するとします。

Texture2D foo : register(t0);

ピクセル シェーダーでも次のように宣言できます。

Texture2D bar : register(t0);

アプリケーションが t0 VISIBILITY_ALLにルート署名バインドを作成すると、両方のシェーダーに同じテクスチャが表示されます。 シェーダーが実際に各シェーダーに異なるテクスチャを表示する必要がある場合は、VISIBILITY_VERTEXと_PIXELを使用して 2 つのルート署名スロットを定義できます。 ルート署名スロットに対してどの可視性が設定されているかにかかわらず、1 つの固定最大ルート署名サイズに対するコストは常に同じです (コストは SlotType のみに依存します)。

ロー エンドの D3D11 ハードウェアでは、ルート レイアウトで記述子テーブルのサイズを検証するときにSHADER_VISIBILITYも考慮されます。これは、一部の D3D11 ハードウェアでは、ステージあたりのバインディングの最大数しかサポートできないためです。 このような制限が課せられるのは低階層のハードウェアで実行されるだけであり、現代的なハードウェアでは制限はありません。

ルート署名に、名前空間 (シェーダーへのレジスタ バインド) で相互に重複する複数の記述子テーブルが定義されており、そのうちの 1 つが可視性のために_ALLを指定している場合、レイアウトは無効です (作成は失敗します)。

ルート署名の定義

D3D12_ROOT_SIGNATURE_DESC構造体には記述子テーブルとインライン定数を含めることができます。各スロットの種類は、D3D12_ROOT_PARAMETER構造体と列挙型D3D12_ROOT_PARAMETER_TYPEによって定義されます。

ルート署名スロットを開始するには、ID3D12GraphicsCommandListの SetComputeRoot*** メソッドと SetGraphicsRoot*** メソッドを参照してください。

静的サンプラーは、 D3D12_STATIC_SAMPLER 構造体を使用してルート署名で説明されています。

いくつかのフラグによって、特定のシェーダーのアクセスがルート署名に制限されます。 D3D12_ROOT_SIGNATURE_FLAGSを参照してください。

ルート署名データ構造のシリアル化/逆シリアル化

このセクションで説明するメソッドは D3D12Core.dll によってエクスポートされるものであり、ルート署名のデータ構造をシリアル化および逆シリアル化するためのメソッドが用意されています。

シリアル化された形式は、ルート署名を作成するときに API に渡される形式です。 シェーダーをルート署名付きで作成した場合は (その機能が追加されているとき)、コンパイルされたシェーダーの中には既にシリアル化されたルート署名が存在することになります。

アプリケーションが D3D12_ROOT_SIGNATURE_DESCデータ構造 を手続き的に生成する場合は、 D3D12SerializeRootSignature を使用してシリアル化されたフォームを作成する必要があります。 その出力を ID3D12Device::CreateRootSignature に渡すことができます。

アプリケーションにシリアル化されたルート署名が既にある場合、またはコンパイル済みシェーダーの中にルート署名が存在していてそのレイアウト定義をプログラムで検出したい場合は ("リフレクション" と呼ばれます)、D3D12CreateRootSignatureDeserializer を呼び出すことができます。 これにより ID3D12RootSignatureDeserializer インターフェイスが生成されます。このインターフェイスには、逆シリアル化されたD3D12_ROOT_SIGNATURE_DESCデータ構造を返すメソッド 含まれています。 このインターフェイスが、逆シリアル化されたデータ構造体の有効期間全体の所有者となります。

ルート署名作成 API

ID3D12Device::CreateRootSignature API は、シリアル化された形式のルート署名を受け取ります。

パイプライン状態オブジェクトでのルート署名

パイプライン状態を作成するメソッド (ID3D12Device::CreateGraphicsPipelineState および ID3D12Device::CreateComputePipelineState ) は、省略可能な ID3D12RootSignature インターフェイスを入力パラメーターとして受け取ります ( D3D12_GRAPHICS_PIPELINE_STATE_DESC 構造体に格納されます)。 これは、シェーダー内の既存のルート署名よりも優先されます。

ルート署名がパイプライン状態作成メソッドの 1 つに渡された場合は、そのルート署名は PSO 内のすべてのシェーダーに対応しているかどうかの検証が行われ、その後でドライバーに渡されてすべてのシェーダーとともに使用されます。 これらのシェーダーの中に、別のルート署名を持つものがある場合は、API に渡されたルート署名で置き換えられます。 ルート署名が渡されない場合は、渡されるすべてのシェーダーが同じルート署名を持つ必要があり、これがドライバーに提供されます。 PSO をコマンド リストまたはバンドルに対して設定しても、ルート署名が変更されることはありません。 このことを達成するには、メソッド SetGraphicsRootSignatureSetComputeRootSignature を使用します。 描画 (グラフィックス)/ディスパッチ (計算) が呼び出される時点までに、アプリケーションは現在の PSO が現在のルート署名と一致していることを保証する必要があります。このとおりでない場合は、動作は未定義となります。

バージョン 1.1 のルート署名を定義するためのコード

以下の例では、次の形式のルート署名を作成する方法を示します。

RootParameterIndex 内容
[0] ルート定数: { b2 } (1 CBV)
[1] 記述子テーブル: { t2-t7、u0-u3 } (6 SRV + 4 UAV)
[2] ルート CBV: { b0 } (1 CBV、静的データ)
[3] 記述子テーブル: { s0-s1 } (2 サンプラー)
[4] 記述子テーブル: { t8 - 無制限 } (無制限の SRV、揮発性記述子)
[5] 記述子テーブル: { (t0、space1) - 無制限 } (無制限の 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 でのルート署名の指定

ルート署名の使用