Microsoft* DirectX* 12 におけるリソースバインドの紹介

同カテゴリーの次の記事

OpenCL* 2.0 のジェネリック・アドレス空間

この記事は、インテル® デベロッパー・ゾーンに公開されている「Introduction to Resource Binding in Microsoft DirectX* 12」の日本語参考訳です。


こちらから原文の PDF をダウンロードできます。

2014 年 3 月 20 日、マイクロソフト社はゲーム・デベロッパー・カンファレンスで DirectX* 12 を発表しました。リソースのオーバーヘッドを軽減することで、DirectX* 12 はアプリケーションをより効率良く実行し、消費電力を下げ、そしてモバイルデバイス上でより長時間のゲームプレイを可能にします。

SIGGRAPH 2014 において、インテルは Microsoft* Surface* Pro 3 タブレット上で簡単な天体アプリケーションのデモを実行し、CPU の消費電力を計測しました。デモで使用したアプリケーションは、ボタンをタップすることで DirectX* 11 API と DirectX* 12 API を切り替えることができました。このデモは、固定されたフレームレートで空間内に多くの小惑星を描画します (http://www.isus.jp/article/windows/siggraph-2014-directx-12-on-intel)。 DirectX* 12 API を使用した場合、DirectX* 11 と比較すると CPU の消費電力を半分に抑えることができ、その結果デバイスの冷却に貢献し長時間のバッテリー寿命につながりました。一般的なゲームシナリオでは、CPU パワーによる利点をより良い物理エンジン、AI、経路探索、または CPU 消費が激しいタスクで活用することで、ゲームをさらに豊かにしエネルギー効率を改善できます。

ツール要件

DirectX* 12 を使用してゲームを開発するには、以下のツールが必要です:

  • Windows* 10 Technical Preview
  • DirectX* 12 SDK
  • Visual Studio* 2013
  • DirectX* 12 をサポートする GPU ドライバー

ゲーム開発者の方は、https://onedrive.live.com/survey?resid=A4B88088C01D9E9A!107&authkey=!AFgbVA2sYbeoepQ で、Microsoft* DirectX* Early Access Program をご覧ください。

SDK をインストールするセットアップの手順と GPU ドライバーは、DirectX* Early Access Program に参加後に入手できます。

はじめに

ハイレベルな観点から DirectX* 10 および DirectX* 11 と比較すると、DirectX* 12 アーキテクチャーはステート管理の方法とリソースがメモリー上で追跡および管理されるという点で異なります。

DirectX* 10 では、実行中のステートグループを設定するためステート・オブジェクトが導入されました。DirectX* 12 では、シェーダーとともにより大きなステートグループを設定できるよう、パイプライン・ステート・オブジェクト (PSO) が導入されています。この記事では、リソースを扱う違いについて注目し、今後の記事に向けてステートが PSO でどのようにグループ化されるかを説明します。

DirectX* 11 では、システムは、広範囲に DirectX* 11 を利用する場合アプリケーションの設計を制限するリソース利用パターンの追跡や予測に対する責任がありました。DirectX* 12 では、基本的にシステムやドライバーではなくプログラマーが次の 3 つの利用パターンを扱う責任を持ちます:

  1. リソースのバインド
    DirectX* 10 と 11 は、アプリケーションは既に開放していてもまだ未処理の GPU ワークによって参照されるリソースを保持するため、グラフィックス・パイプラインのリソースのバインド追跡を行いました。DirectX* 12 は、リソースのバインドを追跡しません。アプリケーションが (言い換えるとプログラマーが)、オブジェクトのライフタイム管理を行う必要があります。
  2. リソースバインドの調査
    DirectX* 12 は、リソースのトランジションが発生した可能性がある場合や、それを識別した際にリソースのバインドを調査しません。例えば、アプリケーションがレンダー・ターゲット・ビュー (RTV) を介して、レンダーターゲットに書き込みを行うと、そのレンダーターゲットはシェーダー・リソース・ビュー (SRV) を介してテクスチャーとして読み取られることがあります。DirectX* 11 の API を使用すると、GPU ドライバーはメモリーのリード・モディファイ・ライトのハザードを回避するためにリソースのトランジションが起こったことを知ることが期待されました。DirectX* 12 では、開発者は特定の API 呼び出しに起因するすべてのリソースのトランジションを特定し追跡しなければいけません。
  3. マップされたメモリーの同期
    DirectX* 11 では、ドライバーが CPU と GPU 間のメモリーマップの同期を処理します。システムは、CPU がアクセスするためにマッピングされたリソースがまだアンマップされていないことにより、レンダリングを遅延させる必要があるかどうかを識別するため、リソースバインドを調査しました。DiorectX* 12 では、アプリケーションが CPU と GPU のリソースアクセスの同期を処理する必要があります。メモリーアクセスを同期する手法の 1 つに、GPU が処理を完了したときにスレッドをウェイクアップするイベントを要求する方法があります。

アプリケーション領域にこれらのリソース利用パターンを移行すると、異なる GPU アーキテクチャーに対応するため新たなインターフェイスのセットを持つ必要があります。

この記事ではさらに、新しいリソースのバインドメカニズム、最初のビルディング・ブロックとなるディスクリプターについて説明します。

ディスクリプター

ディスクリプターは、メモリーにストアされるリソースを表します。ディスクリプターは、GPU 固有の不透過な形式で GPU へのオブジェクトを記述するデータブロックです。ディスクリプターを簡単に考えると、DirectX* 11 における古い “ビュー” システムの代替です。さらに、DirectX* 11 のシェーダー・リソース・ビュー (SRV) と順不同のアクセスビュー (UAV) など異なるディスクリプター・タイプに加え、DirectX* 12 は、サンプラーと定数バッファービュー (CBV) のようなディスクリプターもあります。

例えば、SRV は基礎となるリソース、mipmaps / 配列スライスのセット、そしてメモリーを解釈するフォーマットを選択します。SRV ディスクリプターは、テクスチャーである Direct3D* リソースの GPU 仮想アドレスを含んでいる必要があります。アプリケーションは、基礎となるリソースがすでに破棄されていないか、または非常駐であるためアクセスできない状態ではないか確認しなければいけません。

図 1 は、テクスチャーへの “ビュー” を示すディスクリプターです:


図 1. ディスクリプターに記述されるシェーダー・リソース・ビュー (Copyright © Microsoft の利用許可による)

DirectX* 12 でシェーダー・リソース・ビューを作成するには、次の構造体と Direct3D* デバイスメソッドを使用します:

typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC
{
    DXGI_FORMAT Format;
    D3D12_SRV_DIMENSION ViewDimension;

    union
    {
        D3D12_BUFFER_SRV Buffer;
        D3D12_TEX1D_SRV Texture1D;
        D3D12_TEX1D_ARRAY_SRV Texture1DArray;
        D3D12_TEX2D_SRV Texture2D;
        D3D12_TEX2D_ARRAY_SRV Texture2DArray;
        D3D12_TEX2DMS_SRV Texture2DMS;
        D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray;
        D3D12_TEX3D_SRV Texture3D;
        D3D12_TEXCUBE_SRV TextureCube;
        D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray;
        D3D12_BUFFEREX_SRV BufferEx;
    };
} D3D12_SHADER_RESOURCE_VIEW_DESC;

interface ID3D12Device
{
...
    void CreateShaderResourceView (
        _In_opt_ ID3D12Resource* pResource,
        _In_opt_ const D3D12_SHADER_RESOURCE_VIEW_DESC* pDesc,
        _In_ D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor);
};

SRV コードの例は次のようになります:

// SRV を作成
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D12_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = mTexture->Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;

mDevice->CreateShaderResourceView(mTexture.Get(), &srvDesc, mCbvSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

このコードは、2D テクスチャー向けの SRV を作成し、フォーマットと GPU 仮想アドレスを指定します。CreateShaderResourceView の最後の引数は、メソッドを呼び出す前に割り当てられるディスクリプター・ヒープと呼ばれるハンドルです。ディスクリプターは通常ディスクリプター・ヒープにストアされます。詳細は次節で説明します。

注: また、ルート・パラメーターと呼ばれるドライバーバージョンのメモリーを介して、GPU にいくつかのタイプのディスクリプターを渡すこともできます。詳細については後述します。

ディスクリプター・ヒープ

ディスクリプター・ヒープは、複数のディスクリプターのための 1 つのメモリー割り当てと考えることができます。異なるタイプのディスクリプター・ヒープは、1 つ以上のディスクリプター・タイプを含むことができます。以下にサポートされているタイプを示します:

Typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
 D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP	= 0,
 D3D12_SAMPLER_DESCRIPTOR_HEAP = (D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP + 1) ,
 D3D12_RTV_DESCRIPTOR_HEAP	= ( D3D12_SAMPLER_DESCRIPTOR_HEAP + 1 ) ,
 D3D12_DSV_DESCRIPTOR_HEAP	= ( D3D12_RTV_DESCRIPTOR_HEAP + 1 ) ,
 D3D12_NUM_DESCRIPTOR_HEAP_TYPES = ( D3D12_DSV_DESCRIPTOR_HEAP + 1 ) 
} 	D3D12_DESCRIPTOR_HEAP_TYPE;

CBV、SRV そして UAV 向けのディスクリプター・ヒープ・タイプがあります。レンダー・ターゲット・ビュー (RTV) と深度ステンシルビュー (DSV) を扱うタイプもあります。

次のコードは、9 つのディスクリプター向けのディスクリプター・ヒープを作成します。それぞれは、CBV、SRV もしくは UAV です:

// シェーダー・リソース・ビューと定数バッファービュー・ディスクリプター・ヒープの作成
D3D12_DESCRIPTOR_HEAP_DESC descHeapCbvSrv = {};
descHeapCbvSrv.NumDescriptors = 9;
descHeapCbvSrv.Type = D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP;
descHeapCbvSrv.Flags = D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapCbvSrv, __uuidof(ID3D12DescriptorHeap), (void**)&mCbvSrvDescriptorHeap));

