解説 Direct3D12

はじめに

そういえば,あんまり日本語でDirect3D12のことを日本語で解説した記事もないですし,もうすぐCEDECということで,せっかくなので今年の4月に開催になったBuild 2014の”Direct3D 12 API Preview”のプレゼンテーションやGDCの情報,IntelやAMDなどのスライドから色々と新しい概念などを解説していきたいと思います.
※勘違いなどもありますので,間違いなどあればコメントでご指摘ください.
最近は,ゲームエンジンも便利なので直接Direct3D12を扱う機会は少なくなるかもしれませんが,ゲームエンジンのDiretct3D12対応などの場合に知っておくと描画の効率化を考える上で良いんじゃ無いかと思います.
このあと順番に参考文献を紹介していきますが,必ずしも全部目を通す必要がありませんが自分で原典に当たりたい方のために貼っておきます.
そんなわけで,Direct3D12がどういったAPIになるのか知る上で基本となるのが,下記の”Direct3D 12 API Preview”です.この記事で引用するスライド画像はこのスライドの画像になります.
こちらは,動画ですがスライドもダウンロードできます.
Direct3D12 API Preview
http://channel9.msdn.com/Events/Build/2014/3-564
スライド
http://view.officeapps.live.com/op/view.aspx?src=http%3a%2f%2fvideo.ch9.ms%2fsessions%2fbuild%2f2014%2f3-564.pptx

その他に,MicrosoftはGDCでいくつかの発表をしましたが,GDC Vaultの無料エリアにはスライドがないですが,Build 2014にない話題はそちらで触れられてた話題になります.
http://schedule.gdconf.com/session-id/828181
Intelなんかもこのプレゼンテーションをベースにした記事を書いています.要素ごとに分解した記事になってます.

そのほかに,AMDのスライドなんかも参考にしていきたいと思います.

Direct3D12で目指すもの

Direct3D12はいろいろなところですでに言われていますが,CPUのオーバーヘッドの低減を目指しているようです.
よりパフォーマンスを出すためにマルチコアCPUに対してスケーラブルに使える.Direct3D11ではDeferred Contextの採用でマルチスレッド対応をしようとしたが,充分な性能を発揮できなかった.
コンソールライクのAPIでパフォーマンスを引き出しやすいものにする.
Direct3D11のレンダリング機能のスーパーセットになっている(Direct3D11時代のAPIの記述自体に近い.移行しやすい).
なお,GPUそのものの新機能みたいなものはまだ発表されてないので,この記事の話題以外に純粋にGPUの進化による新機能というのはあるのかもしれません.

Direct3D12で出てくる新しい概念

おそらく現時点でわかっているこれら新しい概念を押さえるのがDirect3D12を使うために必要な情報になると思います.それ以外は,まだ情報が出ていないので現時点で何とも言えないと思います.

  • Pipeline State Object (PSO)
  • Descripter Heaps & Table
    • Descripter
  • Command Lists & Command Queue
  • Bundles
  • MultiDraw

これらの概念については順番に解説していきます.

Pipeline State Object(PSO)

