Direct3D 12 レンダー パス

レンダー パス機能は、Windows 10 Version 1809 (10.0;ビルド 17763)、Direct3D 12レンダー パスの概念が導入されています。 レンダー パスは、コマンド リストに記録するコマンドのサブセットで構成されます。

各レンダー パスの開始位置と終了位置を宣言するには、 ID3D12GraphicsCommandList4::BeginRenderPassEndRenderPass の呼び出し内で、そのパスに属するコマンドを入れ子にします。 したがって、すべてのコマンド リストに 0 個、1 つ以上のレンダー パスが含まれます。

シナリオ

レンダリング パスは、他の手法の中でもTile-Based遅延レンダリング (TBDR) に基づいている場合に、レンダラーのパフォーマンスを向上させることができます。 具体的には、この手法は、リソース レンダリングの順序要件とデータの依存関係をより適切に識別できるようにアプリケーションを有効にすることで、オフチップ メモリとの間のメモリ トラフィックを減らすことで、レンダラーの GPU 効率を向上させるのに役立ちます。

レンダリング パス機能を活用するために明示的に記述されたディスプレイ ドライバーは、最適な結果を提供します。 ただし、レンダー パス API は、既存のドライバーでも実行できます (ただし、必ずしもパフォーマンスが向上するとは限りません)。

これらは、レンダリング パスが価値を提供するように設計されているシナリオです。

アプリケーションで、Tile-Based Deferred Rendering (TBDR) アーキテクチャ上のメモリをメインするリソースの不要な読み込み/格納を回避できるようにする

レンダリング パスの価値提案の 1 つは、レンダリング操作のセットに対するアプリケーションのデータ依存関係を示す中央の場所を提供することです。 これらのデータ依存関係により、ディスプレイ ドライバーはバインド/バリア時にこのデータを検査し、メモリとの間のリソースの読み込み/格納を最小限に抑える命令を発行メイン。

TBDR アーキテクチャで、レンダー パス間でオンチップ キャッシュ内のリソースを日和見的に永続できるようにする (別のコマンド リストでも)

Note

具体的には、このシナリオは、複数のコマンド リスト間で同じレンダー ターゲットに書き込む場合に限定されます。

一般的なレンダリング パターンは、レンダリング コマンドが並列で生成されている場合でも、アプリケーションが複数のコマンド リスト間で同じレンダー ターゲットに順次レンダリングすることです。 このシナリオでレンダー パスを使用すると、ディスプレイ ドライバーがコマンド リスト境界でメモリメインへのフラッシュを回避できるため、これらのパスをこのような方法で組み合わせることができます (アプリケーションは、すぐに成功するコマンド リストでレンダリングを再開することがわかっているため)。

アプリケーションの責任

レンダー パス機能を使用しても、Direct3D 12 ランタイムもディスプレイ ドライバーも、読み込みとストアを再注文/回避する機会を減らす責任を負いません。 レンダー パス機能を正しく活用するために、アプリケーションにはこれらの責任があります。

  • 操作のデータ/順序付け依存関係を適切に識別します。
  • フラッシュを最小限に抑える方法で送信を注文します (そのため、_PRESERVE フラグの使用 を最小限に 抑えます)。
  • リソース バリアを正しく利用し、リソースの状態を追跡します。
  • 不要なコピー/クリアは避けてください。 これらを識別するために、 PIX on Windows ツールからの自動パフォーマンス警告を利用できます。

レンダー パス機能の使用

レンダー パスとは

レンダー パスは、これらの要素によって定義されます。

  • レンダリング パスの期間中に固定される一連の出力バインディング。 これらのバインドは、1 つ以上のレンダー ターゲット ビュー (RTV)、深度ステンシル ビュー (DSV) に対して行われます。
  • 出力バインディングのセットを対象とする GPU 操作の一覧。
  • レンダー パスの対象となるすべての出力バインディングの読み込み/格納の依存関係を記述するメタデータ。

出力バインドを宣言する

レンダー パスの開始時に、レンダー ターゲットや深度/ステンシル バッファーへのバインドを宣言します。 レンダー ターゲットにバインドすることは省略可能で、深度/ステンシル バッファーにバインドする場合は省略可能です。 ただし、2 つのうち少なくとも 1 つにバインドする必要があります。次のコード例では、両方にバインドします。

これらのバインドは 、ID3D12GraphicsCommandList4::BeginRenderPass の呼び出しで宣言します。