ディスクリプター・ヒープの最初の 2 つのエントリーは、ディスクリプター数とヒープで許されるディスクリプター・タイプです。3 番目の引数 D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE は、このディスクリプター・ヒープがシェーダーから可視であることを示しています。シェーダーから可視でないディスクリプター・ヒープは、例えば、シェーダーが選択できない CPU 上のもしくは RTV 向けのディスクリプターをステージングするために利用できます。

このコードは、シェーダーからディスクリプター・ヒープが見えるようにフラグを設定していますが、1 つ以上のインダイレクション・レベルがあります。シェーダーはディスクリプター・テーブルを介してディスクリプター・ヒープを参照できます (また、テーブルを使用しないルート・ディスクリプターがありますが、これに関しては後述します)。

ディスクリプター・テーブル

ディスクリプター・ヒープの主な目的は、1 フレーム以上を可能な限りレンダリングできるよう、すべてのディスクリプターを格納するのに必要なメモリーを割り当てることです。

注: ディスクリプター・ヒープの切り替えは、基盤となるハードウェアに依存し、GPU パイプラインをフラッシュする可能性があります。そのため、ディスクリプター・ヒープの切り替えは、最小限にするかグラフィックス・パイプラインをフラッシュする他の操作とペアにする必要があります。

ディスクリプター・テーブルは、ディスクリプター・ヒープへオフセットされます。グラフィックス・パイプラインが常にヒープ全体を参照することを強制する代わりに、シェーダーが必要とするリソースのセットを変更するため、ディスクリプター・テーブルを切り替えるのは安価な方法です。この方法では、シェーダーはヒープ空間のどこに必要とするリソースがあるか知る必要はありません。

