3D グラフィックスを使用するリアルタイム アプリケーションを作成するすべての開発者は、パフォーマンスの最適化を懸念しています。 このセクションでは、コードから最適なパフォーマンスを得るためのガイドラインを示します。
- パフォーマンスに関する一般的なヒントの
- データベースとカリング
- プリミティブのバッチ処理 の
- 照明のヒントの
- テクスチャ サイズ の
- 行列変換
- 動的テクスチャの使用 の
- 動的頂点バッファーとインデックス バッファー を使用した
- メッシュ を使用した
- Z バッファー パフォーマンス の
パフォーマンスに関する一般的なヒント
- 必要な場合にのみクリアします。
- 状態の変更を最小限に抑え、残りの状態の変更をグループ化します。
- 可能な場合は、小さいテクスチャを使用します。
- シーン内のオブジェクトを前面から背面に描画します。
- リストやファンの代わりに三角形のストリップを使用します。 頂点キャッシュのパフォーマンスを最適化するには、後でなく、三角形の頂点を再利用するようにストリップを配置します。
- システム リソースの不均衡な共有を必要とする特殊効果を適切に低下させます。
- アプリケーションのパフォーマンスを常にテストします。
- 頂点バッファー スイッチを最小化します。
- 可能な場合は静的頂点バッファーを使用します。
- FVF ごとに、オブジェクトごとに 1 つではなく、静的オブジェクトに対して 1 つの大きな静的頂点バッファーを使用します。
- アプリケーションで AGP メモリ内の頂点バッファーへのランダム アクセスが必要な場合は、32 バイトの倍数の頂点形式サイズを選択します。 それ以外の場合は、最小の適切な形式を選択します。
- インデックス付きプリミティブを使用して描画します。 これにより、ハードウェア内でより効率的な頂点キャッシュを実現できます。
- 深度バッファー形式にステンシル チャネルが含まれている場合は、常に深度チャネルとステンシル チャネルを同時にクリアします。
- シェーダー命令とデータ出力を可能な限り組み合わせます。 例えば:
// Rather than doing a multiply and add, and then output the data with // two instructions: mad r2, r1, v0, c0 mov oD0, r2 // Combine both in a single instruction, because this eliminates an // additional register copy. mad oD0, r1, v0, c0
データベースとカリング
Direct3D で優れたパフォーマンスを得るには、世界中のオブジェクトの信頼性の高いデータベースを構築することが重要です。 ラスタライズやハードウェアの改善よりも重要です。
管理できる可能性のある最も低いポリゴン数を維持する必要があります。 最初から低ポリゴン モデルを構築して、多角形の数を少ない値に設計します。 開発プロセスの後半でパフォーマンスを犠牲にすることなく、これを行うことができる場合は、多角形を追加します。 最も高速なポリゴンは描画しないポリゴンです。
プリミティブのバッチ処理
実行中に最適なレンダリング パフォーマンスを得るには、バッチでプリミティブを操作し、レンダリング状態の変更の数をできるだけ少なくします。 たとえば、2 つのテクスチャを持つオブジェクトがある場合は、最初のテクスチャを使用する三角形をグループ化し、テクスチャを変更するために必要なレンダリング状態に従います。 次に、2 番目のテクスチャを使用するすべての三角形をグループ化します。 Direct3D の最も簡単なハードウェア サポートは、レンダリング状態のバッチと、ハードウェア抽象化レイヤー (HAL) を介したプリミティブのバッチで呼び出されます。 命令がバッチ処理されるほど、実行中に実行される HAL 呼び出しが少なくなります。
照明のヒント
ライトはレンダリングされた各フレームに頂点ごとのコストを追加するため、アプリケーションでの使用方法に注意することで、パフォーマンスを大幅に向上させることができます。 次のヒントのほとんどは、「最も高速なコードは決して呼び出されないコード」から派生しています。
- できるだけ少ない光源を使用してください。 たとえば、照明レベル全体を上げるには、新しい光源を追加する代わりにアンビエント ライトを使用します。
- 方向ライトは、ポイント ライトやスポットライトよりも効率的です。 方向ライトの場合、ライトへの方向は固定され、頂点単位で計算する必要はありません。
- スポットライトは、光の円錐の外側の領域が迅速に計算されるため、ポイント ライトよりも効率的です。 スポットライトの効率が向上するかどうかは、スポットライトによって照明されるシーンの量によって異なります。
- 照明する必要があるシーンの部分のみにライトを制限するには、range パラメーターを使用します。 すべてのライトタイプは、範囲外になるとかなり早く終了します。
- 反射ハイライトは、ライトのコストのほぼ 2 倍になります。 必要な場合にのみ使用してください。 可能な限り、D3DRS_SPECULARENABLEレンダリングの状態を既定値の 0 に設定します。 マテリアルを定義するときは、そのマテリアルの反射ハイライトをオフにするには、反射パワー値を 0 に設定する必要があります。単に反射色を0,0,0に設定するだけでは十分ではありません。
テクスチャ サイズ
テクスチャ マッピングのパフォーマンスは、メモリの速度に大きく依存します。 アプリケーションのテクスチャのキャッシュ パフォーマンスを最大化するには、いくつかの方法があります。
- テクスチャを小さくします。 テクスチャが小さいほど、メイン CPU のセカンダリ キャッシュに保持される可能性が高くなります。
- プリミティブ単位でテクスチャを変更しないでください。 使用するテクスチャの順序でポリゴンをグループ化し続けるようにします。
- 可能な限り正方形のテクスチャを使用します。 寸法が 256 x 256 のテクスチャが最も高速です。 たとえば、アプリケーションで 4 つの 128 x 128 テクスチャを使用する場合は、同じパレットを使用し、それらすべてを 1 つの 256 x 256 テクスチャに配置してみてください。 この手法により、テクスチャ スワップの量も減ります。 もちろん、256 x 256 テクスチャは、前述のようにテクスチャを可能な限り小さくする必要があるため、アプリケーションでそれほど多くのテクスチャが必要でない限り使用しないでください。
行列変換
Direct3D では、ワールドを使用し、いくつかの内部データ構造を構成するために設定したマトリックスを表示します。 新しいワールドマトリックスまたはビューマトリックスを設定するたびに、関連する内部構造が再計算されます。 これらのマトリックスをフレームごとに数千回など頻繁に設定すると、計算に時間がかかります。 ワールド マトリックスとビュー マトリックスをワールド マトリックスとして設定したワールド ビュー マトリックスに連結し、ビュー マトリックスを ID に設定することで、必要な計算の数を最小限に抑えることができます。 必要に応じてワールド マトリックスを変更、連結、リセットできるように、個々のワールドおよびビュー マトリックスのキャッシュされたコピーを保持します。 このドキュメントをわかりやすくするために、Direct3D サンプルではこの最適化を採用することはめったにありません。
動的テクスチャの使用
ドライバーが動的テクスチャをサポートしているかどうかを確認するには、D3DCAPS9 構造体のD3DCAPS2_DYNAMICTEXTURES フラグを確認します。
動的テクスチャを使用する場合は、次の点に注意してください。
- これらは管理できません。 たとえば、プールをD3DPOOL_MANAGEDすることはできません。
- 動的テクスチャは、D3DPOOL_DEFAULTで作成された場合でもロックできます。
- D3DLOCK_DISCARDは、動的テクスチャの有効なロック フラグです。
フォーマットごとに 1 つの動的テクスチャのみを作成し、場合によってはサイズごとに作成することをお勧めします。 動的ミップマップ、キューブ、ボリュームは、すべてのレベルをロックするオーバーヘッドが増えるため、推奨されません。 ミップマップの場合、D3DLOCK_DISCARDは最上位レベルでのみ許可されます。 最上位レベルのみをロックすると、すべてのレベルが破棄されます。 この動作は、ボリュームとキューブでも同じです。 キューブの場合、最上位レベルと面 0 はロックされます。
次の擬似コードは、動的テクスチャの使用例を示しています。
DrawProceduralTexture(pTex)
{
// pTex should not be very small because overhead of
// calling driver every D3DLOCK_DISCARD will not
// justify the performance gain. Experimentation is encouraged.
pTex->Lock(D3DLOCK_DISCARD);
<Overwrite *entire* texture>
pTex->Unlock();
pDev->SetTexture();
pDev->DrawPrimitive();
}
動的頂点バッファーとインデックス バッファーの使用
グラフィックス プロセッサがバッファーを使用している間に静的頂点バッファーをロックすると、パフォーマンスが大幅に低下する可能性があります。 ロック呼び出しは、グラフィックス プロセッサが呼び出し元のアプリケーションに戻る前に、バッファーからの頂点またはインデックス データの読み取りが完了するまで待機する必要があります。大幅な遅延が発生します。 フレームごとに静的バッファーを数回ロックしてからレンダリングすると、ロック ポインターを返す前にコマンドを完了する必要があるため、グラフィックス プロセッサはレンダリング コマンドをバッファリングできなくなります。 バッファー化されたコマンドがない場合、アプリケーションが頂点バッファーまたはインデックス バッファーの入力を完了し、レンダリング コマンドを発行するまで、グラフィックス プロセッサはアイドル状態のままになります。
頂点データやインデックス データは決して変更されないのが理想的ですが、これは常に可能であるとは限りません。 アプリケーションがフレームごとに頂点またはインデックス データを変更する必要がある状況は多数あります。場合によっては、フレームごとに複数回変更する必要があります。 このような場合は、頂点バッファーまたはインデックス バッファーをD3DUSAGE_DYNAMICで作成する必要があります。 この使用フラグにより、Direct3D は頻繁なロック操作用に最適化されます。 D3DUSAGE_DYNAMICは、バッファーが頻繁にロックされている場合にのみ役立ちます。定数のままのデータは、静的な頂点またはインデックス バッファーに配置する必要があります。
動的頂点バッファーを使用するときにパフォーマンスを向上させるには、アプリケーションで IDirect3DVertexBuffer9::Lock呼び出すか、適切なフラグで IDirect3DIndexBuffer9::Lockをする必要があります。 D3DLOCK_DISCARDは、アプリケーションが古い頂点またはインデックス データをバッファーに保持する必要がないことを示します。 D3DLOCK_DISCARDでロックが呼び出されたときにグラフィックス プロセッサがバッファーを使用している場合は、古いバッファー データではなく、メモリの新しい領域へのポインターが返されます。 これにより、アプリケーションが新しいバッファーにデータを配置している間、グラフィックス プロセッサは古いデータを引き続き使用できます。 アプリケーションで追加のメモリ管理は必要ありません。古いバッファーは、グラフィックス プロセッサが終了すると自動的に再利用または破棄されます。 D3DLOCK_DISCARDを使用してバッファーをロックすると、常にバッファー全体が破棄され、0 以外のオフセットフィールドまたは制限付きサイズ フィールドを指定しても、バッファーのロック解除された領域の情報は保持されないことに注意してください。
スプライトをレンダリングするために 4 つの頂点を追加するなど、アプリケーションがロックごとに格納する必要があるデータの量が少ない場合があります。 D3DLOCK_NOOVERWRITEは、アプリケーションが動的バッファーで既に使用されているデータを上書きしないことを示します。 ロック呼び出しは古いデータへのポインターを返します。これにより、アプリケーションは頂点またはインデックス バッファーの未使用の領域に新しいデータを追加できます。 描画操作で使用される頂点またはインデックスは、グラフィックス プロセッサで引き続き使用される可能性があるため、アプリケーションで変更しないでください。 その後、動的バッファーがいっぱいになった後、アプリケーションはD3DLOCK_DISCARDを使用してメモリの新しい領域を受け取り、グラフィックス プロセッサの完了後に古い頂点またはインデックス データを破棄する必要があります。
非同期クエリ メカニズムは、頂点がグラフィックス プロセッサでまだ使用されているかどうかを判断するのに役立ちます。 頂点を使用する最後の DrawPrimitive 呼び出しの後にD3DQUERYTYPE_EVENT型のクエリを発行します。 IDirect3DQuery9::GetData がS_OKを返すとき、頂点は使用されなくなりました。 D3DLOCK_DISCARDまたはフラグなしのバッファーをロックすると、常に頂点がグラフィックス プロセッサと正しく同期されることを保証しますが、フラグなしのロックを使用すると、前に説明したパフォーマンスの低下が発生します。 IDirect3DDevice9::BeginScene、IDirect3DDevice9::EndScene、IDirect3DDevice9::P resent などの他の API 呼び出しでは、グラフィックス プロセッサが頂点を使用して終了することは保証されません。
動的バッファーと適切なロック フラグを使用する方法を次に示します。
// USAGE STYLE 1
// Discard the entire vertex buffer and refill with thousands of vertices.
// Might contain multiple objects and/or require multiple DrawPrimitive
// calls separated by state changes, etc.
// Determine the size of data to be moved into the vertex buffer.
UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
// Discard and refill the used portion of the vertex buffer.
CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
// Lock the vertex buffer.
BYTE* pBytes;
if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
return false;
// Copy the vertices into the vertex buffer.
memcpy( pBytes, pVertices, nSizeOfData );
m_pVertexBuffer->Unlock();
// Render the primitives.
m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)
// USAGE STYLE 2
// Reusing one vertex buffer for multiple objects
// Determine the size of data to be moved into the vertex buffer.
UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
// No overwrite will be used if the vertices can fit into
// the space remaining in the vertex buffer.
DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
// Check to see if the entire vertex buffer has been used up yet.
if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
{
// No space remains. Start over from the beginning
// of the vertex buffer.
dwLockFlags = D3DLOCK_DISCARD;
m_nNextVertexData = 0;
}
// Lock the vertex buffer.
BYTE* pBytes;
if( FAILED( m_pVertexBuffer->Lock( (UINT)m_nNextVertexData, nSizeOfData,
&pBytes, dwLockFlags ) ) )
return false;
// Copy the vertices into the vertex buffer.
memcpy( pBytes, pVertices, nSizeOfData );
m_pVertexBuffer->Unlock();
// Render the primitives.
m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST,
m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
// Advance to the next position in the vertex buffer.
m_nNextVertexData += nSizeOfData;
メッシュの使用
インデックス付き三角形ストリップではなく、Direct3D インデックス付き三角形を使用してメッシュを最適化できます。 ハードウェアは、連続する三角形の 95% が実際にストリップを形成し、それに応じて調整することを検出します。 多くのドライバーは、古いハードウェアでもこれを行います。
D3DX メッシュ オブジェクトには、その顔の属性と呼ばれる DWORD でタグ付けされた各三角形 (顔) を含めることができます。 DWORD のセマンティクスはユーザー定義です。 これらは、メッシュをサブセットに分類するために D3DX によって使用されます。 アプリケーションは、ID3DXMesh::LockAttributeBuffer 呼び出しを使用して顔ごとの属性を設定します。 ID3DXMesh::Optimize メソッドには、D3DXMESHOPT_ATTRSORT オプションを使用して、属性のメッシュ頂点と面をグループ化するオプションがあります。 この操作が完了すると、mesh オブジェクトは、ID3DXBaseMesh::GetAttributeTableを呼び出すことによって、アプリケーションが取得できる属性テーブルを計算します。 メッシュが属性で並べ替えられない場合、この呼び出しは 0 を返します。 ID3DXMesh::Optimize メソッドによって生成されるため、アプリケーションで属性テーブルを設定する方法はありません。 属性の並べ替えはデータに依存するため、アプリケーションがメッシュが属性の並べ替えを行っていることを認識している場合でも、属性テーブルを生成するには ID3DXMesh::Optimize 呼び出す必要があります。
次のトピックでは、メッシュのさまざまな属性について説明します。
属性 ID
属性 ID は、顔のグループを属性グループに関連付ける値です。 この ID は、ID3DXBaseMesh::D rawSubset描画する必要がある顔のサブセット記述します。 属性 ID は、属性バッファー内の顔に対して指定されます。 属性 ID の実際の値は、32 ビットに収まるものにすることができますが、n は属性の数である場合に 0 から n を使用するのが一般的です。
属性バッファー
属性バッファーは、各顔が属する属性グループを指定する DWORD の配列 (顔ごとに 1 つ) です。 このバッファーは、メッシュの作成時にゼロに初期化されますが、ロード ルーチンによって塗りつぶされるか、ID 0 を持つ複数の属性が必要な場合は、ユーザーが入力する必要があります。 このバッファーには、ID3DXMesh::Optimizeの属性に基づいてメッシュを並べ替えるために使用される情報が含まれています。 属性テーブルが存在しない場合は、ID3DXBaseMesh::D rawSubset、このバッファーをスキャンして、描画する特定の属性の顔を選択します。
属性テーブル
属性テーブルは、メッシュによって所有および管理される構造です。 1 つを生成する唯一の方法は、属性の並べ替えまたはより強力な最適化を有効に ID3DXMesh::Optimize を呼び出すことです。 属性テーブルは、ID3DXBaseMesh::D rawSubsetへの単一描画プリミティブ呼び出しをすばやく開始するために使用されます。 他の唯一の用途は、進行するメッシュでもこの構造が維持されるため、現在の詳細レベルでアクティブになっている顔と頂点を確認できます。
Z バッファーのパフォーマンス
アプリケーションでは、z バッファリングとテクスチャリングを使用する場合に、シーンが前面から背面にレンダリングされるようにすることで、パフォーマンスを向上させることができます。 テクスチャ化された z バッファープリミティブは、スキャン ラインごとに z バッファーに対して事前テストされます。 スキャンラインが以前にレンダリングされたポリゴンによって隠れている場合、システムはスキャンラインを迅速かつ効率的に拒否します。 Z バッファリングはパフォーマンスを向上させることができますが、この手法は、シーンが同じピクセルを複数回描画する場合に最も役立ちます。 これは正確に計算するのが難しいですが、多くの場合、近い近似を行うことができます。 同じピクセルが 2 回未満で描画される場合は、z バッファリングをオフにし、シーンを背面から前面にレンダリングすることで、最高のパフォーマンスを実現できます。
関連トピック