リソース管理のベスト プラクティス

XNA デベロッパー コネクション (XDC)、ソフトウェア デザイン エンジニア

XNA デベロッパー コネクション (XDC)

2005 年 6 月

管理下のテクスチャーは、自動テクスチャー管理とも呼ばれ、DirectX ではバージョン 6 から使用されており、その後のリリースで修正され、向上してきました。Direct3D 9 API のリリース時点で、自動リソース管理ではテクスチャー、頂点バッファー、およびインデックス バッファーがサポートされ、そのすべてで共通のインターフェイスが使用されています。Direct3D のリソース マネージャーを使用することにより、アプリケーションではデバイスの消失状態の処理が著しく単純になり、ビデオ メモリー リソースのある程度の量の過剰割り当ての処理をシステムに頼ることができます。

システムにはある程度抽象的な性質があるので、開発者がマネージ リソースを使用する際に問題が生じる場合があります。リソースの一般的なシナリオの多くはマネージ リソースに適していますが、非マネージ リソースを使用したほうが効率が良い場合もあります。ここでは、一般的なリソースの取り扱いのベスト プラクティスについて、マネージ リソースと非マネージ リソースの動作について説明し、ランタイムおよびドライバーによるリソースの典型的な取り扱い方法の詳細についても説明します。

この技術関連記事では、以下について説明します。

  • ビデオ メモリー
  • マネージ リソース
  • ドライバー マネージ リソース
  • 既定のリソース
  • システム メモリー リソース
  • 一般的な推奨事項

ビデオ メモリー

ビデオ システムでリソースを使用できるようにするには、GPU にアクセスできるメモリーに配置する必要があります。ビデオ メモリーをローカルに配置すると、GPU の最適なパフォーマンスが得られますが、一定のリソース (レンダー ターゲット、深度バッファー、ステンシル バッファーなど) をローカル ビデオ メモリーに配置する必要があります。AGP の出現により、GPU はシステム メモリーの一部に直接アクセスすることもできます。このメモリー領域は AGP アパーチュアや非ローカル ビデオ メモリーとも呼ばれ、その他の目的では使用できません。非ローカル ビデオ メモリーは CPU で読み取りおよび書き込みを行うことができます。CPU での読み取りおよび書き込みは通常、ローカル ビデオ メモリーへのアクセスのパフォーマンスが高くないので、非ローカル ビデオ メモリーは共有メモリー リソースとして使用するのに最適です。AGP メモリーについて主に注意することは、ローカル ビデオ メモリー同様、デバイスの消失状態では無効になり、AGP メモリーに配置した永続的なアセットは復元する必要があるということです。

図形 1.  GPU、CPU、ビデオ RAM、およびシステム RAM の関係

Bb147334.ManagingResources1(ja-jp,VS.85).gif

統合されたビデオ ソリューションの中には、ユニファイド メモリー アーキテクチャ (UMA) を使用しているものがあります。この場合、システムのすべてのコンポーネントがメイン メモリーをアドレス指定できます。Direct3D では、ローカル ビデオ メモリーの構成と同じヒントを利用して、アプリケーションを変更することなく UMA をサポートします。こうしたシステムでは、リソースは常にシステム メモリーに配置され、UMA のプロパティおよびハードウェア実装の固有の動作を活用する一方で、ドライバーはリソースがより従来型のアーキテクチャでの動作に近い働きをさせる役割をしています。

図形 2.  ユニファイド メモリー アーキテクチャでは GPU および CPU にシステム RAM への同等のアクセス権がある

Bb147334.ManagingResources2(ja-jp,VS.85).gif

マネージ リソース

大部分のリソースは、POOL_MANAGED のマネージ リソースとして作成する必要があります。すべてのリソースは、システム メモリーに作成してから、必要に応じてビデオ メモリーにコピーします。デバイスの消失状態は、システム メモリー コピーから自動的に処理されます。すべてのマネージ リソースを同時にビデオ メモリーに収める必要があるわけではないため、リソースの比較的小規模なビデオ メモリー ワーキング セットが一定のフレームでレンダリングする必要があるもののみの場合、メモリーに過剰割り当てを行うことができます。このバッキング ストア システム メモリーのほとんどは、ディスクに何度もページ アウトされる可能性が高いことに注意してください。これは、失われたビデオ メモリーを復元するためにこのデータをページ インする必要があるため、Reset 操作が遅くなる可能性があるためです。