言い換えると、図 2 に示すように、アプリケーションは、異なるシェーダー向けに同じディスクリプター・ヒープ指す複数のディスクリプター・テーブルを利用することができます。


図 2. ディスクリプター・ヒープと異なるディスクリプター・テーブルへの異なるシェーダーインデックス

SRV 向けのディスクリプター・テーブルとサンプラーは、ピクセルシェーダー向けの可視性と共に次のコードで作成されます。

// SRV とピクセルシェーダー向けのサンプラーのディスクリプター・テーブルを定義
D3D12_DESCRIPTOR_RANGE descRange[2];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER, 1, 0);

D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);

ディスクリプター・テーブルの可視性は、D3D12_SHADER_VISIBILITY_PIXEL フラグを設定することでピクセルシェーダーを制限できます。以下の enum は、ディスクリプター・テーブルの可視性を異なるレベルで定義しています:

typedef enum D3D12_SHADER_VISIBILITY
{
 D3D12_SHADER_VISIBILITY_ALL	= 0,
 D3D12_SHADER_VISIBILITY_VERTEX	= 1,
 D3D12_SHADER_VISIBILITY_HULL	= 2,
 D3D12_SHADER_VISIBILITY_DOMAIN	= 3,
 D3D12_SHADER_VISIBILITY_GEOMETRY	= 4,
 D3D12_SHADER_VISIBILITY_PIXEL	= 5
} D3D12_SHADER_VISIBILITY;

