リソース バインディングの概要Resource Binding Overview

DirectX 12 でのリソース バインディングの鍵となるのは、"記述子"、"記述子テーブル"、"記述子ヒープ"、および "ルート署名" の概念です。The key to resource binding in DirectX 12 are the concepts of a descriptor, descriptor tables, descriptor heaps, and a root signature.

リソースとグラフィックス パイプラインResources and the Graphics Pipeline

シェーダー リソース (テクスチャ、定数テーブル、画像、バッファーなど) は、シェーダー パイプラインに直接バインドされるのではなく、"記述子" を介して参照されます。Shader resources (such as textures, constant tables, images, buffers and so on) are not bound directly to the shader pipeline; instead, they are referenced through a descriptor. 記述子は小さなオブジェクトであり、1 つのリソースに関する情報が格納されます。A descriptor is a small object that contains information about one resource.

複数の記述子をまとめたものが、"記述子テーブル" です。Descriptors are grouped together to form descriptor tables. 各記述子テーブルには、ある範囲のリソースの種類に関する情報が格納されます。Each descriptor table stores information about one range of types of resource. リソースにはさまざまな種類があります。There are many different types of resources. 最も一般的なリソースは次のとおりです。The most common resources are:

  • 定数バッファー ビュー (CBV)Constant buffer views (CBVs)
  • 順序指定されていないアクセス ビュー (UAV)Unordered access views (UAVs)
  • シェーダー リソース ビュー (SRV)Shader resource views (SRVs)
  • サンプラーSamplers

SRV、UAV、および CBV の記述子は、同じ記述子テーブルにまとめることができます。SRV, UAV, and CBVs descriptors can be combined into the same descriptor table.

グラフィックスと計算のパイプラインでは、リソースにアクセスするためにインデックスで記述子テーブルの中を参照します。The graphics and compute pipelines gain access to resources by referencing into descriptor tables by index.

記述子テーブルは "記述子ヒープ" に格納されます。Descriptor tables are stored in a descriptor heap. 記述子ヒープには、レンダリングされる 1 つまたは複数のフレームのすべての記述子を (記述子テーブルとして) 入れておくのが理想的です。Descriptor heaps will ideally contain all the descriptors (in descriptor tables) for one or more frames to be rendered. すべてのリソースはユーザー モード ヒープに格納されます。All the resources will be stored in user mode heaps.

もう 1 つの概念として、"ルート署名" があります。Another concept is that of a root signature. ルート署名とはバインドの規則であり、アプリケーションによって定義され、シェーダーによって使用されます。その目的は、シェーダーがアクセスする必要があるリソースを見つけることです。The root signature is a binding convention, defined by the application, that is used by shaders to locate the resources that they need access to. ルート署名には次のものを格納できます。The root signature can store:

  • 記述子ヒープ内の記述子テーブルのインデックス。ここで記述子テーブルのレイアウトが事前定義されます。Indexes to descriptor tables in a descriptor heap, where the layout of the descriptor table has been pre-defined.
  • 定数。アプリで記述子と記述子テーブルを経由することなく、ユーザー定義定数 (ルート定数 ともいいます) を直接シェーダーにバインドできるようにするためです。Constants, so apps can bind user-defined constants (known as root constants) directly to shaders without having to go through descriptors and descriptor tables.
  • ルート署名内に直接存在する、ごく少数の記述子。たとえば、描画ごとに変化する定数バッファー ビュー (CBV) です。これを利用すると、アプリケーションでその記述子を記述子ヒープに入れておく必要がなくなります。A very small number of descriptors directly inside the root signature, such as a constant buffer view (CBV) that changes per draw, thereby saving the application from needing to put those descriptors in a descriptor heap.

つまり、ルート署名を利用すると、描画ごとに変化するデータが少量の場合のパフォーマンス最適化が可能です。In other words, the root signature provides performance optimizations suitable for small amounts of data that change per draw.

