常駐

オブジェクトが "常駐" と見なされるのは、GPU からアクセス可能であるときです。

常駐のバジェット

GPU ではまだページ フォールトがサポートされていないため、アプリケーションではデータを物理メモリにデータをコミットする必要があり、コミットされている間は GPU からアクセスできるようになります。 このプロセスは "何かを常駐にする" と呼ばれており、物理的なシステム メモリと物理的な独立型 (ディスクリート) ビデオ メモリの両方に対して行う必要があります。 D3D12 では、ほとんどの API オブジェクトが、GPU からアクセス可能なある程度の量のメモリをカプセル化します。 この GPU からアクセス可能なメモリは、API オブジェクトの作成時に常駐となり、API オブジェクトが破棄されると常駐が解除されます。

プロセスで使用できる物理メモリの量は、ビデオ メモリ バジェットと呼ばれます。 バジェットは、バックグラウンド プロセスのウェイクアップとスリープのときに著しく変動する可能性があり、ユーザーが別のアプリケーションに切り替えるときに大幅に変動する場合があります。 アプリケーションでは、バジェットが変化したときに通知を受け取ることや、現在のバジェットと現在使用中のメモリの量の両方を問い合わせることができます。 アプリケーションの使用量がバジェット内に収まらない場合は、プロセスが間欠的に停止します。その間に他のアプリケーションを実行できるようにする、あるいは作成 API からエラーを返せるようにするためです。 IDXGIAdapter3 インターフェイスに、この機能に関するメソッドがあります。具体的には、QueryVideoMemoryInfoRegisterVideoMemoryBudgetChangeNotificationEvent です。

アプリケーションの実行に最低限必要になるメモリの量を、予約を使用して示すことをお勧めします。 理想的には、ユーザー指定の "低" グラフィックス設定、またはさらに低いものが、このような予約に適した値です。 予約を設定しておくと、アプリケーションに通常よりも大きなバジェットが付与されることはなくなります。 代わりに、この予約情報を OS カーネルが利用して、メモリ負荷が高い状況の影響をすばやく最小限に抑えることができます。 さらに、アプリケーションがフォアグラウンド アプリケーションではないときは、予約していた量を確実にそのアプリケーションで使用できるとは限りません。

ヒープ リソース

多くの API オブジェクトは GPU でアクセス可能なメモリをカプセル化しますが、ヒープ & リソースは、アプリケーションが物理メモリを使用および管理する最も重要な方法であると予想されます。 ヒープは物理メモリを管理するための最も低いレベルの単位であるため、その常駐プロパティについて、ある程度理解することをお勧めします。

  • ヒープを部分的に常駐にすることはできませんが、予約済みリソースによる回避策が存在します。
  • ヒープのバジェットは、特定のプールの一部として確保する必要があります。 UMA アダプターのプールは 1 つですが、独立型 (ディスクリート) アダプターにはプールが 2 つあります。 カーネルがディスクリート アダプター上のヒープをビデオ メモリからシステム メモリに移すこともできるのは確かですが、これは最後の手段としてのみ行われます。 アプリケーションはカーネルのバジェット超過動作に依存してはなりません。代わりに、適切なバジェット管理に焦点を合わせる必要があります。
  • ヒープを常駐の対象から除外することができ、これによってその内容をディスクにページアウトできます。 しかし、ヒープを破棄するほうが、どのアダプター アーキテクチャでも確実に常駐を解除できる手法です。 D3D12_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORTのMaxGPUVirtualAddressBitsPerProcess フィールドが予算サイズに近いアダプターでは、Evict は常駐を確実に回収しません。
  • ヒープの作成に時間がかかる場合がありますが、これはバックグラウンドのスレッド処理のために最適化されています。 レンダー スレッドのグリッチを避けるため、バックグラウンド スレッドでヒープを作成することをお勧めします。 D3D12 では、複数のスレッドで同時に作成ルーチンを呼び出しても問題ありません。

D3D12 では、リソース モデルにさらに柔軟性と直交性が導入されているため、アプリケーションでの選択肢が増えています。 D3D12 のリソースの種類は、大きく分けてコミット済み、配置済み、予約済みの 3 つがあります。

  • コミット済みリソースでは、リソースとヒープの両方が同時に作成されます。 ヒープは暗黙的なものであり、これに直接アクセスすることはできません。 ヒープのサイズは、リソース全体をヒープ内に収容できるように適切に設定されます。
  • 配置済みリソースでは、ヒープ内のゼロ以外のオフセットにリソースを配置できます。 オフセットのアラインメントは一般的に 64 KB であることが必要ですが、双方向でいくつかの例外が存在します。 MSAA リソースでは 4 MB オフセット アラインメントが必要ですが、小さいテクスチャの場合は 4 KB オフセット アラインメントが可能です。 配置済みリソースを直接別のヒープに再配置したり、再マップしたりすることはできませんが、リソース データを単純にヒープ間で移転することは可能です。 新しい配置済みリソースを別のヒープ内に作成してリソース データをコピーした後は、新しいリソース データの場所に対して新しいリソース記述子を使用する必要があります。
  • 予約済みリソースを使用できるのは、タイル リソース階層 1 以上がアダプターでサポートされる場合のみです。 この方法を利用できるときは、常駐管理に関して最も高度な手法を利用できることになりますが、現時点ではすべてのアダプターでサポートされているわけではありません。 リソース記述子の再生成、部分ミップ レベルの常駐、スパース テクスチャ のシナリオなどを必要とせずに、リソースを再マップできます。予約済みリソースが使用可能な場合でも、すべてのリソースの種類がサポートされるわけではないため、完全に一般的なページベースの常駐マネージャーはまだ実現できません。

