クエリ (Direct3D 9)
リソースの状況を問い合わせることを目的としたクエリが何種類かあります。特定のリソースの状況には、グラフィック プロセッシング ユニット (GPU) の状況、ドライバーの状況、またはランタイムの状況が含まれます。各種クエリの違いを理解するには、クエリの状態を理解する必要があります。次にクエリの各状態を説明した状態遷移の図を示します。
図では 3 つの状態がそれぞれ円で囲んで示されています。各実線は状態遷移を発生するアプリケーション主導型のイベントです。破線は、発行済みステートからシグナル ステートにクエリを切り替えるリソース主導型のイベントです。これらのステートはそれぞれ次のような別々の目的を持ちます。
- シグナル ステートはアイドル状態に似ています。クエリ オブジェクトが生成され、アプリケーションがクエリを発行するのを待機します。クエリが完了してシグナル ステートに戻ると、クエリへの回答を取得できます。
- ビルド ステートは、クエリのステージング領域のようなものです。ビルド ステートから、クエリは (D3DISSUE_BEGIN を呼び出すことで) 発行されますが、まだ発行済みステートには移行していません。アプリケーションが (D3DISSUE_END を呼び出すことで) クエリの終了を発行すると、クエリは発行済みステートに移行します。
- 発行済みステートは、クエリされるリソースがクエリのコントロールを持つことを意味します。リソースがその作業を終了すると、リソースはマシン ステートをシグナル ステートに移行します。発行済みステートの間、アプリケーションはポーリングを実行してシグナル ステートへの移行を検出する必要があります。シグナル ステートへの移行が発生すると、IDirect3DQuery9::GetData はアプリケーションにクエリ結果 (引数を介して) を返します。
使用可能なクエリの種類を次の表に示します。
クエリの種類 | 発行イベント | GetData バッファー | ランタイム | クエリの非明示的な開始 |
---|---|---|---|---|
BANDWIDTHTIMINGS | D3DISSUE_BEGIN, D3DISSUE_END | D3DDEVINFO_D3D9BANDWIDTHTIMINGS | リテール/デバッグ | なし |
CACHEUTILIZATION | D3DISSUE_BEGIN, D3DISSUE_END | D3DDEVINFO_D3D9CACHEUTILIZATION | リテール/デバッグ | なし |
EVENT? | D3DISSUE_END | BOOL | リテール/デバッグ | IDirect3D9::CreateDevice |
INTERFACETIMINGS | D3DISSUE_BEGIN, D3DISSUE_END | D3DDEVINFO_D3D9INTERFACETIMINGS | リテール/デバッグ | なし |
OCCLUSION? | D3DISSUE_BEGIN, D3DISSUE_END | DWORD | リテール/デバッグ | なし |
PIPELINETIMINGS | D3DISSUE_BEGIN, D3DISSUE_END | D3DDEVINFO_D3D9PIPELINETIMINGS | リテール/デバッグ | なし |
RESOURCEMANAGER | D3DISSUE_END | D3DDEVINFO_ResourceManager | デバッグのみ | IDirect3DDevice9::Present |
TIMESTAMP? | D3DISSUE_END | UINT64 | リテール/デバッグ | なし |
TIMESTAMPDISJOINT | D3DISSUE_BEGIN, D3DISSUE_END | BOOL | リテール/デバッグ | なし |
TIMESTAMPFREQ | D3DISSUE_END | UINT64 | リテール/デバッグ | なし |
VCACHE | D3DISSUE_END | D3DDEVINFO_VCACHE | リテール/デバッグ | IDirect3D9::CreateDevice |
VERTEXSTATS | D3DISSUE_END | D3DDEVINFO_D3DVERTEXSTATS | デバッグのみ | IDirect3DDevice9::Present |
VERTEXTIMINGS | D3DISSUE_BEGIN, D3DISSUE_END | D3DDEVINFO_D3D9STAGETIMINGS | リテール/デバッグ | なし |
開始イベントと終了イベントを必要とするクエリもあれば、終了イベントのみを必要とするクエリもあります。終了イベントのみを必要とするクエリは、別の暗黙的イベントの発生時に開始します (これは表に一覧されています)。常に回答が TRUE のイベント クエリを除き、すべてのクエリが回答を返します。アプリケーションは、クエリのステート、または IDirect3DQuery9::GetData のリターン コードのいずれかを使用します。
クエリの作成
クエリを作成する前に、次のように NULL ポインターで IDirect3DDevice9::CreateQuery を呼び出すことで、ランタイムがクエリをサポートするかどうかを確認できます。
IDirect3DQuery9* pEventQuery;
// Create a device pointer m_pd3dDevice
// Create a query object
HRESULT hr = m_pd3dDevice->CreateQuery(D3DQUERYTYPE_EVENT, NULL);
このメソッドは、クエリが作成できる場合はサクセス コードを返し、作成できない場合はエラー コードを返します。IDirect3DDevice9::CreateQuery が成功すると、次のようなクエリ オブジェクトを作成できます。
IDirect3DQuery9* pEventQuery;
m_pd3dDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);
この呼び出しが作成すると、クエリ オブジェクトが作成されます。クエリは、発行を待機するシグナル ステート (初期化されていない回答で) では本質的にアイドル状態です。クエリを終了したら、これを他のインターフェイスと同じように解放します。
クエリの発行
アプリケーションは、クエリを発行することでクエリの状態を変更します。クエリ発行の例を次に示します。
IDirect3DQuery9* pEventQuery;
m_pD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);
// Issue a Begin event
pEventQuery->Issue(D3DISSUE_BEGIN);
or
// Issue an End event
pEventQuery->Issue(D3DISSUE_END);
シグナル ステートのクエリは、発行時に次のように移行します。
発行の種類 | クエリの移行先 . . |
---|---|
D3DISSUE_BEGIN | ビルド ステート。 |
D3DISSUE_END | 発行済みステート。 |
ビルド ステートのクエリは、発行時に次のように移行します。
発行の種類 | クエリの移行先 . . |
---|---|
D3DISSUE_BEGIN | (移行なし。ビルド ステートのまま残ります。クエリ ブラケットを再起動します。) |
D3DISSUE_END | 発行済みステート。 |
発行済みステートのクエリは、発行時に次のように移行します。
発行の種類 | クエリの移行先 . . |
---|---|
D3DISSUE_BEGIN | ビルド ステートに移行し、クエリ ブラケットを再起動します。 |
D3DISSUE_END | 既存のクエリを破棄した後、発行済みステートに移行します。 |
クエリ ステートの確認、およびクエリへの回答の取得
IDirect3DQuery9::GetData は、次のことを実行します。
- クエリ ステートをリターン コードで返す。
- クエリへの回答を pData で返す。
3 つのクエリ ステートそれぞれからの IDirect3DQuery9::GetData リターン コードは次のとおりです。
クエリ ステート | GetData リターン コード |
---|---|
シグナル | S_OK |
ビルド | エラー コード |
発行済み | S_FALSE |
たとえば、クエリが発行済みステートにあり、クエリへの回答が使用できない場合、IDirect3DQuery9::GetData は S_FALSE を返します。リソースがその作業を終了し、アプリケーションがクエリの終了を発行すると、リソースはクエリをシグナル ステートに移行します。シグナル ステートから、IDirect3DQuery9::GetData は S_OK を返します。これはクエリへの回答も pData に返されることを意味します。たとえば、次の例では、レンダリング シーケンスで描画されたピクセル数を返すイベントのシーケンスを示します。
- クエリを作成します。
- 開始イベントを発行します。
- 何かを描画します。
- 終了イベントを発行します。
対応するコードのシーケンスは次のとおりです。
IDirect3DQuery9* pOcclusionQuery;
DWORD numberOfPixelsDrawn;
m_pD3DDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, &pOcclusionQuery);
// Add an end marker to the command buffer queue.
pOcclusionQuery->Issue(D3DISSUE_BEGIN);
// API render loop
...
Draw(...)
...
// Add an end marker to the command buffer queue.
pOcclusionQuery->Issue(D3DISSUE_END);
// Force the driver to execute the commands from the command buffer.
// Empty the command buffer and wait until the GPU is idle.
while(S_FALSE == pOcclusionQuery->GetData( &numberOfPixelsDrawn,
sizeof(DWORD), D3DGETDATA_FLUSH ))
;
これらのコード行は、次のいくつかのことを実行します。
- 描画されるピクセル数を返す IDirect3DQuery9::GetData を呼び出します。
- リソースがシグナル ステートにクエリを移行できるようにする D3DGETDATA_FLUSH を指定します。
- ループから IDirect3DQuery9::GetData を呼び出すことでクエリ リソースをポーリングします。IDirect3DQuery9::GetData が S_FALSE を返す限り、これはリソースが回答をまだ返していないことを意味します。
IDirect3DQuery9::GetData の戻り値は、本質的にはクエリがどのようなステートであるかを伝えるものです。表示される値は S_OK、S_FALSE、およびエラーです。ビルド ステートにあるクエリでは IDirect3DQuery9::GetData を呼び出さないでください。
- S_OK はリソース (GPU またはドライバー、またはランタイム) が終了したことを意味します。クエリはシグナル ステートに戻ります。IDirect3DQuery9::GetData によって回答が (あれば) 返されます。
- S_FALSE は、リソース (GPU またはドライバー、またはランタイム) が回答をまだ返すことができないことを意味します。これは、GPU が終了していないか、作業をまだ確認していないために発生することがあります。
- エラーは、クエリが復帰できないエラーを生成していることを意味します。これは、デバイスがクエリ中に失われた場合に発生することがあります。クエリがエラー (S_FALSE 以外の) を生成したら、シグナル ステートからクエリ シーケンスを再起動するクエリを再作成する必要があります。
最新の情報を提供する D3DGETDATA_FLUSH を指定する代わりに、クエリが発行済みステートにあるかどうかをチェックする軽量なゼロを指定することができます。ゼロを指定すると、IDirect3DQuery9::GetData はコマンド バッファーをフラッシュしません。このため、無限ループを回避するように注意が必要です (詳細については、「IDirect3DQuery9::GetData」を参照してください)。ランタイム クエリはコマンド バッファーで動作するため、D3DGETDATA_FLUSH はコマンド バッファーをドライバー (しがたって GPU に。「Direct3D API 呼び出しの正確なプロファイリング (Direct3D 9)」を参照) にフラッシュするためのメカニズムです。コマンド バッファーがフラッシュしている間、クエリはシグナル ステートに移行することができます。
例 :イベント クエリ
イベント クエリは開始イベントをサポートしていません。
- クエリを作成します。
- 終了イベントを発行します。
- GPU がアイドル状態となるまでポーリングします。
- 終了イベントを発行します。
IDirect3DQuery9* pEventQuery = NULL;
m_pD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);
// Add an end marker to the command buffer queue.
pEventQuery->Issue(D3DISSUE_END);
// Empty the command buffer and wait until the GPU is idle.
while(S_FALSE == pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH ))
;
... // API calls
// Add an end marker to the command buffer queue.
pEventQuery->Issue(D3DISSUE_END);
// Force the driver to execute the commands from the command buffer.
// Empty the command buffer and wait until the GPU is idle.
while(S_FALSE == pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH ))
;
これはイベント クエリがアプリケーション プログラミング インターフェイス (API) 呼び出しをプロファイルするために使用するコマンドのシーケンスです (「Direct3D API 呼び出しの正確なプロファイリング (Direct3D 9)」を参照)。このシーケンスでは、マーカーを使用してコマンド バッファー内の作業量をコントロールしやすくします。
アプリケーションは、コマンド バッファーのフラッシュに関連付けられた膨大なコストに特別な注意を払う必要があります。これによってオペレーティング システムはカーネル モードに切り替わり、かなりパフォーマンスが低下するためです。また、アプリケーションはクエリが完了するのを待機することによって CPU サイクルが無駄になることも認識しておく必要があります。
クエリとは、パフォーマンスを向上するためのレンダリング中に使用される最適化です。したがって、クエリが終了するのを待機するために時間を費やしても利点はありません。クエリが発行され、結果がアプリケーションがこれらをチェックする時間までに準備されていない場合、最適化の試行は成功せず、レンダリングは通常どおり継続します。
オクルージョン カリング中がこの典型的な例として挙げられます。上記の while ループの代わりに、クエリを使用するアプリケーションはオクルージョン カリングを実装することで、結果が必要となるまでにクエリが完了しているかどうかをチェックできます。クエリが完了していない場合は、テスト対象のオブジェクトがオクルードされていない (つまり見ることができる) かのように続行 (最悪のケースのシナリオとして) して、これをレンダリングします。コードは次のようになります。
IDirect3DQuery9* pOcclusionQuery = NULL;
m_pD3DDevice->CreateQuery( D3DQUERYTYPE_OCCLUSION, &pOcclusionQuery );
// Add a begin marker to the command buffer queue.
pOcclusionQuery->Issue( D3DISSUE_BEGIN );
... // API calls
// Add an end marker to the command buffer queue.
pOcclusionQuery->Issue( D3DISSUE_END );
// Avoid flushing and letting the CPU go idle by not using a while loop.
// Check if queries are finished:
DWORD dwOccluded = 0;
if( S_FALSE == pOcclusionQuery->GetData( &dwOccluded, sizeof(DWORD), 0 ) )
{
// Query is not done yet or object not occluded; avoid flushing/wait by continuing with worst-case scenario
pSomeComplexMesh->Render();
}
else if( dwOccluded != 0 )
{
// Query is done and object is not occluded.
pSomeComplexMesh->Render();
}