バインドに関する Direct3D 12 の設計では、バインドがその他のタスク (たとえばメモリ管理、オブジェクト有効期間の管理、状態追跡、メモリの同期) と切り離されています (「Direct3D 11 とのバインド モデルの違い」を参照)。The Direct3D 12 design for binding separates it from other tasks, such as memory management, object lifetime management, state tracking, and memory synchronization (refer to Differences in the Binding Model from Direct3D 11). Direct3D 12 のバインドは低オーバーヘッドとなるように設計されており、呼び出し頻度の高い API に合わせて最適化されています。Direct3D 12 binding is designed to be low overhead and optimized for the API calls that are made most frequently. また、拡張性の点でも、ローエンドのハードウェアからハイエンドまで対応し、グラフィックス エンジン プログラミングの古いアプローチ (線形の Direct3D 11 パイプライン) にも新しいアプローチ (並列的) にも対応します。It is also scalable across low end to high end hardware, and scalable from older (the more linear Direct3D 11 pipeline) to the newer (more parallel) approaches to graphics engine programming.

リソースの種類とビューResource types and views

リソースの種類は Direct3D 11 と同じです。つまり、次のとおりです。Resource types are the same as Direct3D 11, namely:

  • Texture1D、および Texture1DArrayTexture1D, and Texture1DArray
  • Texture2D、および Texture2DArray、Texture2DMS、Texture2DMSArrayTexture2D, and Texture2DArray, Texture2DMS, Texture2DMSArray
  • Texture3DTexture3D
  • バッファー (型指定、構造化および未加工)Buffers (typed, structured and raw)

リソース ビューは Direct3D 11 と似ていますが少し異なり、頂点とインデックスのバッファー ビューが追加されています。Resource views are similar but slightly different from Direct3D 11, vertex and index buffer views have been added.

  • 定数バッファー ビュー (CBV)Constant buffer view (CBV)
  • 順序指定されていないアクセス ビュー (UAV)Unordered access view (UAV)
  • シェーダー リソース ビュー (SRV)Shader resource view (SRV)
  • サンプラーSamplers
  • レンダー ターゲット ビュー (RTV)Render Target View (RTV)
  • 深度ステンシル ビュー (DSV)Depth Stencil View (DSV)
  • インデックス バッファー ビュー (IBV)Index Buffer View (IBV)
  • 頂点バッファー ビュー (VBV)Vertex Buffer View (VBV)
  • ストリーム出力ビュー (SOV)Stream Output View (SOV)

これらのビューのうち、最初の 4 つだけが実際にシェーダーに認識されます (「シェーダーに認識される記述子ヒープ」と「シェーダーが認識できない記述子ヒープ」を参照)。Only the first four of these views are actually visible to shaders, refer to Shader Visible Descriptor Heaps and Non Shader Visible Descriptor Heaps.

リソース バインディングの制御フローResource Binding Flow of Control