一度だけ設定することで、すべてのシェーダーステージに引数をブロードキャストする、可視性を設定するフラグを提供します。

シェーダーは、ディスクリプター・テーブルを介してリソースを検出できますが、ディスクリプター・テーブルはルート署名のルート引数によってそのシェーダーが最初に参照したことを通知される必要があります。

ルート署名と引数

ルート署名は、シェーダーがアクセスを必要とするリソースを検出するため、シェーダーが使用するルート引数を記憶します。これらの引数は、アプリケーションがシェーダーを利用できるようにするため、必要となるリソースのコレクション向けのコマンドリスト上のバインド空間として存在します。

ルート引数は次のようになります:

  • ディスクリプター・テーブル: 前述のように、ディスクリプター・テーブルは、ディスクリプター・ヒープへのオフセットとディスクリプター数を保持します。
  • ルート・ディスクリプター: 少数のディスクリプターのみをルート引数に直接保存することができます。これは、アプリケーションがディスクリプター・ヒープにディスクリプターを配置する労力を軽減し、間接参照を排除します。
  • ルート定数: これらは、ルート・ディスクリプターやディスクリプター・テーブルを経由せず、シャーダーに直接提供される定数です。

最適なパフォーマンスを達成するため、アプリケーションは更新頻度の高い順にルート引数を並べ替える必要があります。

ディスクリプター・テーブル、ルート・ディスクリプター、およびルート定数などのすべてのルート引数はコマンドリストに作成され、ドライバーはアプリケーションの変わりにそれらのバージョンを管理します。つまり、描画もしくはディスパッチ呼び出し間でルート引数が変更されるたびに、ハードウェアはルート署名のバージョン番号を更新します。それぞれの描画/ディスパッチ呼び出しは、いずれかの引数が変更されるとルート引数状態のフルセットを取得します。

ルート・ディスクリプターとルート定数がアクセスされると GPU のレベルを低下させます。ディスクリプター・テーブルは大量のデータアクセスを可能にしますが、間接アクセスのコストがかかります。ディスクリプター・テーブルの高レベルの間接アクセスにより、アプリケーションは実行のためコマンドが送信されるまで、コンテンツを初期化することができます。さらに、すべての DirectX* 12 ハードウェアでサポートされるシェーダーモデル 5.1 は、シェーダーへ任意のディスクリプター・テーブルを動的に索引検索することを可能にします。そのため、シェーダーは、実行時にディスクリプター・テーブルから必要とするディスクリプターを選択することができます。アプリケーションは、単一の大きなディスクリプター・テーブルを作成し、常に目的のディスクリプターを取得するため (マテリアル ID のようなものを介して) 索引付けを行うことができます。

