リソース管理のベスト プラクティス
マネージド テクスチャ (自動テクスチャ管理とも呼ばれます) は、バージョン 6 以降の DirectX で使用でき、以降のリリースでいくつかのリビジョンと機能強化が行われました。 Direct3D 9 API のリリース時点で、自動リソース管理には、テクスチャ、頂点バッファー、インデックス バッファーのサポートが含まれており、すべて一貫した共有インターフェイスを備えています。 Direct3D リソース マネージャーを使用することで、アプリケーションは紛失したデバイスの状況の処理を大幅に簡素化でき、システムを利用してビデオ メモリ リソースの過剰コミットメントを適切に処理できます。
開発者は、システムの抽象的な性質により、マネージド リソースの使用が困難な場合があります。 リソースの多くの一般的なシナリオはマネージド リソースに適していますが、アンマネージ リソースを使用するとパフォーマンスが向上する場合があります。 この記事では、一般的にリソースを処理するためのベスト プラクティス、マネージド リソースとアンマネージド リソースの動作について説明し、リソースが通常ランタイムとドライバーによって処理される方法について詳しく説明します。
この記事では、次の概念について説明します。
ビデオ メモリ
ビデオ システムでリソースを使用するには、GPU からアクセスできるメモリ内に配置する必要があります。 ローカル ビデオ メモリは GPU に最適なパフォーマンスを提供し、特定のリソース (レンダー ターゲットや深度/ステンシル バッファーなど) はローカル ビデオ メモリに配置する必要があります。 AGP の登場により、GPU はシステム メモリの一部に直接アクセスすることもできます。 AGP アパーチャと呼ばれるこのメモリ領域は、非ローカル ビデオ メモリと呼ばれ、他の目的では使用できません。 ローカル以外のビデオ メモリは、CPU から読み取りおよび書き込みできます。これは通常、ローカル ビデオ メモリへの高パフォーマンス アクセスを持たないため、共有メモリ リソースとして使用するのに最適です。 AGP メモリについて覚えておくべき重要な点は、ローカル ビデオ メモリと同様に、デバイスが失われた状況で無効になり、そこに存在する永続的な資産を復元する必要があることです。
図 1. GPU、CPU、ビデオ RAM、およびシステム RAM の関係
一部の統合ビデオ ソリューションでは、メインメモリがシステムのすべてのコンポーネントでアドレス指定可能な統合メモリ アーキテクチャ (UMA) を使用しています。 Direct3D は、ローカル のビデオ メモリ構成と同じヒントを使用して、アプリケーションを変更することなく UMA をサポートします。 このようなシステムでは、リソースは常にシステム メモリに配置され、ドライバーは、UMA のプロパティとハードウェア実装の特定の動作を利用しながら、より従来のアーキテクチャで行うのと同じようにリソースが動作することを保証する責任があります。
図 2. GPU と CPU は、統合メモリ アーキテクチャのシステム RAM に等しいアクセス権を持ちます
マネージド リソース
リソースの大部分は、POOL_MANAGEDでマネージド リソースとして作成する必要があります。 すべてのリソースがシステム メモリに作成され、必要に応じてビデオ メモリにコピーされます。 紛失したデバイスの状況は、システム メモリのコピーから自動的に処理されます。 すべてのマネージド リソースが一度にビデオ メモリに収まる必要がないため、メモリをオーバーコミットできます。このメモリでは、特定のフレームでレンダリングするために必要なリソースのワーキング セットが小さいビデオ メモリがすべてです。 このバッキング ストア システム メモリの大部分は、時間の経過と同時にディスクにページングされる可能性があるため、失われたビデオ メモリを復元するためにこのデータを再度ページングする必要があるため、リセット操作が遅くなる可能性があることに注意してください。
ランタイムは、リソースが最後に使用されたときにタイムスタンプを保持し、必要なマネージド リソースの読み込みにビデオ メモリの割り当てが失敗すると、LRU 方式でこのタイムスタンプに基づいてリソースが解放されます。 SetPriority の使用はタイムスタンプよりも優先されるため、より一般的に使用されるリソースは優先度の高い値に設定する必要があります。 Direct3D 9.0 では、ドライバーによって管理されるビデオ メモリに関する情報が限られているため、ランタイムでは、割り当てが成功するのに十分な大きさのリージョンを作成するために、いくつかのリソースを削除する必要がある場合があります。 適切な優先順位は、何かが削除され、その後すぐに再び必要になる状況を排除するのに役立ちます。 アプリケーションは EvictManagedResources を呼び出して、すべてのマネージド リソースを強制的に削除することもできます。 ここでも、これは次のフレームに必要なすべてのリソースを再読み込みする時間のかかる操作ですが、ワーキング セットが大幅に変更されるレベル遷移やビデオ メモリの断片化を解消する場合に非常に便利です。
フレーム数は、削除を選択したリソースが現在のフレームの早い段階で使用されたかどうかをランタイムが検出できるように保持されます。これは、ビデオ メモリに収まるよりも多くのリソースが 1 つのフレームで使用されているスラッシング状態を意味します。 これにより、フレームの残りの部分に対して LRU ではなく MRU 形式に切り替える交換ポリシーがトリガーされます。これは、このような条件下では若干パフォーマンスが向上する傾向にあります。 このようなスラッシング動作は、レンダリングパフォーマンスに大きな影響を与えます。 現在のフレームの概念は EndScene に関連付けられているため、マネージド リソースを使用するアプリケーションでは、このメソッドを定期的に呼び出す必要があります。
アプリケーションでマネージド リソースがどのように動作しているかに関する詳細を確認する開発者は、 IDirect3DQuery9 インターフェイスを介して RESOURCEMANAGER イベント クエリを使用できます。 これはデバッグ ランタイムを使用する場合にのみ機能するため、この情報はアプリケーションによって依存できませんが、ランタイムによって管理されるリソースの詳細が提供されます。
リソース マネージャーの動作を理解することは、アプリケーションのチューニングとデバッグを行うときに役立ちますが、アプリケーションを現在のランタイムまたはドライバーの実装の詳細と密接に結び付けすぎないようにすることが重要です。 ドライバーの変更やハードウェアの変更によって動作が大きく変わる可能性があり、Direct3D の将来のバージョンでは、リソース管理が大幅に改善され、高度になります。
ドライバー管理対象リソース
Direct3D ドライバーは、D3DCAPS2_CANMANAGERESOURCEによって示されるドライバーマネージド テクスチャ機能を自由に実装できます。これにより、ドライバーはランタイムの代わりにリソース管理を処理できます。 この機能を実装する (まれな) ドライバーの場合、ドライバーのリソース マネージャーの正確な動作は大きく異なる可能性があり、ドライバーの実装での動作の詳細については、ドライバーのベンダーにお問い合わせください。 または、デバイスの作成時に D3DCREATE_DISABLE_DRIVER_MANAGEMENT を指定することで、ランタイム マネージャーが常に使用されるようにすることもできます。
既定のリソース
マネージド リソースはシンプルで効率的で使いやすいですが、ビデオ メモリを直接使用することが好ましい場合や、必要な場合もあります。 このようなリソースは、POOL_DEFAULT カテゴリに作成されます。 このようなリソースを使用すると、アプリケーションにさらに複雑な問題が発生します。 すべてのPOOL_DEFAULTリソースの紛失したデバイスの状況に対処するにはコードが必要です。データをコピーする際には、パフォーマンスに関する考慮事項を考慮する必要があります。 USAGE_WRITEONLYを指定しないか、レンダー ターゲットをロック可能にしないと、パフォーマンスに重大なペナルティが課される可能性があります。
POOL_DEFAULT リソースで Lock を 呼び出すと、特定のヒント フラグを使用しない限り、POOL_MANAGED リソースを操作するよりも GPU が停止する可能性が高くなります。 リソースの場所によっては、返されるポインターが一時的なシステム メモリ バッファーに存在する可能性があります。または、AGP メモリへのポインターを直接指定することもできます。 一時的なシステム メモリ バッファーの場合は、 Unlock 呼び出し後にデータをビデオ メモリに転送する必要があります。 ビデオ リソースが書き込み専用でない場合は、 ロック中にデータを一時バッファーに転送する必要があります。 AGP メモリ領域の場合、一時的なコピーは回避されますが、必要なキャッシュ動作によってパフォーマンスが低下する可能性があります。
読み取り/書き込みサイクルを誘発する書き込みコーミングのペナルティを回避するために、AGP アパーチャ メモリへのポインターにデータの完全なキャッシュ行を書き込むように注意する必要があり、メモリ領域のシーケンシャル アクセスが推奨されます。 作成時にアプリケーションがデータにランダムにアクセスする必要があり、バッファーにマネージド リソースを使用したくない場合は、代わりにシステム メモリ コピーを操作する必要があります。 データが作成されたら、キャッシュの書き込み結合操作に高いペナルティを支払わないように、結果をロックされたリソース メモリにストリーミングできます。
LOCK_NOOVERWRITE フラグを使用すると、一部のリソースに対して効率的な方法でデータを追加できますが、理想的には、同じリソースに対する複数の Lock 呼び出しと Unlock 呼び出しを回避できます。 さまざまなロック フラグを適切に使用することは、ロックされたメモリを埋めるときにキャッシュに適したデータ アクセス パターンを使用する場合と同様に、最適なパフォーマンスを得るために重要です。
マネージド リソースと既定のリソースの両方を使用する
マネージド リソースとPOOL_DEFAULT リソースの割り当てを混在すると、ビデオ メモリの断片化が発生し、マネージド リソースで使用可能なビデオ メモリのランタイムビューが混乱する可能性があります。 POOL_MANAGED リソースを使用する前に、すべてのPOOL_DEFAULT リソースを作成するか、アンマネージド リソースを割り当てる前 に EvictManagedResources 呼び出しを使用することをお勧めしています。 ビデオ メモリに存在するPOOL_DEFAULTから行われたすべての割り当ては、リソース マネージャーまたは他の目的で使用できないリソースの有効期間にメモリを関連付けます。
以前のバージョンの Direct3D とは異なり、バージョン 9 ランタイムでは、ビデオ メモリの不足に対するアンマネージド リソースの割り当ての失敗をあきらめる前に、一部のマネージド リソースが自動的に削除されますが、これにより、追加の断片化が発生し、リソースが最適でない場所に強制的に配置される可能性があります (たとえば、ローカル以外のビデオ メモリに静的テクスチャがある場合など)。 ここでも、マネージド リソースを使用する前に、必要なすべてのアンマネージド リソースを事前に割り当てることをお勧めします。
動的な既定のリソース
高頻度で生成および更新されるデータは、デバイスの復元時にすべての情報が再作成されるため、バッキング ストアの必要はありません。 このようなデータは通常、POOL_DEFAULTで最適に作成され、USAGE_DYNAMIC ヒントを指定して、ドライバーがリソースを配置するときに最適化の決定を行うことができるようにし、頻繁に更新されることを認識します。 これは通常、リソースをローカル以外のビデオ メモリに配置することを意味するため、通常、GPU がローカル ビデオ メモリよりもアクセスに時間がかかります。 UMA アーキテクチャの場合、ドライバーは、CPU 書き込みアクセスを最適化するために動的リソースの特定の配置を選択する場合があります。
この使用は、ソフトウェア スキニング ソリューションと CPU ベースのパーティクル システムが頂点/インデックス バッファーを満たす場合に一般的です。LOCK_DISCARD フラグを使用すると、リソースが前のフレームから引き続き使用されている場合に、停止が作成されなくなります。 この場合、マネージド リソースを使用すると、システム メモリ バッファーが更新され、ビデオ メモリにコピーされ、置き換えられる前にフレームまたは 2 つのみ使用されます。 ローカル以外のビデオ メモリを使用するシステムの場合、この動的パターンを適切に使用することで、余分なコピーは削除されます。
標準テクスチャはロックできず、 UpdateSurface または UpdateTexture でのみ更新できます。 一部のシステムでは、ロック可能な動的テクスチャをサポートし、LOCK_DISCARD パターンを使用できますが、そのようなリソースを使用する前に、機能ビット (D3DCAPS2_DYNAMICTEXTURES) を確認する必要があります。 高度に動的なテクスチャ (ビデオまたは手続き型) の場合、アプリケーションでは、一致するPOOL_DEFAULTとPOOL_SYSTEMMEMリソースを作成し、 UpdateTexture を使用してビデオ メモリの更新を処理できます。 頻繁かつ部分的な更新の場合は、 UpdateTexture パラダイムの方が適している可能性があります。
動的リソースと同様に、動的送信に大きく依存するシステムを設計する場合は注意が必要です。 ローカル ビデオ メモリの適切な使用率を確保し、限られたバスとメインメモリ帯域幅をより効率的に使用するには、静的リソースをPOOL_MANAGEDに配置する必要があります。 半静的なリソースの場合、ローカル ビデオ メモリへの時折のアップロードのコストは、動的にすることによって生成される一定のバス トラフィックよりもはるかに小さい場合があります。
システム メモリ リソース
POOL_SYSTEMMEMでリソースを作成することもできます。 グラフィックス パイプラインでは使用できませんが、 UpdateSurface または UpdateTexture を使用してPOOL_DEFAULTリソースを更新するためのソースとして使用できます。 ロック動作は単純ですが、前述のいずれかの方法で使用されている場合は、停止が発生する可能性があります。
これらはシステム メモリに存在しますが、POOL_SYSTEMMEMリソースは、デバイス ドライバーでサポートされているのと同じ形式と機能 (最大サイズなど) に制限されます。 POOL_SCRATCH リソースの種類は、ランタイムでサポートされているすべての形式と機能を利用できるシステム メモリ リソースのもう 1 つの形式ですが、デバイスからアクセスすることはできません。 スクラッチ リソースは、主にコンテンツ ツールで使用することを目的としています。
図 3: ビデオ RAM、AGP 開口、およびシステム RAM のメモリ リソース
一般的な推奨事項
リソース管理の技術的な実装の詳細を正しく取得することは、アプリケーションのパフォーマンス目標を達成するために長い道のりになります。 Direct3D にリソースを提示する方法と、データをタイムリーに読み込むアーキテクチャ設計を計画することは、より複雑な作業です。 アプリケーションに対して次の決定を行う場合は、いくつかのベスト プラクティスをお勧めします。
- すべてのリソースを事前に処理します。 リソースの負荷の高い読み込み時間の変換と最適化に依存することは開発時に便利ですが、そうすることで、ユーザーのコンピューターのパフォーマンスに大きな負担がかかります。 事前に処理されたリソースの読み込みが速く、使用が速くなり、高度なオフライン作業を行うオプションが提供されます。
- フレームごとに多くのリソースを作成しないようにします。 必要なドライバーの操作によって CPU と GPU をシリアル化できます。また、関連する操作は、多くの場合、カーネル遷移を必要とするため、負荷が高いです。 作成を複数のフレームに分散するか、リソースを作成または解放せずに再利用します。 レンダリングに最近使用されたリソースをロックまたは解放する前に、複数のフレームを待機するのが理想的です。
- フレームの最後に、すべてのリソース チャネル (つまり、ストリーム ソース、テクスチャ ステージ、および現在のインデックス) のバインドを解除してください。 これにより、リソースに対するダングル参照が削除されてから、リソース マネージャーは実際に使用されなくなったリソースを常駐状態に保ちます。
- テクスチャの場合は、mip-maps で圧縮形式 (DXTn など) を使用し、テクスチャ アトラスを使用することを検討してください。 これにより、帯域幅の要件が大幅に削減され、リソースの全体的なサイズが小さくなり、効率が高くなります。
- ジオメトリの場合は、インデックス付きジオメトリを使用します。これは頂点バッファー リソースの圧縮に役立ち、最新のビデオ ハードウェアは頂点の再利用に合わせて大幅に最適化されています。 プログラム可能な頂点シェーダーを使用することで、頂点情報を圧縮し、頂点の処理中に展開できます。 繰り返しますが、これは帯域幅の要件を減らし、頂点バッファー リソースの効率を高めます。
- リソース管理を過剰に最適化しないようにします。 ドライバー、ハードウェア、およびオペレーティング システムの将来のリビジョンでは、アプリケーションが特に組み合わせに合わせてチューニングされすぎると、互換性の問題が発生する可能性があります。 ほとんどのアプリケーションは CPU にバインドされているため、コストの高い CPU ベースの管理は、一般に、解決するよりも多くのパフォーマンスの問題を引き起こします。
関連トピック