常駐の優先順位

Windows 10 Creators Update では、メモリ負荷が理由で一部のリソースを降格する必要がある場合にどのヒープとリソースを優先的に常駐のままにするかについて、開発者が影響を与えることができます。 これは、アプリケーションのパフォーマンスを向上させるのに役立ちます。このときに活用される知識は、ランタイムが API 使用状況から推測することはできないからです。 コミット済みリソースを使う開発方法から、予約済みおよびタイル リソースを使う方法に移行すると、優先順位を指定することが楽に感じられるようになると予想されます。

このような優先順位を適用することは、2 つの動的なメモリ バジェットを管理しながらその 2 つの間でリソースを手動で降格および昇格するのに比べて簡単であるはずです。優先順位の適用は、既にアプリケーションで可能であるからです。 したがって、常駐優先順位 API の設計は大まかなものであり、ヒープとリソースには作成時にそれぞれ妥当な既定の優先順位が割り当てられています。 詳細については、「 ID3D12Device1::SetResidencyPriority 」および 「D3D12_RESIDENCY_PRIORITY 列挙」を参照してください。

優先順位に関して、開発者は次のいずれかを行うことになります。

  • 少数の例外的なヒープの優先順位を上げます。このようなヒープの降格が、自然なアクセス パターンで求められるよりも早く、あるいはより頻繁に行われた場合のパフォーマンスへの影響を適切に軽減するためです。 この方法は、Direct3D 11 や OpenGL などのグラフィックス API から移植されたアプリケーションで活用されることが予想されます。これらの API のリソース管理モデルは Direct3D 12 のものと大幅に異なっているためです。
  • ほぼすべてのヒープの優先順位を、アプリケーション独自のバケット化方式 (アクセス頻度に関するプログラマーの知識に基づく固定、または動的) でオーバーライドします。固定方式は動的方式に比べて管理が簡単ですが、開発の過程で使用パターンが変化すると効果が低くなることがあり、プログラマーの介入が必要になります。 この方法は、Direct3D 12 スタイルのリソース管理を考慮して開発されたアプリケーションで利用されることが予想されます。たとえば、常駐ライブラリを使用するアプリケーションです (特に動的方式)。

既定の優先順位アルゴリズム

アプリケーションで管理を試みるヒープに対して有用な優先順位を指定するには、最初に既定の優先順位アルゴリズムを理解する必要があります。 その理由は、ヒープに特定の優先順位を割り当てる値が、同じメモリについて競合関係にある他の優先ヒープとの相対的な優先順位から導出されるからです。

既定の優先順位を生成するために選ばれた戦略は、ヒープを 2 つのバケットに分類することです。高い優先順位が付けられるヒープは、そうでないヒープに比べて GPU による書き込みの頻度が高いと見なされます。

高優先度バケットの内容は、ヒープとリソースのうち、作成時に設定されたフラグでレンダー ターゲット、深度ステンシル バッファー、または順序指定されていないアクセス ビュー (UAV) であることが指定されているものです。 これらは、 D3D12_RESIDENCY_PRIORITY_HIGHから始まる範囲内の優先度値が割り当てられます。これらのヒープとリソースの中でさらに優先順位を付けるために、優先度の最も低い 16 ビットは、ヒープまたはリソースのサイズを 10 MB で割って設定されます (非常に大きなヒープの場合は飽和から0xFFFF)。 この追加の優先順位付けでは、より大きなヒープとリソースが優先されます。

優先度の低いバケットには、他のすべてのヒープとリソースが含まれており、優先度の値 D3D12_RESIDENCY_PRIORITY_NORMALが割り当てられます。 これらのヒープとリソースの間では、これ以上の優先順位付けは試行されません。

常駐管理のプログラミング

単純なアプリケーションでは、単にコミット済みリソースを作成し、これをメモリ不足エラーが発生するまで続けるということも考えられます。 エラーが発生した場合は、他のコミット済みリソースまたは API オブジェクトを破棄すると、さらにリソースを作成できるようになります。 しかし、単純なアプリケーションであっても、負のバジェット変更を監視して未使用の API オブジェクトを破棄することを、フレームごとに 1 回程度行うことを強くお勧めします。

常駐管理の設計は、アダプター アーキテクチャに合わせて最適化を試みるときや、常駐の優先順位を採用するときに、より複雑になります。 個別のメモリの 2 つのプールを個別に予算化して管理する方が、1 つだけを管理するよりも複雑になります。また、使用パターンが進化した場合、大規模に固定優先度を割り当てることは保守上の負担になる可能性があります。 システム メモリへのテクスチャのオーバーフローによって、複雑さがさらに増します。これは、システム メモリ内の不適切なリソースがフレーム レートに深刻な影響を与える可能性があるためです。 また、リソースがより高い GPU 帯域幅の恩恵を受けるか、それとも GPU 帯域幅が低くても許容されるかを特定するのに役立つ、シンプルな機能はありません。

さらに複雑な設計では、現在のアダプターの機能を照会して特定することになります。 この情報は、 D3D12_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORTD3D12_FEATURE_DATA_ARCHITECTURED3D12_TILED_RESOURCES_TIERD3D12_RESOURCE_HEAP_TIERで入手できます。

1 つのアプリケーションの部分ごとに、最終的には異なる手法を使うことになる可能性があります。 たとえば、大きなテクスチャやあまり実行されないコード パスにはコミット済みリソースを使用する一方で、多くのテクスチャにはストリーミング プロパティを指定し、一般的な配置済みリソース手法を使用します。

ID3D12Heap

メモリ管理