ルート署名、ルート記述子、ルート定数、記述子テーブル、および記述子ヒープのみに注目すると、アプリのレンダリング ロジック フローは次のようになります。Focusing just on root signatures, root descriptors, root constants, descriptor tables, and descriptor heaps, the flow of rendering logic for an app should be similar to the following:

  • 1 つまたは複数のルート署名オブジェクトを作成します (アプリケーションに必要なバインド構成ごとに 1 つ)。Create one or more root signature objects – one for every different binding configuration an application needs.
  • シェーダーとパイプラインの状態を、これらとともに使用されるルート署名オブジェクトを指定して作成します。Create shaders and pipeline state with the root signature objects they will be used with.
  • 記述子ヒープを 1 つ (または、必要に応じて複数) 作成します。この中に、レンダリングの各フレームの SRV、UAV、および CBV 記述子がすべて格納されます。Create one (or, if necessary, more) descriptor heaps that will contain all the SRV, UAV, and CBV descriptors for each frame of rendering.
  • 記述子を指定して記述子ヒープを初期化します。これは、一連の記述子を複数のフレームにまたがって再利用する場合に、可能であれば行います。Initialize the descriptor heap(s) with descriptors where possible for sets of descriptors that will be reused across many frames.
  • レンダリングされるフレームごとに:For each frame to be rendered:
    • コマンド リストごとに:For each command list:
      • 使用される現行ルート署名を設定します (レンダリング時に必要に応じて変更しますが、変更が必要になることはほとんどありません)。Set the current root signature to use (and change if needed during rendering – which is rarely required).
      • ルート署名の定数やルート署名記述子を、新しいビューに合わせて更新します (たとえばワールド/ビュー射影)。Update some root signature’s constants and/or root signature descriptors for the new view (such as world/view projections).
      • 描画するアイテムごとに:For each item to draw:
        • 新しい記述子を記述子ヒープ内に定義します (オブジェクトごとのレンダリングで必要な場合)。Define any new descriptors in descriptor heaps as needed for per-object rendering. シェーダーから認識可能な記述子ヒープについては、アプリが使用する記述子ヒープ領域が実行中のレンダリングで参照されていないことを保証する必要があります。たとえば、レンダリング中に、記述子ヒープを通して領域を連続的に割り当てるときです。For shader-visible descriptor heaps, the app must make sure to use descriptor heap space that isn’t already being referenced by rendering that could be in flight – for example, linearly allocating space through the descriptor heap during rendering.
        • ルート署名を更新し、必要なヒープ記述子の領域へのポインターを反映します。Update the root signature with pointers to the required regions of the descriptor heaps. たとえば、ある記述子テーブルは初期化済みの静的な (不変の) 記述子を指し、別の記述子テーブルでは現在のレンダリングに対して構成されている動的な記述子を指すように設定します。For example, one descriptor table might point to some static (unchanging) descriptors initialized earlier, while another descriptor table might point to some dynamic descriptors configured for the current rendering.
        • ルート署名の定数やルート署名記述子を、アイテムごとのレンダリングに合わせて更新します。Update some root signature’s constants and/or root signature descriptors for per-item rendering.
        • 描画するアイテムのパイプライン状態を設定します (変更が必要な場合のみ)。現在バインドされているルート署名に対応している必要があります。Set the pipeline state for the item to draw (only if change needed), compatible with the currently bound root signature.
        • DrawDraw
      • 繰り返す (次のアイテム)Repeat (next item)
    • 繰り返す (次のコマンド リスト)Repeat (next command list)
    • 厳密には、GPU によるメモリでの作業が完了してそのメモリが使用されなくなったときに、メモリは解放可能になります。Strictly when the GPU has finished with any memory that will no longer be used, it can be released. 記述子からそのメモリへの参照は、その記述子を使用するレンダリングが他に提出されていなければ、削除する必要はありません。Descriptors' references to it do not need to be deleted if additional rendering that uses those descriptors is not submitted. したがって、後続のレンダリングでは記述子ヒープ内の他の領域を参照することも、不要になった記述子を有効な記述子で上書きすることによって記述子ヒープ領域を再利用することもできます。So, subsequent rendering can point to other areas in descriptor heaps, or stale descriptors can be overwritten with valid descriptors to reuse the descriptor heap space.
  • 繰り返す (次のフレーム)Repeat (next frame)

その他の記述子の種類である、レンダー ターゲット ビュー (RTV)、深度ステンシル ビュー (DSV)、インデックス バッファー ビュー (IBV)、頂点バッファー ビュー (VBV)、シェーダー オブジェクト ビュー (SOV) は、管理方法が異なることに注意してください。Note that other descriptor types, render target views (RTVs), depth stencil views (DSV), index buffer views (IBVs), vertex buffer views (VBVs), and shader object views (SOV), are managed differently. コマンド リストへの記録時に、描画ごとにバインドされる一連の記述子のバージョン管理はドライバーが行います (ルート署名のバインドのバージョン管理がハードウェア/ドライバーで行われる方法に似ています)。The driver handles the versioning of the set of descriptors bound for each draw during recording of the command list (similar to how the root signature bindings are versioned by the hardware/driver). これは、シェーダーから認識可能な記述子ヒープの内容とは別のものです。これについてはアプリケーションが、描画ごとに別の記述子を参照するたびにヒープを介して手動で割り当てる必要があります。This is different from the contents of shader-visible descriptor heaps, for which the application must manually allocate through the heap as it references different descriptors between draws. シェーダーから認識可能なヒープの内容のバージョン管理はアプリケーションに任されます。その結果として、アプリケーションではたとえば、変化しない記述子を再利用することや、多数の記述子の静的なセットを使うことや、シェーダー インデックス指定 (たとえばマテリアル ID による) を利用して記述子ヒープからどの記述子を使用するかを選択することや、記述子に応じて異なる手法の組み合わせを使用することができます。Versioning of heap content that is shader-visible is left to the application because it allows applications to do things like reuse descriptors that don’t change, or use large static sets of descriptors and use shader indexing (such as by material ID) to select descriptors to use from the descriptor heap, or use combinations of techniques for different sets of descriptors. ハードウェアは、このような柔軟な取り扱いを他の記述子の種類 (RTV、DSV、IBV、VBV、SOV) について行うことはできません。The hardware isn’t equipped to handle this type of flexibility for the other descriptor types (RTV, DSV, IBV, VBV, SOV).

