查询 (Direct3D 9)

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

显示查询状态之间转换的关系图

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

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

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

查询类型 问题事件 GetData 缓冲区 运行时 查询的隐式开始
BANDWIDTHTIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9BANDWIDTHTIMINGS 零售/调试 空值
CACHEUTILIZATION D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9CACHEUTILIZATION 零售/调试 空值
事件 D3DISSUE_END BOOL 零售/调试 CreateDevice
INTERFACETIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9INTERFACETIMINGS 零售/调试 空值
闭塞 D3DISSUE_BEGIND3DISSUE_END DWORD 零售/调试 空值
PIPELINETIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9PIPELINETIMINGS 零售/调试 空值
RESOURCEMANAGER D3DISSUE_END D3DDEVINFO_ResourceManager 仅调试 目前
TIMESTAMP D3DISSUE_END UINT64 零售/调试 空值
TIMESTAMPDISJOINT D3DISSUE_BEGIND3DISSUE_END BOOL 零售/调试 空值
TIMESTAMPFREQ D3DISSUE_END UINT64 零售/调试 空值
VCACHE D3DISSUE_END D3DDEVINFO_VCACHE 零售/调试 CreateDevice
VERTEXSTATS D3DISSUE_END D3DDEVINFO_D3DVERTEXSTATS 仅调试 目前
VERTEXTIMINGS D3DISSUE_BEGIND3DISSUE_END D3DDEVINFO_D3D9STAGETIMINGS 零售/调试 空值

 

某些查询需要 begin 和 end 事件,而其他查询只需要结束事件。 仅需要结束事件的查询在表) 中列出的另一个隐式事件 (开始。 所有查询都返回答案,其答案始终为 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 (无转换,保持为建筑状态。重启查询 bracket.)
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。 提供零将导致 GetData 不刷新命令缓冲区。 出于此原因,必须小心避免无限循环 (请参阅 GetData 了解) 的详细信息。 由于运行时队列在命令缓冲区中工作, D3DGETDATA_FLUSH 是一种将命令缓冲区刷新到驱动程序 (因此 GPU 的机制;请参阅 准确分析 Direct3D API 调用 (Direct3D 9) ) 。 在命令缓冲区刷新期间,查询可能会转换为信号状态。

示例:事件查询

事件查询不支持 begin 事件。

  • 创建查询。
  • 发出结束事件。
  • 轮询,直到 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();
}

高级主题