ランタイムは、最後にリソースが使用された時刻のタイムスタンプを保持しており、必要なマネージ リソースの読み込みでビデオ メモリーの割り当てが失敗した場合、このタイムスタンプに基づいて LRU 方式でリソースを解放します。SetPriority を使用する場合はタイムスタンプに優先されるので、より一般的に使用されるリソースには、優先順位の値を高く設定する必要があります。Direct3D 9.0 では、ドライバーが管理するビデオ メモリーに関する情報は限られているので、割り当てを成功させるために、ランタイムではいくつかのリソースを破棄して十分な大きさの領域を作成する必要がある場合があります。優先順位を適切に指定することで、破棄された後にすぐ必要となるものが出てくる状況を回避するのに役立ちます。アプリケーションでは、すべてのマネージ リソースを強制的に削除するために、EvictManagedResources も呼び出すことができます。ここでも、次のフレームで必要となるリソースをすべて再読み込みする操作には時間がかかる可能性がありますが、ワーキング セットが大幅に変更される場合のレベルの移行や、ビデオ メモリーの断片化の除去には非常に有効です。

破棄すると選択したリソースが現在のフレームの早い段階で使用されたかどうかをランタイムが検出できるように、フレーム カウントも保持されています。これは、ビデオ メモリーに収まるよりも多くのリソースが、1 つのフレームで使用されているスラッシングの状態を示します。この場合、フレームの残りに対する置換ポリシーは LRU ではなく MRU 方式に切り替わります。こうした状況では、MRU 方式のほうが多少効率がよくなる傾向があるためです。こうしたスラッシングの動作は、レンダリングのパフォーマンスに非常に大きな影響を与えます。現在のフレームの概念は EndScene に関連付けられているので、マネージ リソースを使用するアプリケーションはすべて、このメソッドを定期的に呼び出す必要があることに注意してください。

アプリケーションでマネージ リソースがどのように動作しているかについて、より多くの情報を必要とする開発者は、IDirect3DQuery9 インターフェイスを使用して RESOURCEMANAGER イベント クエリを使用できます。これは、デバッグ ランタイムを使用する際にのみ動作するので、アプリケーションはこの情報に依存することはできませんが、ランタイムに管理されるリソースについて詳細情報を得ることができます。

リソース マネージャーの動作について理解すると、アプリケーションの微調整やデバッグの際に役立ちますが、アプリケーションを現在のランタイムまたはドライバーの実装の詳細に強く結び付け過ぎないことが重要です。ドライバーのリビジョンまたはハードウェアの変更により、動作は非常に大きく変更されます。また、Direct3D の今後のバージョンでは、リソース管理が大幅に向上し、洗練されることになります。

ドライバー マネージ リソース

Direct3D のドライバーは、D3DCAPS2_CANMANAGERESOURCE で示されるドライバー管理下のテクスチャーの機能を自由に実装できます。これにより、ランタイムではなくドライバーでリソース管理を処理できます。この機能を実装する (数少ない) ドライバーでは、ドライバーのリソース マネージャーの動作は厳密には大きく異なる場合があるので、そのドライバーの実装でどのように動作するかの詳細については、ドライバーのベンダーに問い合せる必要があります。または、デバイスの作成時に D3DCREATE_DISABLE_DRIVER_MANAGEMENT を指定して、ランタイム マネージャーを常に使用することもできます。

既定のリソース

マネージ リソースはシンプルかつ効率的で、使いやすいのですが、ビデオ メモリーを直接使用することが推奨される、または要求される場合もあります。このようなリソースは、POOL_DEFAULT カテゴリで作成されます。こうしたリソースを使用すると、アプリケーションがより複雑になります。コードでは、すべての POOL_DEFAULT リソースでデバイスの消失状態に対応する必要があり、リソースにデータをコピーする際には、パフォーマンスに関する考慮事項を念頭に入れる必要があります。USAGE_WRITEONLY の指定に失敗したり、レンダー ターゲットをロック可能にすると、深刻なパフォーマンス低下をもたらす可能性もあります。

POOL_DEFAULT リソースに対して Lock を呼び出すと、特定のヒント フラグを使用しないかぎり、GPU が POOL_MANAGED リソースを操作するより、ストールを引き起こす可能性が高くなります。リソースの場所によっては、ポインターが一時システム メモリー バッファーへ返されたり、直接 AGP メモリーへのポインターになったりする可能性があります。一時システム メモリー バッファーへ返された場合、Unlock の呼び出しの後にビデオ メモリーにデータを移す必要があります。ビデオ リソースが書き込み専用でない場合、Lock 中はデータを一時バッファーに移す必要があります。AGP メモリー領域の場合、一時コピーは回避されますが、必要なキャッシュ動作のパフォーマンスが低下することがあります。

