Direct3D 11 と Direct3D 12 の間の重要な変更点

Direct3D 12 では、プログラミング モデルが Direct3D 11 から大きく変わりました。 Direct3D 12 を使用すると、従来よりもアプリをハードウェアに近づけることができます。 Direct3D 12 は、ハードウェア寄りになったことで速度と効率が向上しています。 ただし、Direct3D 12 によってアプリの速度と効率が高まった代わりに、Direct3D 11 に比べて開発者の担当するタスクが増加しています。

Direct3D 12 では、再び低水準プログラミングに戻りました。パイプラインの全体的な状態を表すオブジェクト、作業送信用のコマンド リストとバンドル、リソースにアクセスするための記述子ヒープとテーブルなどの新機能が追加されており、ゲームおよびアプリのグラフィカル要素をより詳細に制御できます。

Direct3D 12 によってアプリの速度と効率が高まりましたが、Direct3D 11 に比べて開発者の担当するタスクが増加しています。

明示的同期

  • Direct3D 12 では、CPU と GPU の同期がアプリで明示的に行うべき処理となり、Direct3D 11 のようにランタイムで暗黙的に実行されることはありません。 したがって、Direct3D 12 ではパイプライン ハザードの有無の自動チェックも行われず、こちらもアプリで行うべき処理となります。
  • Direct3D 12 では、データ更新のパイプライン処理がアプリで行うべき処理になりました。 つまり、Direct3D 11 の "Map/Lock-DISCARD" パターンは、Direct3D 12 では手動で実行する必要があります。 Direct3D 11 では、 ID3D11DeviceContext::MapD3D11_MAP_WRITE_DISCARDで呼び出すときに GPU がまだバッファーを使用している場合、ランタイムは古いバッファー データではなく新しいメモリ領域へのポインターを返します。 このため、アプリで新しいバッファーにデータを配置しながら、GPU で古いデータを使用し続けることができます。 アプリ内で追加のメモリ管理を行う必要はなく、古いバッファーは GPU での使用が終われば自動的に再利用または破棄されます。
  • Direct3D 12 では、動的更新 (定数バッファー、動的頂点バッファー、動的テクスチャなど) はすべて、アプリで明示的に制御します。 こうした動的更新には、必須の GPU フェンスまたはバッファー処理も含まれます。 メモリが不要になるまで利用可能な状態に保っておくことは、アプリで行うべき処理です。
  • Direct3D 12 では、インターフェイスの有効期間についてのみ COM 形式の参照カウントが使用されます (そのために、デバイスの有効期間に関連する Direct3D の弱参照モデルを使用します)。 すべてのリソースおよび記述について、メモリの有効期間を適切な時間だけ維持することはアプリ単独で行うべき処理であり、参照カウントは行われません。 Direct3D 11 では、参照カウントを使用してインターフェイスの依存関係の有効期間も管理されます。

物理メモリ常駐状態の管理

Direct3D 12 アプリケーションでは、キュー間、アダプター間、CPU スレッド間での競合状態を防止しなければなりません。 D3D12 では CPU と GPU の同期が行われなくなり、リソースの名前変更やマルチバッファー処理用の簡便なメカニズムもサポートされなくなりました。 特定の処理ユニットでメモリの使用が完了する前に、別の処理ユニットでそのメモリを上書きしないようにするには、フェンスを使用する必要があります。

Direct3D 12 アプリケーションでは、GPU でのデータの読み取り中はそのデータをメモリ内に格納しておく必要があります。 オブジェクトごとに使用されるメモリは、各オブジェクトの作成中は常駐状態になります。 こうしたメソッドを呼び出すアプリケーションでは、フェンスを使用して、解放済みオブジェクトへの GPU のアクセスを防止する必要があります。

必要な同期の種類としては他にリソース障壁があり、これは非常に細かいレベルでリソースとサブリソースの遷移を同期するために使用されます。

Direct3D 12 でのメモリ管理」を参照してください。

パイプライン状態オブジェクト

Direct3D 11 では、大量の独立オブジェクトを使用することでパイプラインの状態操作が可能です。 たとえば、入力アセンブラーの状態、ピクセル シェーダーの状態、ラスタライザーの状態、および出力マージャーの状態をすべて個別に変更できます。 この設計はグラフィックス パイプラインを簡便かつ比較的大まかに表したものですが、多くの場合各種状態は相互に依存しているため、最新のハードウェアの性能が活用されません。 たとえば、多くの GPU では、ピクセル シェーダーと出力マージャーの状態は単一のハードウェア表現にまとめられています。 しかし、Direct3D 11 API ではこれらのパイプライン ステージを個別に設定できるので、ディスプレイ ドライバーは、パイプラインの状態が最終化されるまでその状態の問題を解決できません。つまり、描画時になるまで解決できません。 このスキームによりハードウェア状態の設定が遅れるため、余分なオーバーヘッドが発生し、フレームあたりの最大描画呼び出し回数が減少します。