Direct3Dのバージョンの歴史の話ですが,Direct3D9からDirect3D10になったときにRenderStateやSamplerState, BlendState, RasterrizerStateなどはここの要素を切り替えていたのをステートオブジェクトの形にしてパフォーマンスの向上をしようとしました.この変更でDirect3D9よりもパフォーマンスの向上をしたわけですが,Direct3D11になってその変更でもAPIのオーバーヘッドというのはなかなか解消できなかったようです.Direct3D12では,ステートオブジェクトからさらに踏み込んだ改良でCPUのオーバーヘッドを削減しようという仕組み入れようと考えているようです.
その仕組みが,Pipeline State ObjectですここでいうPipelineというのはGPUが頂点データなどを受け取って,RenderTargetに描き込むまでのGPUの処理のパイプラインを指します.PSOは,そのGPUパイプラインに向けたステートオブジェクトで,下図のようなものになります.
dx12001
PSOは従来のステートオブジェクトやシェーダオブジェクトなどをひとまとめにしたオブジェクトになります.具体的には以下に分類されます.
d3d12007
ここからわかるのは,「同じ頂点構造」(Input Layout),「同じシェーダの構成」,「同じステート」の描画コール1つのPSOを構築し,バインドすることで個々のステート変更のオーバーヘッドを減らすことを目指すようです.PSOを作って,描画コールのステート変更を最小限にすることで効率化します.Deferred RenderingのG-bufferへの書き込みやShadow mapなどへの書き込みなどは少ないPSOのバリエーションにできると思いますので,効率化し安いのでは無いかと思います.
一方で,デメリットとしては,PSOの中の要素の一部でも違うとそれは別なPSOを作らないと行けない点ですね.
Direct3D12で効率的な描画を行うにはPSOを描画単位で切りかえないようにして,まとめて描画するというのが望ましいと思います.それによりステート変更のオーバーヘッドを低減できるのでは無いかと思います.

Descriptor Heaps & Table

