Queries

There are several types of queries which are designed to query the status of resources. The status of a given resource includes graphics processing unit (GPU) status, driver status, or runtime status. To understand the difference between the different query types, you need to understand the query states. Here's a state transition diagram that explains each of the query states:

The diagram shows three states, each defined by circles. Each of the solid lines are application-driven events that cause a state transition. The dashed line is a resource-driven event that switches a query from the issued state to the signaled state. Each of these states has a different purpose:

The following table lists the available query types.

Query Type Issue Event GetData buffer Runtime Implicit beginning of query
BANDWIDTHTIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9BANDWIDTHTIMINGS Retail/Debug N/A
CACHEUTILIZATION D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9CACHEUTILIZATION Retail/Debug N/A
EVENT D3DISSUE_END BOOL Retail/Debug IDirect3D9::CreateDevice
INTERFACETIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9INTERFACETIMINGS Retail/Debug N/A
OCCLUSION D3DISSUE_BEGIN, D3DISSUE_END DWORD Retail/Debug N/A
PIPELINETIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9PIPELINETIMINGS Retail/Debug N/A
RESOURCEMANAGER D3DISSUE_END D3DDEVINFO_ResourceManager Debug only IDirect3DDevice9::Present
TIMESTAMP D3DISSUE_END UINT64 Retail/Debug N/A
TIMESTAMPDISJOINT D3DISSUE_BEGIN, D3DISSUE_END BOOL Retail/Debug N/A
TIMESTAMPFREQ D3DISSUE_END UINT64 Retail/Debug N/A
VCACHE D3DISSUE_END D3DDEVINFO_VCACHE Retail/Debug IDirect3D9::CreateDevice
VERTEXSTATS D3DISSUE_END D3DDEVINFO_D3DVERTEXSTATS Debug only IDirect3DDevice9::Present
VERTEXTIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9STAGETIMINGS Retail/Debug N/A

Some of the queries require a begin and end event, while others only require an end event. The queries that only require an end event begin when another implicit event occurs (which is listed in the table). All queries return an answer, except the event query whose answer is always TRUE. An application uses either the state of the query or the return code of IDirect3DQuery9::GetData.

Create a Query

Before you create a query, you can check to see if the runtime supports queries by calling IDirect3DDevice9::CreateQuery with a NULL pointer like this:

IDirect3DQuery9* pEventQuery;

// Create a device pointer m_pd3dDevice

// Create a query object
HRESULT hr = m_pd3dDevice->CreateQuery(D3DQUERYTYPE_EVENT, NULL);

This method returns a success code if a query can be created; otherwise it returns an error code. Once IDirect3DDevice9::CreateQuery succeeds, you can create a query object like this:

IDirect3DQuery9* pEventQuery;
m_pd3dDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);

If this call succeeds, a query object is created. The query is essentially idle in the signaled state (with an uninitialized answer) waiting to be issued. When you are finished with the query, release it like any other interface.

Issue a Query

An application changes a query state by issuing a query. Here is an example of issuing a query:

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);

A query in the signaled state will transition like this when issued:

Issue Type Query Transitions to the . . .
D3DISSUE_BEGIN Building state.
D3DISSUE_END Issued state.

A query in the building state will transition like this when issued:

Issue Type Query Transitions to the . . .
D3DISSUE_BEGIN (No transition, stays in the building state. Restarts the query bracket.)
D3DISSUE_END Issued state.

An query in the issued state will transition like this when issued:

Issue Type Query Transitions to the . . .
D3DISSUE_BEGIN Building state and restarts the query bracket.
D3DISSUE_END Issued state after abandoning the existing query.

Check the Query State and Get the Answer to the Query

IDirect3DQuery9::GetData does two things:

  1. Returns the query state in the return code.
  2. Returns the answer to the query in pData.

From each of the three query states, here are the IDirect3DQuery9::GetData return codes:

Query State GetData return code
Signaled S_OK
Building Error code
Issued S_FALSE

For example, when a query is in the issued state and the answer to the query is not available, IDirect3DQuery9::GetData returns S_FALSE. When the resource finishes its work and the application has issued a query end, the resource transitions the query to the signaled state. From the signaled state, IDirect3DQuery9::GetData returns S_OK which means that the answer to the query is also returned in pData. For instance, here is the sequence of events to return the number of pixels drawn in a render sequence:

The following is the corresponding sequence of code:

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 ))
    ;

These lines of code do several things:

The return value of IDirect3DQuery9::GetData essentially tells you in what state the query is. Possible values are S_OK, S_FALSE, and an error. Do not call IDirect3DQuery9::GetData on a query that is in the building state.

Instead of specifying D3DGETDATA_FLUSH, which provides more up-to-date information, you could supply zero which is a more light-weight check if the query is in the issued state. Supplying zero will cause IDirect3DQuery9::GetData to not flush the command buffer. For this reason, care must be taken to avoid infinate loops (see IDirect3DQuery9::GetData for details). Since the runtime queues up work in the command buffer, D3DGETDATA_FLUSH is a mechanism for flushing the command buffer to the driver (and hence the GPU; see Accurately Profiling Direct3D API Calls). During the command buffer flush, a query may transition to the signaled state.

Example: An Event Query

An event query does not support a begin event.

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 ))
    ;

This is the sequence of commands an event query uses to profile application programming interface (API) calls (see Accurately Profiling Direct3D API Calls). This sequence uses markers to help control the amount of work in the command buffer.

Note that applications should pay special attention to the large cost associated with flushing the command buffer because this causes the operating system to switch into kernel mode, thus incurring a sizeable performance penalty. Applications should also be aware of wasting CPU cycles by waiting for queries to complete.

Queries are an optimization to be used during rendering to increase performance. Therefore, it is not beneficial to spend time waiting for a query to finish. If a query is issued and if the results are not yet ready by the time the application checks for them, the attempt at optimizing did not succeed and rendering should continue as normal.

The classic example of this is during Occlusion Culling. Instead of the while loop above, an application using queries can implement occlusion culling to check to see if a query had finished by the time it needs the result. If the query has not finished, continue (as a worst-case scenario) as if the object being tested against is not occluded (i.e. it is visible) and render it. The code would look similar to the following.

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();
}