書き込み結合のペナルティを回避するため、AGP アパーチュア メモリーへのポインターへのデータのフル キャッシュ ラインの書き込みには注意が必要です。書き込み結合は読み取り/書き込みサイクルを発生させ、メモリー領域の順次アクセスが推奨されます。アプリケーションで、データの作成時にそのデータへのランダム アクセスが必要で、バッファーに対してマネージ リソースを使用しない場合、代わりにシステム メモリーのコピーを使用する必要があります。データの作成後は、その結果をロックされたリソース メモリーにストリームして、キャッシュの書き込み結合操作の高いペナルティを回避できます。

一部のリソースでは、LOCK_NOOVERWRITE フラグを使用して効率的にデータを追加できますが、理想的には、同じリソースに対して Lock および Unlock を複数回呼び出すことを回避できます。パフォーマンスを最適化するには、ロックされたメモリーを埋め込む際にキャッシュに適したデータ アクセスのパターンを使用し、さまざまな lock フラグを適切に使用することが重要です。

マネージ リソースと既定のリソースの両方の使用

マネージ リソースと POOL_DEFAULT リソースを取り混ぜて割り当てると、ビデオ メモリーが断片化し、マネージ リソースで使用可能なランタイムのビデオ メモリーのビューが混乱することがあります。理想的には、すべての POOL_DEFAULT リソースを作成してから POOL_MANAGED リソースを使用できるようにするか、非マネージ リソースを割り当てる前に EvictManagedResources 呼び出しを使用する必要があります。POOL_DEFAULT で作成されたビデオ メモリー内のすべての割り当ては、リソース マネージャーで使用できず、他の目的では使用できないリソースが存続するかぎり、メモリーを占有し続けることに注意してください。

Direct3D の以前のバージョンとは異なり、バージョン 9 のランタイムでは、ビデオ メモリーが不足した場合に、失敗した非マネージ リソースの割り当てを放棄する前にいくつかのマネージ リソースを自動的に破棄しますが、これは潜在的に、断片化を進め、さらにはリソースを最適でない場所 (たとえば、スタティック テクスチャーを非ローカル ビデオ メモリーに配置するなど) に強制的に移動する可能性があります。ここでも、前もって必要な非マネージ リソースをすべて割り当ててから、マネージ リソースを使用することが最適です。

動的な既定のリソース

頻繁に生成され、更新されるデータは、デバイスの復元時にすべての情報が再作成されるので、バッキング ストアの必要はありません。こうしたデータは一般的に、頻繁に更新されることを踏まえて、リソースを配置する際にドライバーで最適な判断ができるように、POOL_DEFAULT で作成し、USAGE_DYNAMIC ヒントを指定するのが最適です。これは通常、リソースを非ローカル ビデオ メモリーに配置するということです。したがって多くの場合、ローカル ビデオ メモリーよりも GPU のアクセス速度がかなり遅くなります。UMA アーキテクチャでは、CPU の書き込みアクセスを最適化するために、動的リソースについてはドライバーで特定の配置を選択することがあります。

この方法は、頂点バッファーとインデックス バッファーを入力するソフトウェア スキニング ソリューションおよび CPU ベースのパーティクル システムで一般的で、LOCK_DISCARD フラグを使用することにより、前のフレームでリソースがまだ使用されている場合でもストールが作成されなくなります。この場合にマネージ リソースを使用すると、システム メモリー バッファーが更新され、ビデオ メモリーにコピーされて、1 つか 2 つのフレームで使用されると置き換えられてしまいます。非ローカル ビデオ メモリーがあるシステムでは、この動的パターンを適切に使用することにより、余分なコピーがなくなります。

標準テクスチャーはロックできず、UpdateSurface または UpdateTexture を使用して更新することのみが可能です。一部のシステムでは、ロック可能な動的テクスチャーをサポートし、LOCK_DISCARD パターンを使用しますが、こうしたリソースを使用する前に、能力ビット (D3DCAPS2_DYNAMICTEXTURES) をチェックする必要があります。非常に動的なテクスチャー (ビデオまたはプロシージャ) に対しては、一致する POOL_DEFAULT リソースおよび POOL_SYSTEMMEM リソースをアプリケーションで作成し、UpdateTexture を使用してビデオ メモリーの更新を処理できます。非常に頻繁で部分的な更新では、UpdateTexture パラダイムが最適な選択であると考えられます。