異なるハードウェア・アーキテクチャーでは、ディスクリプター・テーブルを使用するのに対して、大きなルート定数のセットとルート・ディスクリプターを利用することでトレードオフにより異なったパフォーマンス特性を示すでしょう。そのため、ハードウェア・ターゲット・プラットフォームに応じて、ルート引数とディスクリプター・テーブル間の利用比率を調整する必要があります。アプリケーション向けの妥当な組み合わせは、次のすべてのバインドタイプの組み合わせとなるかもしれません: ルート定数、ルート・ディスクリプター、描画呼び出しが発行された時にオンザフライで生成されるディスクリプター向けのディスクリプター・テーブル、そして大きなディスクリプター・テーブルの動的な索引検索。

次のコードは、ルート署名中のルート引数として前述した 2 つのディスクリプター・テーブルをストアします。 

// SRV とピクセルシェーダー向けのサンプラーのディスクリプター・テーブルを定義
D3D12_DESCRIPTOR_RANGE descRange[2];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER, 1, 0);

D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);

// 整数型ルート署名のディスクリプター・テーブルをストア
D3D12_ROOT_SIGNATURE descRootSignature;
descRootSignature.Init(2, rootParameters, 0);

ComPtr<ID3DBlob> pOutBlob;
ComPtr<ID3DBlob> pErrorBlob;
ThrowIfFailed(D3D12SerializeRootSignature(&descRootSignature,   
              D3D_ROOT_SIGNATURE_V1, pOutBlob.GetAddressOf(),                    
              pErrorBlob.GetAddressOf()));

ThrowIfFailed(mDevice->CreateRootSignature(pOutBlob->GetBufferPointer(), 
              pOutBlob->GetBufferSize(), __uuidof(ID3D12RootSignature),                  
             (void**)&mRootSignature));

PSO のすべてのシェーダーは、この PSO に指定されたルート署名と互換性がなければいけません;それ以外では PSO は作成されません。

ルート署名は、コマンドリストもしくはバンドルに設定される必要があります。これは以下を呼び出すことで行われます: 

commandList->SetGraphicsRootSignature(mRootSignature);

ルート署名を設定した後、バインドセットを定義する必要があります。上記の例は、次のコードで行われます

// SRV とサンプラーのディスクリプター・ヒープのインデックスには、
// 2つのディスクリプター・テーブルを設定
commandList->SetGraphicsRootDescriptorTable(0, 
               mCbvSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
commandList->SetGraphicsRootDescriptorTable(1, 
               mSamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

アプリケーションは、描画呼び出しやディスパッチ呼び出しを発行する前に、ルート署名内の 2 つのスロットに適切な引数を設定する必要があります。この例では、最初のスロットは SRV ディスクリプターへのディスクリプター・ヒープのインデックスを操作するディスクリプターを保持し、2 番目のスロットはサンプラー・ディスクリプターへのディスクリプター・ヒープのインデックスであるディスクリプター・テーブルを保持します。

アプリケーションは、描画呼び出し間で 2 番目のスロットをバインドするように、変更できます。これは、2 番目の描画呼び出しには、2 番目のスロットをバインドするだけで良いことを意味します。

すべてをまとめる

次のソースコードは、リソースをバインドするために使用するすべてのメカニズムを示しています。このアプリケーションは、単一のテクスチャーを使用し、サンプラーとテクスチャーの SRV を提供します:

// SRV とピクセルシェーダー向けのサンプラーのディスクリプター・テーブルを定義します
D3D12_DESCRIPTOR_RANGE descRange[2];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER, 1, 0);

D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);