サブ割り当てSuballocation

Direct3D 12 では、アプリが低水準の制御をメモリ管理に関して行うことができます。In Direct3D 12, the app has low-level control over memory management. 以前のバージョンの Direct3D (これには Direct3D 11 も含まれます) では、割り当てはリソースごとに 1 つです。In earlier versions of Direct3D, including Direct3D 11, there would be one allocation per resource. Direct3D 12 では、アプリで API を使用して大きなメモリ ブロックを割り当てるときに、1 つのオブジェクトに必要な大きさを超えるサイズを指定することができます。In Direct3D 12, the app can use the API to allocate a large block of memory, larger than any single object would need. これが完了した後に、アプリで記述子を作成して、その大きなメモリ ブロックのセクションを指すことができます。After this is done, the app can create descriptors to point to sections of that large memory block. この何をどこに入れるか (小さいブロックを大きいブロックの内側に) を決めるプロセスを、サブ割り当てといいます。This process of deciding what to put where (smaller blocks inside the large block) is known as suballocation. これをアプリで実行できるようにすると、計算とメモリを効率的に利用できます。Enabling the app to do this can yield gains in efficient use of computation and memory. たとえば、リソースの名前を変更することは現代的な方法ではありません。For example, resource renaming is rendered obsolete. この代わりに、アプリでフェンスを使用すると、あるリソースがいつ使用され、いつ使用されないかを特定できます。それには、コマンド リストの実行の中でそのリソースの使用が必要である部分にフェンスを置きます。In place of this, apps can use fences to determine when a particular resource is being used and when it's not by fencing on command list executions where the command list requires the use of that particular resource.

リソースを解放するFreeing Resources

パイプラインにバインドされているメモリを解放するには、GPU によるそのメモリの使用が完了している必要があります。Before any memory that has been bound to the pipeline can be freed, the GPU must be finished with it.

フレーム レンダリングを待つことは、GPU による使用の完了を確証するための最も粗い方法です。Waiting for frame rendering is probably the coarsest way to be certain that the GPU has finished. より粒度を高めるには、ここでもフェンスを使用します。あるコマンドの完了を追跡したい場合は、そのコマンドをコマンド リストに記録するときに、コマンドの直後にフェンスを挿入します。At a finer grain, you can again use fences—when a command is recorded into a command list that you want to track the completion of, insert a fence immediately after it. これで、フェンスを利用してさまざまな同期操作を行うことができます。Then, you can do various synchronization operations with the fence. 新しい作業 (コマンド リスト) を提出し、指定したフェンスが GPU を通過するまで待ちます。フェンスは、その前のすべてが完了していることを示します。あるいは、フェンスが通過したときに CPU イベントを生成するよう設定しておきます (アプリではそれまでの間、スレッドをスリープさせて待ちます)。You submit new work (command lists) that waits until a specified fence has passed on the GPU, which indicates that everything before it is complete, or you can request that a CPU event be raised when the fence has passed (which the app can be waiting on with a sleeping thread). Direct3D 11 では、これは EnqueueSetEvent() でした。In Direct3D 11, this was EnqueueSetEvent().

リソース バインディングResource Binding