查询 (Direct3D 9)

有多种类型的查询旨在查询资源的状态。 给定资源的状态包括图形处理单元(GPU)状态、驱动程序状态或运行时状态。 若要了解不同查询类型之间的差异,需要了解查询状态。 以下状态转换关系图说明了每个查询状态。

显示查询状态关系图

此图显示了三种状态,每个状态由圆定义。 每个实线都是导致状态转换的应用程序驱动事件。 虚线是资源驱动的事件,可将查询从已发出状态切换到信号状态。 每个状态都有不同的用途:

  • 信号状态类似于空闲状态。 查询对象已生成,并正在等待应用程序发出查询。 查询完成并转换回信号状态后,可以检索查询的答案。
  • 生成状态类似于查询的暂存区域。 从生成状态开始,已发出查询(通过调用 D3DISSUE_BEGIN),但尚未转换为已颁发状态。 当应用程序发出查询结束(通过调用 D3DISSUE_END),查询将转换为已颁发的状态。
  • 颁发的状态意味着正在查询的资源可以控制查询。 资源完成其工作后,资源会将状态机转换为信号状态。 在发出状态期间,应用程序必须轮询以检测到信号状态的转换。 转换到信号状态后,GetData 将查询结果(通过参数)返回到应用程序。

下表列出了可用的查询类型。

查询类型 问题事件 GetData 缓冲区 运行 隐式查询开头
BANDWIDTHTIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9BANDWIDTHTIMINGS 零售/调试 N/A
CACHEUTILIZATION D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9CACHEUTILIZATION 零售/调试 N/A
事件 D3DISSUE_END BOOL 零售/调试 CreateDevice
INTERFACETIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9INTERFACETIMINGS 零售/调试 N/A
闭塞 D3DISSUE_BEGIND3DISSUE_END DWORD 零售/调试 N/A
PIPELINETIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9PIPELINETIMINGS 零售/调试 N/A
RESOURCEMANAGER D3DISSUE_END D3DDEVINFO_ResourceManager 仅调试 演示
时间戳 D3DISSUE_END UINT64 零售/调试 N/A
TIMESTAMPDISJOINT D3DISSUE_BEGIND3DISSUE_END BOOL 零售/调试 N/A
TIMESTAMPFREQ D3DISSUE_END UINT64 零售/调试 N/A
VCACHE D3DISSUE_END D3DDEVINFO_VCACHE 零售/调试 CreateDevice
VERTEXSTATS D3DISSUE_END D3DDEVINFO_D3DVERTEXSTATS 仅调试 演示
VERTEXTIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9STAGETIMINGS 零售/调试 N/A

 

某些查询需要开始和结束事件,而另一些查询则只需要结束事件。 仅当发生另一个隐式事件(表中列出的)时,才需要结束事件的查询。 所有查询都返回一个答案,但其答案始终 TRUE的事件查询除外。 应用程序使用查询的状态或 GetData的返回代码。

创建查询

在创建查询之前,可以通过使用以下 NULL 指针调用 CreateQuery 来检查运行时是否支持查询:

IDirect3DQuery9* pEventQuery;

// Create a device pointer m_pd3dDevice

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

如果可以创建查询,此方法将返回成功代码;否则,它将返回错误代码。 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 放弃现有查询后已发出状态。

 

检查查询状态并获取查询答案

GetData 执行两项作:

  1. 返回代码中的查询状态。
  2. 返回 pData中的查询的答案。

从这三个查询状态中的每一个,下面是 GetData 返回代码:

查询状态 GetData 返回代码
暗示 S_OK
建筑 错误代码
发出 S_FALSE

 

例如,当查询处于发出状态且查询答案不可用时,GetData 返回S_FALSE。 当资源完成其工作并且应用程序已发出查询结束时,资源会将查询转换为信号状态。 从信号状态中,GetData 返回S_OK,这意味着查询答案也会在 pData 中返回。 例如,下面是在呈现序列中绘制多重采样时返回像素数(或样本)的事件序列:

  • 创建查询。
  • 发出开始事件。
  • 绘制内容。
  • 发出结束事件。

下面是相应的代码序列:

IDirect3DQuery9* pOcclusionQuery;
DWORD numberOfSamplesDrawn;

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( &numberOfSamplesDrawn, 
                                  sizeof(DWORD), D3DGETDATA_FLUSH ))
    ;

// To get the number of pixels drawn when multisampling is enabled,
// divide numberOfSamplesDrawn by the sample count of the render target.

这些代码行执行多项作:

  • 调用 GetData 以返回绘制的像素/样本数。
  • 指定 D3DGETDATA_FLUSH,使资源能够将查询转换为信号状态。
  • 通过从循环调用 GetData 来轮询查询资源。 只要 GetData 返回S_FALSE,这意味着资源尚未返回答案。

GetData 的返回值实质上告诉你查询的状态。 可能的值为S_OK、S_FALSE和错误。 不要对处于生成状态的查询调用 GetData

  • S_OK表示资源(GPU 或驱动程序或运行时)已完成。 查询将返回到信号状态。 GetData返回答案(如果有)。
  • S_FALSE意味着资源(GPU 或驱动程序或运行时)尚无法返回答案。 这可能是因为 GPU 尚未完成或尚未看到工作。
  • 错误表示查询生成了无法恢复的错误。 如果设备在查询期间丢失,则情况可能是这样。 查询生成错误(非S_FALSE)后,必须重新创建查询,该查询将从信号状态重启查询序列。

如果不指定 D3DGETDATA_FLUSH,它提供更多的 up-to日期信息,而是提供零,如果查询处于颁发状态,则提供更轻量检查。 提供零将导致 GetData 不刷新命令缓冲区。 因此,必须小心避免无限循环(有关详细信息,请参阅 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 周期。

查询是在呈现期间要用于提高性能的优化。 因此,花些时间等待查询完成并不有益。 如果发出查询,并且当应用程序检查结果时尚未准备好结果,则尝试优化未成功,呈现应照常继续。

经典示例是在遮挡 Culling 期间。 与上述 循环时 相反,使用查询的应用程序可以实现遮挡剔除,以检查查询在需要结果时是否已完成。 如果查询尚未完成,请继续(作为最坏的情况),就好像要针对的对象没有遮挡(即可见)并呈现它。 代码如下所示。

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

高级主题