// ルート署名にディスクリプター・テーブルを格納します
D3D12_ROOT_SIGNATURE descRootSignature;
descRootSignature.Init(2, rootParameters, 0);

ComPtr<ID3DBlob> pOutBlob;
ComPtr<ID3DBlob> pErrorBlob;
ThrowIfFailed(D3D12SerializeRootSignature(&descRootSignature,   
              D3D_ROOT_SIGNATURE_V1, pOutBlob.GetAddressOf(),                    
              pErrorBlob.GetAddressOf()));

ThrowIfFailed(mDevice->CreateRootSignature(pOutBlob->GetBufferPointer(), 
              pOutBlob->GetBufferSize(), __uuidof(ID3D12RootSignature),                  
             (void**)&mRootSignature));



// シェーダー・リソース・ビューのディスクリプター・ヒープを作成
D3D12_DESCRIPTOR_HEAP_DESC descHeapCbvSrv = {};
descHeapCbvSrv.NumDescriptors = 1; // SRV 向け
descHeapCbvSrv.Type = D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP;
descHeapCbvSrv.Flags = D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapCbvSrv, __uuidof(ID3D12DescriptorHeap), (void**)&mCbvSrvDescriptorHeap));
		
// サンプラーのディスクリプター・ヒープを作成
D3D12_DESCRIPTOR_HEAP_DESC descHeapSampler = {};
descHeapSampler.NumDescriptors = 1;
descHeapSampler.Type = D3D12_SAMPLER_DESCRIPTOR_HEAP;
descHeapSampler.Flags = D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapSampler, __uuidof(ID3D12DescriptorHeap), (void**)&mSamplerDescriptorHeap));

// ヒープにテクスチャーをアップロードするコードをスキップ