動的リソースは有用ですが、動的サブミッションに大きく依存するシステムを設計する際には注意が必要です。静的リソースは、ローカル ビデオ メモリーを適切に使用し、限られたバスおよびメイン メモリー帯域幅を効率的に使用できるようにするため、POOL_MANAGED に配置する必要があります。静的な要素のあるリソースの場合、ローカル ビデオ メモリーへの低頻度のアップロードのコストは、そのリソースを動的リソースにすることにより絶え間なく発生するバス トラフィックに比べて小さいことがわかります。

システム メモリー リソース

リソースは、POOL_SYSTEMMEM にも作成できます。こうしたりソースはグラフィックス パイプラインでは使用できませんが、UpdateSurface および UpdateTexture を使用することにより、POOL_DEFAULT リソースを更新するためのソースとして使用できます。前述のメソッドの 1 つで使用中の場合にストールが発生する可能性がありますが、ロック動作は単純です。

POOL_SYSTEMMEM リソースはシステム メモリーに置かれますが、デバイス ドライバーでサポートされる同一のフォーマットおよび機能 (最大サイズなど) に制限されます。POOL_SCRATCH リソース タイプはシステム メモリー リソースの別の形式で、ランタイムでサポートされるすべてのフォーマットと機能を使用できますが、デバイスからアクセスできません。スクラッチ リソースは、コンテンツ ツールで主に使用することを目的としています。

図形 3.  ビデオ RAM、AGP アパーチュア、およびシステム RAM のメモリー リソース

Bb147334.ManagingResources3(ja-jp,VS.85).gif

一般的な推奨事項

リソース管理の技術的な実装の細部の調整は、アプリケーションのパフォーマンス目標の達成に向かう長い道のりです。リソースを Direct3D にどう提示するかや、適切なタイミングでのデータの読み込みに関するアーキテクチャ設計を計画することは、さらに複雑な作業です。アプリケーションでこれらの決定を行う際のベスト プラクティスを多数推奨しています。

  • すべてのリソースを事前に処理します。高いコストがかかる、読み込み時のリソースの変換や最適化を多用すると、開発時には便利ですが、ユーザーのコンピューターには非常に大きなパフォーマンス負荷を与えてしまいます。事前処理されたリソースは高速で読み込めて、高速で使用でき、スマートにオフライン作業を行うことができます。
  • フレームごとに多数のリソースを作成しない。カーネルの移行が頻繁に要求されるため、ドライバーのやり取りが必要となって CPU および GPU がシリアル化する可能性があり、関連する操作は負荷が大きくなります。複数のフレームでリソースの作成を分散したり、リソースの作成やリリースをせずに再利用したりします。理想的には、レンダリングに使用されたばかりのリソースは、ロックしたりリリースしたりする前に、数フレーム待機します。
  • フレームの最後に、必ずすべてのリソース チャネル (つまりストリーム ソース、テクスチャー ステージ、現在のインデックス) のバインドを解除します。こうすることにより、リソース マネージャーで、実際にはもう使用されないリソースが置かれたままになる前に、リソースにぶら下がる参照が削除されます。
  • テクスチャーには、ミップマップ付きの圧縮形式を使用し (たとえば DXTn)、テクスチャー アトラスの使用も考慮します。これらにより、必要となる帯域幅が大幅に減少し、リソースの全体サイズを縮小できるので、効率が良くなります。
  • ジオメトリには、インデックス付きのジオメトリを使用します。これにより、頂点バッファー リソースが圧縮されます。最新のビデオ ハードウェアは頂点の再使用について非常に良く最適化されています。プログラム可能な頂点シェーダーを使用することにより、頂点情報を圧縮し、その情報を頂点処理の際に展開することができます。これも、必要となる帯域幅を減らし、頂点バッファー リソースの効率を高める効果があります。
  • リソース管理を最適化しすぎないようにします。アプリケーションが特定の組み合わせに対応して細かく調整されすぎている場合、ドライバー、ハードウェア、およびオペレーティング システムの今後の変更により、互換性の問題が発生する可能性があります。ほとんどのアプリケーションは CPU の制約を受けるので、コストの高い CPU ベースの管理は一般的に、パフォーマンスの問題を解決するよりむしろ発生させてしまいます。

関連項目

リソースの管理 (Direct3D 9), 喪失したデバイス (Direct3D 9), パフォーマンスの最適化 (Direct3D 9), 圧縮テクスチャー リソース (Direct3D 9), クエリ (Direct3D 9), D3DUSAGE, D3DPOOL, D3DCREATE