PSOではステートに関する新機能の話をしましたが,ここではリソースのバインドに関する新機能の話題になります.リソースは,テクスチャやコンスタントバッファなど描画コールの単位でバインドされるものが変わることが多いです.
Descriptor Heaps と Descriptor Tableは,これらのリソースをバインドする仕組みを変えるものになります.
まず一番最初に知る必要があるのはDescriptorです.Descriptorの説明のスライドは下記です.
d3d12003
Descriptorはリソースの定義を記述した小さなチャンクデータです.具体的には,リソースがそれがテクスチャなのか,バッファなのかとかどういったフォーマットなのかを記載してあるものです.Direct3DのAPIでいうとShaderResourceViewなどの”View”などに結びつくものです.
Descriptor Heapsは,複数のDescriptorを格納しておくヒープです.具体的にどう使うかは予想ですが,ある1フレームの描画で使用するリソース全部のDescriptor全部をヒープに入れておくことで,描画時に毎度毎度リソースをバインドしないで済むようにするものだと思っています.毎フレーム構築するよりもゲームであれば,レベルごとに作るとかでもいいのかもしれません.個々のDescriptor自体がリソースの実態ではなく,小さなデータにすぎないのでメモリがたくさん使える現在では事前に使用するリソースがすべて把握できてると効率がよいかもしれませんね.
d3d12004
実際の描画時には,ヒープから直接参照するのではなくヒープからテーブルを作るようです.これがDescriptor Tablesです.Direct3D12では描画時に使うリソースをリソースのスロット(t#とかb#とか)に直接セットする以外に, Descriptor Tablesで使用する部分をHeapの中からインデックスとサイズを指定して使用します.
d3d12005
 
Direct3D12では描画のたびにテクスチャやコンスタントバッファをバインドするのではなく,あらかじめそのフレームで使用するリソースのDescriptorをHeapに積んでおき,描画時にはそのHeapから使用するもののTablesを使って参照するという形になります.
Build 2014のスライドではすでにHLSLでの使用例が書かれています.
d3d120089
d3d12011
シェーダはテーブルの中のリソースを使うという形になります.テーブルがどのくらいのサイズなのかなどはよくわかっていないですが,OpenGLのBindless Textureのようなものは目指しているようでした.

Command Lists & Command Queue

Build 2014のスライドではBundlesの方が先に出てきますが,こちらの解説では逆に解説します.
Direct3D12では個々の描画コマンドはCommandListsに積んで,Command Queueに登録して実行されます.Direct3D11ではImmediate ContextやDeferred Contextがあってそこにコマンドを積んで実行していましたが,Direct3D12ではデバイスコンテキストではなくコマンドリストとコマンドキューで実行します.
Direct3D11ではマルチスレッドでコマンドを積む場合には,Deferred Contextを使用していましたが,Direct3D12ではコマンドリストを使います.Deferred Contextはマルチスレッドでコマンドを積めましたが,多くの開発者がパフォーマンス面であまりよい仕組みでは無いということを言ってましたが,この仕組みで改善されるのでは無いかと思います.
Direct3D12ではDeferred Contextと異なりコマンドの積み込みのマルチスレッド処理に制限は無いようです(Deferred ContextではImmediate Contextでできることの一部ができない ).

Bundles

Bundlesは,コマンドリストにいていて描画処理の小さな単位をまとめておける仕組みです.OpenGLのディスプレイリストなどとも近いのかもしれません.
BundlesのイメージはBuild 2014の発表では下図のように紹介されています.黄色の四角の中に描画コマンドが書かれてますが,この黄色四角の単位がBundleです.
dx12002
 
Bundlesでは,従来のデバイスコンテキストでやっていたような描画命令をまとめておくことができます.スタティックなメッシュなどは描画からステートのセットなどの一連の処理はあまりゲーム中で変更がないので一度Bundles生成しておけば,それらを使いまわせばよいですね.Bundlesでは複数の描画コールやPSOの切り替えなども入れて置けます.一方で,一度作ったBundlesの構成を動的にいじることはできなさそうなので,状況に応じてこれは描かないなどの処理を入れたりは難しいかもしれません.
「従来のデバイスコンテキストでやっていたような描画命令をまとめておく」と書きましたが,以下にまとめられています.RenderTargetに対するものやGPUに対するQuery,ストリームアウトプットなどはBundlesには入れられません.
d3d12008
Direct3D12ではマルチスレッドでコマンドを積む以外に,事前にあまり描画処理が動的に流れが変更されないようなものはBundles化しておいて効率化を図るようです.

MultiDraw

MultiDrawはOpenGLにはすでにある描画機能です.MultiDrawはインスタンシング描画に近い機能だと思います.インスタンシング描画では1度の描画コールで複数回描画することができるので描画コールを呼ぶCPUの負荷を下げることができます.MultiDrawでは,同じ頂点構造,同じシェーダを実行する複数の描画単位のものを1度の描画コールで描画する機能です.
具体的な例でいうと,頂点構造が同じ100頂点のモデル,200頂点のモデル,150頂点のモデルのようなものがあった場合に,描画するのに3回描画命令を呼ぶ代わりに一回で3つの描画を行ってしまう仕組みです.一回で描画を行うには,3つの頂点モデルの頂点バッファとインデックスバッファを1つの結合しておく必要があります.結合してあれば,1回の描画命令で描画できるのでインスタンシングに近い効果が得られるものと思います.
Direct3D11の時のインスタンシングのときからConstant BufferやStructuredBufferなどを使えばインスタンス単位の個々のパラメータを格納できますので,まとめての描画は比較的容易にできるんじゃないかと思います.
BundlsとMultiDrawどちらもまとめて描画することを効率化しますが,Built 2014のスライドは下記のように比較して,使うシチュエーションを分けています.
d3d12006
Bundlesは,描画数の動的な変更ができず,MultiDrawはそれが容易です.Bundlesはその中でPSOの切り替えなどができますが,MultiDrawはできません.Bundlesはハードウェアサポートが幅広いですが,MultiDrawはハードウェアのサポートが限定されています(使えないGPUもある).
以上を踏まえて使うケースを分けることになると思います.

まとめ

そんなわけで,駆け足でDirect3D12の新しい概念の中でも重要そうな話を解説してみました.
Direct3D12では,これら新しい機能を利用することでDirect3D11よりもパフォーマンスが出せるようになるわけですが,これらの機能を効果的に使うにはその特徴を押さえた上で描画エンジンの再設計などが必要になると思います.
おそらく再設計のポイントとしては,マルチスレッド対応,PSOやBundlesを活用して描画コマンドのコマンドリストへの登録順序を上手に並び替える(おそらくZソートなどとは別に,PSO単位のソートなどをした方がいい)なども考えないと行けないかもしれませんね.
それから,Direct3D12のPSOの仕組みはDeferred Renderingには有利かもしれないと予想しています.G-buffer書き込みなどは統一化しやすそうですね.