// サンプル・ディスクリプター・ヒープ内にサンプラーのディスクリプターを作成
D3D12_SAMPLER_DESC samplerDesc;
ZeroMemory(&samplerDesc, sizeof(D3D12_SAMPLER_DESC));
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_ALWAYS;
mDevice->CreateSampler(&samplerDesc, 
           mSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

// SRV ディスクリプター・ヒープ内に SRV のディスクリプターを作成
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D12_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = SampleAssets::Textures->Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
mDevice->CreateShaderResourceView(mTexture.Get(), &srvDesc,            
            mCbvSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());


// コマンドリストに書き込み
// ルート署名を設定
commandList->SetGraphicsRootSignature(mRootSignature);

// その他のコマンド ...

// SRV とサンプラー向けにディスクリプター・ヒープへのインデックスに、
// 2 つのディスクリプター・テーブルを作成
commandList->SetGraphicsRootDescriptorTable(0, 
               mCbvSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
commandList->SetGraphicsRootDescriptorTable(1, 
               mSamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

スタティック・サンプラー

ディスクリプター・ヒープとディスクリプター・テーブルを使用してサンプラーを作成する方法を見てきましたが、アプリケーションでサンプラーを使用するには別の方法もあります。多くのアプリケーションは、固定セットのサンプラーのみを必要とするため、ルート引数として静的サンプラーを使用することができます。現在のルート署名は以下のようになります:

typedef struct D3D12_ROOT_SIGNATURE
{
    UINT NumParameters;
    const D3D12_ROOT_PARAMETER* pParameters;
    UINT NumStaticSamplers;
    const D3D12_STATIC_SAMPLER* pStaticSamplers;
    D3D12_ROOT_SIGNATURE_FLAGS Flags;

    // 構造体を初期化
    void Init(
        UINT numParameters,
        const D3D12_ROOT_PARAMETER* _pParameters,
        UINT numStaticSamplers = 0,
        const D3D12_STATIC_SAMPLER* _pStaticSamplers = NULL,
        D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_NONE)
    {
        NumParameters = numParameters;
        pParameters = _pParameters;
        NumStaticSamplers = numStaticSamplers;
        pStaticSamplers = _pStaticSamplers;
        Flags = flags;
    }

    D3D12_ROOT_SIGNATURE() { Init(0,NULL,0,NULL,D3D12_ROOT_SIGNATURE_NONE);}

    D3D12_ROOT_SIGNATURE(
        UINT numParameters,
        const D3D12_ROOT_PARAMETER* _pParameters,
        UINT numStaticSamplers = 0,
        const D3D12_STATIC_SAMPLER* _pStaticSamplers = NULL,
        D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_NONE)
    {
        Init(numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags);
    }
} D3D12_ROOT_SIGNATURE;

静的サンプラーのセットは、ルート署名内で独立したルート引数として定義できます。前述したように、ルート引数は実行時に提供される引数のバインド空間を定義しますが、静的なサンプラーの定義は不変です。

ルート署名は HLSL に記述できるため、静的なサンプラーも同様に記述できます。今のところ、アプリケーションは最大 2032 個の静的サンプラーを持つことが出来ます。これは、2 の 11 乗より僅かに少なく、ドライバーが内部的にスロットの一部を使用することを可能にします。

ルート署名で定義された静的サンプラーは、アプリケーションがディスクリプター・ヒープに配置することを選択したサンプラーとは独立しているため、両者のメカニズムを同時に利用することができます。

サンプラーの選択がシェーダーのコンパイル時に動的かつ不明である場合、アプリケーションがディスクリプター・ヒープ内のサンプラーを管理する必要があります。

まとめ

DirectX* 12 は、リソースの利用パターンを完全に制御する方法を提供します。アプリケーション開発者は、ディスクリプター・ヒープ内のメモリーを割り当て、ディスクリプター内のリソースの記述、そしてルート署名を介してシェーダーに “既知” であるディスクリプター・テーブルにより、ディスクリプター・ヒープへのシェーダー “インデックス” を明確にする責任があります。

また、ルート署名は、次の 4 つのオプションの組み合わせにより、シェーダーのカスタム引数空間を定義することができます:

  • ルート定数
  • スタティック・サンプラー
  • ルート・ディスクリプター
  • ディスクリプター・テーブル

最後に、私たちの課題は、リソースのタイプとその更新頻度に関して最も望ましいバインド形態を選択することです。

著者紹介

Wolfgang 氏は Confetti の CEO です。Confetti は、高度なリアルタイム・グラフィックスの研究やビデオゲームと映像業界のサービス・プロバイダー向けのシンクタンクです。Confetti を共同創立する前、Wolfgang 氏はロックスターのコア技術グループ RAGE のリード・グラフィックス・プログラマーとして 4 年以上従事していました。彼は、ShaderX と GPU Pro Book シリーズの創業者および編集者であり、Microsoft* MVP、リアルタイム・レンダリングに関するいくつかの書籍や記事の著者であり、GDC ウェブサイトに定期的に寄稿を行っています。彼の書籍の 1 つである ShaderX4 は、2006 年にゲーム開発者フロントライン・アワードを受賞しました。Wolfgang 氏は、業界の多くの諮問委員です。その 1 つは、DirectX* 12 向けの Microsoft* グラフィックス諮問委員会です。彼は、ゲーム業界を牽引するいくつかの将来の規格に積極的に貢献しています。彼のツイッターアカウントは、wolfgangengel です。Confetti のウェブサイトは、www.conffx.com です。

謝辞

校正とフィードバックをくれた Chas Boyd、Amar Patel、そして David Reining に感謝します。

参考資料と関連リンク

** 性能に関するテストに使用されるソフトウェアとワークロードは、性能がインテル® マイクロプロセッサー用に最適化されていることがあります。SYSmark* や MobileMark* などの性能テストは、特定のコンピューター・システム、コンポーネント、ソフトウェア、操作、機能に基づいて行ったものです。結果はこれらの要因によって異なります。製品の購入を検討される場合は、他の製品と組み合わせた場合の本製品の性能など、ほかの情報や性能テストも参考にして、パフォーマンスを総合的に評価することをお勧めします。

関連記事