Direct3D 12 では、作成時に確定される不変のパイプライン状態オブジェクト (PSO) にパイプライン状態の大部分を統合することで、このスキームを解消しました。 この PSO を、ハードウェアとドライバーで、GPU 処理の実行に必要なハードウェアのネイティブ命令と状態にすぐに変換できます。 使用中の PSO を動的に変更することもできますが、このためには、ハードウェアで最小限の事前計算済みの状態をハードウェア レジスタに直接コピーするだけで済みます。ハードウェア状態をその場で計算する必要はありません。 PSO を活用することで、描画呼び出しのオーバーヘッドを大幅に削減して、フレームあたりに実行できる描画呼び出しの数を大きく増やすことができます。 PSO の詳細については、「Direct3D 12 でのグラフィックス パイプラインの状態の管理」を参照してください。

コマンド リストとバンドル

Direct3D 11 では、作業の送信はすべて、GPU に送られる単一のコマンド ストリームに相当するイミディエイト コンテキストで処理されます。 また、ゲームではスケーリングのマルチスレッド化を実現するために、遅延コンテキストも提供されています。 Direct3D 11 の遅延コンテキストはハードウェアへのマッピングが完全ではないため、このコンテキストで行える作業は比較的限られています。

Direct3D 12 では、GPU での特定のワークロードの実行に必要な全情報が含まれるコマンド リストを基にした、作業送信用の新しいモデルが導入されています。 新しいコマンド リストのそれぞれには、使用する PSO、必要なテクスチャとバッファー リソース、全描画呼び出しの引数などの情報が含まれます。 各コマンド リストは自己完結型であり状態を継承しないので、ドライバーでは、必要な全 GPU コマンドをフリースレッド方式で事前に計算できます。 必要な逐次処理は、コマンド キューを介した GPU へのコマンド リストの最終送信のみです。

また、Direct3D 12 では、コマンド リストに加えて、作業の 2 次事前計算として "バンドル" も導入されています。 完全に自己完結型で、通常は構築と送信の後に破棄されるコマンド リストとは異なり、バンドルは一種の再利用可能な状態継承です。 たとえば、ゲームで異なるテクスチャを使用して 2 体のキャラクター モデルを描画する場合、1 つの手法として、同一の描画呼び出しを 2 セット使用してコマンド リストを記録する方法が挙げられます。 ただし、別の手法として、単一のキャラクター モデルを描画するバンドルを 1 つ "記録" してから、異なるリソースを使用してこのバンドルをコマンド リスト上で 2 回 "再生" する方法もあります。 後者の場合、ディスプレイ ドライバーは適切な命令を一度計算するだけでよく、実質的にコマンド リストの作成は低コストの関数呼び出し 2 回分に相当します。

コマンド リストとバンドルの詳細については、Direct3D 12 での作業の送信に関するページを参照してください。

記述子ヒープとテーブル

Direct3D 11 のリソース バインディングは抽象度が高く使いやすいものですが、最新のハードウェア機能をほとんど活用していません。 Direct3D 11 のゲームでは、リソースの "ビュー" オブジェクトを作成してから、パイプラインの各種シェーダー ステージにある複数の "スロット" にそれらのビューをバインドします。 その後、シェーダーが、描画時に固定されるそれらの明示的なバインド スロットからデータを読み取ります。 このモデルでは、ゲームで異なるリソースを使用して描画を行う場合、別のスロットに別のビューをバインドし直してから、描画をもう一度呼び出さなければなりません。 このケースも、最新のハードウェア機能を十分に活用すれば解消できるオーバーヘッドです。

Direct3D 12 では、最新のハードウェアに合わせてバインディングのモデルが変更され、パフォーマンスが大幅に向上しています。 Direct3D 12 ではスタンドアロンのリソース ビューとスロットへの明示的マッピングは不要になり、記述子ヒープが提供されます。このヒープ内で、各ゲームがそれぞれのリソース ビューを作成することになります。 このスキームにより、GPU からハードウェアネイティブのリソース記述 (記述子) をメモリに前もって直接書き込めるメカニズムが実現しました。 特定の描画呼び出しのパイプラインで使用するリソースを宣言するには、ゲームで、記述子ヒープ全体の一部に相当する記述子テーブルを 1 つまたは複数指定します。 記述子ヒープには既に適切なハードウェア固有の記述子データが入力されているので、記述子テーブルの変更は非常に低コストの操作になります。

Direct3D 12 では、記述子ヒープとテーブルによるパフォーマンス強化に加えて、シェーダー内でリソースに対して動的にインデックスを付けられるようになりました。これにより、柔軟性がかつてないほど向上し、新しいレンダリング手法が可能になっています。 一例として、最新の遅延レンダリング エンジンでは、一般的に中間 g バッファーに対して、なんらかのマテリアルまたはオブジェクトの識別子をエンコードします。 Direct3D 11 では、1 つの g バッファーに大量のマテリアルを含めると最終レンダリング パスが大幅に遅くなることがあるため、こうしたエンジンではマテリアルを使用しすぎないように注意する必要があります。 動的にインデックスを付けられるリソースを使用すれば、マテリアルが 1,000 個あるシーンでも、マテリアルが 10 個のシーンと同程度の速さで最終処理できます。

記述子ヒープとテーブルの詳細については、「リソース バインディング」および「バインディング モデルにおける Direct3D 11 との違い」を参照してください。

Direct3D 11 からの移植

Direct3D 11 からの移植は複雑なプロセスであり、「Direct3D 11 から Direct3D 12 に移植する」で説明しています。 また、「Working with Direct3D 11, Direct3D 10 and Direct2D (Direct3D 11、Direct3D 10、および Direct2D での作業)」にある各種オプションも参照してください。

Direct3D 12 とは