void render_passes(::ID3D12GraphicsCommandList4 * pIGCL4,
    D3D12_CPU_DESCRIPTOR_HANDLE const& rtvCPUDescriptorHandle,
    D3D12_CPU_DESCRIPTOR_HANDLE const& dsvCPUDescriptorHandle)
{
    const float clearColor4[]{ 0.f, 0.f, 0.f, 0.f };
    CD3DX12_CLEAR_VALUE clearValue{ DXGI_FORMAT_R32G32B32_FLOAT, clearColor4 };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessClear{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, { clearValue } };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessPreserve{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {} };
    D3D12_RENDER_PASS_RENDER_TARGET_DESC renderPassRenderTargetDesc{ rtvCPUDescriptorHandle, renderPassBeginningAccessClear, renderPassEndingAccessPreserve };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessNoAccess{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessNoAccess{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_DEPTH_STENCIL_DESC renderPassDepthStencilDesc{ dsvCPUDescriptorHandle, renderPassBeginningAccessNoAccess, renderPassBeginningAccessNoAccess, renderPassEndingAccessNoAccess, renderPassEndingAccessNoAccess };

    pIGCL4->BeginRenderPass(1, &renderPassRenderTargetDesc, &renderPassDepthStencilDesc, D3D12_RENDER_PASS_FLAG_NONE);
    // Record command list.
    pIGCL4->EndRenderPass();
    // Begin/End further render passes and then execute the command list(s).
}

D3D12_RENDER_PASS_RENDER_TARGET_DESC構造体の最初のフィールドを、1 つ以上のレンダー ターゲット ビュー (RTV) に対応する CPU 記述子ハンドルに設定します。 同様に、 D3D12_RENDER_PASS_DEPTH_STENCIL_DESC には、深度ステンシル ビュー (DSV) に対応する CPU 記述子ハンドルが含まれています。 これらの CPU 記述子ハンドルは、 ID3D12GraphicsCommandList::OMSetRenderTargets に渡すのと同じです。 また、OMSetRenderTargets と同様に、BEGINRenderPass の呼び出し時に、CPU 記述子はそれぞれの (CPU 記述子) ヒープからスナップされます

RTV と DSV は、レンダー パスに継承されません。 むしろ、設定する必要があります。 BeginRenderPass で宣言された RTV と DSV もコマンド リストに反映されません。 代わりに、レンダリング パスの後に未定義の状態になります。

パスとワークロードをレンダリングする

レンダー パスを入れ子にすることはできません。また、レンダー パスを複数のコマンド リストにまたがって配置することはできません (1 つのコマンド リストへの記録中に開始および終了する必要があります)。 レンダリング パスの効率的なマルチスレッド生成を可能にするように設計された最適化については、以下の 「レンダー パス フラグ」セクションで説明します。

レンダー パス内から行う書き込みは、後続のレンダー パスまで読み取ることはできません。 これは、レンダリング パス内からいくつかの種類のバリアを排除します。たとえば、 RENDER_TARGETから現在 バインドされているレンダー ターゲットの SHADER_RESOURCE へのバリアなどです。 詳細については、以下の「 パスとリソース バリアをレンダリングする」セクションを参照してください。

先ほど説明した書き込み読み取り制約の 1 つの例外には、深度テストとレンダー ターゲット ブレンドの一環として発生する暗黙的な読み取りが含まれます。 そのため、これらの API はレンダー パス内では許可されません (コア ランタイムは、記録中にいずれかの API が呼び出された場合にコマンド リストを削除します)。

レンダー パスとリソース バリア

同じレンダー パス内で発生した書き込みを読み取ったり使用したりすることはできません。 特定のバリアは、この制約に準拠していません。たとえば、現在バインドされているレンダー ターゲットの D3D12_RESOURCE_STATE_RENDER_TARGET から *_SHADER_RESOURCE までです (デバッグ レイヤーはその効果に対してエラーになります)。 ただし、現在のレンダー パス の外部 に書き込まれたレンダー ターゲット上の同じバリアは準拠しています。これは、書き込みが現在のレンダー パスの開始前に完了するためです。 ディスプレイ ドライバーがこの点で行うことができる特定の最適化について知ることでメリットが得られる場合があります。 準拠したワークロードの場合、ディスプレイ ドライバーは、レンダー パスで検出されたバリアをレンダー パスの先頭に移動する場合があります。 そこで、それらは合体することができます (並べて表示/ビン分割操作に干渉することはありません)。 これは、現在のレンダー パスが開始される前にすべての書き込みが完了している場合に有効な最適化です。

より完全なドライバー最適化の例を次に示します。これは、リソースのバインド方法に基づいてオンデマンドでバリアを実行する、Direct3D 12スタイルのリソース バインド設計を備えたレンダリング エンジンがあることを前提としています。 フレームの末尾 (次のフレームで使用される) に向かって順序指定されていないアクセス ビュー (UAV) に書き込むとき、エンジンは、フレームの終了時にリソースを D3D12_RESOURCE_STATE_UNORDERED_ACCESS 状態のままにすることがあります。 次のフレームでは、エンジンがリソースをシェーダー リソース ビュー (SRV) としてバインドすると、リソースが正しい状態ではなく、 D3D12_RESOURCE_STATE_UNORDERED_ACCESS から D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCEへのバリアが発行されます。 そのバリアがレンダー パス内で発生した場合、ディスプレイ ドライバーは、すべての書き込みが現在のレンダー パスの 外部 で既に発生していることを前提として正当化され、その結果 、ディスプレイ ドライバーはレンダリング パスの開始までバリアを 移動 する可能性があります。 ここでも、このセクションと最後に説明されている書き込み読み取り制約にコードが準拠している限り、これは有効です。

準拠バリアの例を次に示します。

  • D3D12_RESOURCE_STATE_INDIRECT_ARGUMENTD3D12_RESOURCE_STATE_UNORDERED_ACCESSします。
  • *_SHADER_RESOURCE にD3D12_RESOURCE_STATE_COPY_DESTします

非準拠バリアの例を次に示します。

  • 現在バインドされている RTV/DSV の読み取り状態にD3D12_RESOURCE_STATE_RENDER_TARGETします。
  • 現在バインドされている RTV/DSV の読み取り状態にD3D12_RESOURCE_STATE_DEPTH_WRITEします。
  • 任意のエイリアシング バリア。
  • 順序なしアクセス ビュー (UAV) バリア。 

リソース アクセス宣言

BeginRenderPass 時刻に、そのパス内で RTV や DSV として機能するすべてのリソースを宣言する場合は、開始および終了のアクセス特性も指定する必要があります。 上記の 「出力バインドを宣言 する」セクションのコード例でわかるように、 D3D12_RENDER_PASS_RENDER_TARGET_DESC 構造と D3D12_RENDER_PASS_DEPTH_STENCIL_DESC 構造体を使用してこれを行います。

詳細については、 D3D12_RENDER_PASS_BEGINNING_ACCESS および D3D12_RENDER_PASS_ENDING_ACCESS 構造体、 およびD3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE および D3D12_RENDER_PASS_ENDING_ACCESS_TYPE 列挙体を参照してください。

レンダー パス フラグ

BeginRenderPass に渡される最後のパラメーターは、レンダー パス フラグ (D3D12_RENDER_PASS_FLAGS 列挙体の値) です。

enum D3D12_RENDER_PASS_FLAGS
{
    D3D12_RENDER_PASS_FLAG_NONE = 0,
    D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES = 0x1,
    D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS = 0x2,
    D3D12_RENDER_PASS_FLAG_RESUMING_PASS = 0x4
};

レンダー パス内での UAV 書き込み

順序なしアクセス ビュー (UAV) の書き込みはレンダー パス内で許可されますが、D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITESを指定してレンダリング パス内で UAV 書き込みを発行 し、必要に応じてディスプレイ ドライバーがタイリングをオプトアウトできることを具体的に示す必要があります。

UAV アクセスは、上記の書き込み読み取り制約に従う必要があります (レンダー パスの書き込みでは、後続のレンダー パスまで読み取りは無効です)。 UAV バリアは、レンダー パス内では許可されません。

UAV バインディング (ルート テーブルまたはルート記述子を使用) は、レンダー パスに継承され、レンダー パスから伝達されます。

Suspending-passes および resuming-passes

レンダー パス全体を、中断パスまたは再開パスとして指定できます。 中断後の再開ペアは、パス間で同じビュー/アクセス フラグを持つ必要があります。また、中断するレンダー パスと再開レンダー パスの間に、介入する GPU 操作 (描画、ディスパッチ、破棄、クリア、コピー、更新タイル マッピング、書き込みバッファー イミディエイト、クエリ、クエリ解決など) がない場合があります。

目的のユース ケースはマルチスレッド レンダリングです。たとえば、4 つのコマンド リスト (それぞれ独自のレンダー パスを持つ) は、同じレンダー ターゲットをターゲットにすることができます。 コマンド リスト間でレンダー パスが中断または再開される場合、コマンド リストは ID3D12CommandQueue::ExecuteCommandLists と同じ呼び出しで実行する必要があります。

レンダー パスは、再開と中断の両方を行うことができます。 先ほど示したマルチスレッドの例では、コマンド リスト 2 と 3 はそれぞれ 1 と 2 から再開されます。 同時に、2 と 3 はそれぞれ 3 と 4 に中断されます。

レンダー パスのクエリ機能のサポート

ID3D12Device::CheckFeatureSupport を呼び出して、デバイス ドライバーやハードウェアがレンダー パスを効率的にサポートする範囲を照会できます。

D3D12_RENDER_PASS_TIER get_render_passes_tier(::ID3D12Device * pIDevice)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupport{};
    winrt::check_hresult(
        pIDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupport, sizeof(featureSupport))
    );
    return featureSupport.RenderPassesTier;
}
...
    D3D12_RENDER_PASS_TIER renderPassesTier{ get_render_passes_tier(pIDevice) };

ランタイムのマッピング ロジックにより、render は常に関数を渡します。 ただし、機能のサポートによっては、常に特典が提供されるとは限りません。 上記のコード例と同様のコードを使用して、レンダリング パスとしてコマンドを発行する価値があるかどうかを判断し、それが間違いなく利点ではない場合 (つまり、ランタイムが既存の API サーフェスにマッピングされている場合) を判断できます。 D3D11On12) を使用している場合は、このチェックの実行が特に重要です。

サポートの 3 つの層の説明については、 D3D12_RENDER_PASS_TIER 列挙を参照してください。