Direct3D 12を始める – Pipeline State

Windows 10 Technical Preview向けのWindows SDKには、最新のDirectXのライブラリが同梱されています。
その中でもまだ情報が少ないDirect3D 12に焦点を当て、筆者が調べたことをまとめて記事にしていきます。

前回はFenceについて説明しました。
今回はPipelineStateついて説明します。


パイプラインステートとは

Direct3D 11の入力ストリーム、シェーダ、ステート等をすべてひとつのオブジェクトのまとめたものです。
つまり、Direct3D 12には、頂点シェーダやピクセルシェーダの単独設定や、InputLayoutやRasterizer/Blend/DepthStencilStateの設定のAPIはありません。
すべて、Pipeline Stateの作り直しになります。

Direct3D 12の公開当初は、Pipeline State Object (PSO)と呼ばれていました。
このアイデアの初出はAMDのMantleで、Monolithic Pipelineと表現されていました。


パイプラインステートが必要な理由

最近のGPUでは、シェーダとステートのハードウェア実装とAPIに差が出ています。
例えば、入力レイアウトを変更すると、頂点シェーダ入力との辻褄合わせのためにパッチ処理が行われます。
ブレンドステートやレンダーターゲットの数が変わると、ピクセルシェーダ出力との辻褄合わせのためにパッチ処理が行われます。
ハルシェーダを変更すると、頂点シェーダ出力とドメインシェーダ入力との辻褄合わせのためにパッチ処理が行われます。
ステート等の確定はドローコールまで遅延されるので、変更時に予めパッチ処理しておくことはできません。
このように、ドライバはAPIから見えないところで多くのコストを支払っていて、これがCPU負荷を高める原因になっています。

パイプラインステートは、このようなドライバコストを削減することができます。


パイプラインステートの作成に必要な情報

パイプラインには”Graphics Pipeline”と”Compute Pipeline”の2種類があります。
ComputePipelineは簡単で、基本的にコンパイルしたComputeShaderのバイナリ(とRootSignatureというもの)があれば作成できます。

面倒なのはGraphicsPipelineです。

typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC {
  ID3D12RootSignature                *pRootSignature;
  D3D12_SHADER_BYTECODE              VS;
  D3D12_SHADER_BYTECODE              PS;
  D3D12_SHADER_BYTECODE              DS;
  D3D12_SHADER_BYTECODE              HS;
  D3D12_SHADER_BYTECODE              GS;
  D3D12_STREAM_OUTPUT_DESC           StreamOutput;
  D3D12_BLEND_DESC                   BlendState;
  UINT                               SampleMask;
  D3D12_RASTERIZER_DESC              RasterizerState;
  D3D12_DEPTH_STENCIL_DESC           DepthStencilState;
  D3D12_INPUT_LAYOUT_DESC            InputLayout;
  D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;
  D3D12_PRIMITIVE_TOPOLOGY_TYPE      PrimitiveTopologyType;
  UINT                               NumRenderTargets;
  DXGI_FORMAT                        RTVFormats[8];
  DXGI_FORMAT                        DSVFormat;
  DXGI_SAMPLE_DESC                   SampleDesc;
  UINT                               NodeMask;
  D3D12_CACHED_PIPELINE_STATE        CachedPSO;
  D3D12_PIPELINE_STATE_FLAGS         Flags;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;

5種類のシェーダ、3種類のステート、入力レイアウトに加え、トポロジの種類、レンダーターゲットの種類と数、深度バッファのフォーマット、MSAA、プリミティブリスタートインデックスの設定が必要です。
これだけでDirect3D 11からの移植が面倒くさそうな感じがしてきます。
Direct3D 11を前提に設計されているゲームエンジンでは、これらのパラメータのハッシュを求めて、一致すればキャッシュから取得する戦略を取ることでしょう。
ただし、シェーダバイトコードや入力レイアウトはポインタなので、単にデスクリプタ全体をハッシュ関数に投げるだけでは不十分なので気を付けてください。


パイプラインステートのシリアライズ

Graphics Pipelineにほんのちょっとの変更を加えるだけでPipelineStateを作り直すことになるので、PipelineStateが消費するバイナリサイズは肥大化する可能性があります。
PipelineStateの作成は非常に遅いので、あまり頻繁に作成・破棄することもできません。

そこで有用なのが、Direct3D 12にはPipelineをシリアライズしてID3DBlobで受け取る機能です。
ID3D12PipelineState::GetCachedBlob()で取得できます。
このバイナリをメインメモリやHDDにキャッシュしておくことで、VRAMの浪費を避けることができます。

シリアライズ化したバイナリは、D3D12_GRAPHICS_PIPELINE_STATE_DESCのCachedPSOに指定することで再利用されます。
DESCの中身は同一で、CachedPSOを追加で指定します。
…のはずなのですが、現状ではエラーになってしまいました。
原因は分かりません。

あと気になるのは、GPUの付け替えやドライバのアップデートを行った後にキャッシュが使えるかどうかです。
誰か試してください。

参考までに、DICEのMantle対応での運用方法は以下のようになっているそうです。
・PipelineStateのバイナリサイズは100MB以上
・必要に応じてHDDにキャッシュする
・アプリケーション終了後もバイナリを保持する
・起動時にドライバのメーカー名とバージョンを取得するAPIを使い、変更があればバイナリを破棄し再生性する


まとめ

PipelineStateについて説明しました。
ソースコードはGithubにコミットする予定です。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中