Partilhar via


Consultas (Direct3D 9)

Há vários tipos de consultas que são projetadas para consultar a status de recursos. O status de um determinado recurso inclui status de unidade de processamento gráfico (GPU), status de driver ou status de runtime. Para entender a diferença entre os diferentes tipos de consulta, você precisa entender os estados de consulta. O diagrama de transição de estado a seguir explica cada um dos estados de consulta.

diagrama mostrando transições entre estados de consulta

O diagrama mostra três estados, cada um definido por círculos. Cada uma das linhas sólidas são eventos controlados pelo aplicativo que causam uma transição de estado. A linha tracejada é um evento controlado por recursos que alterna uma consulta do estado emitido para o estado sinalizado. Cada um desses estados tem uma finalidade diferente:

  • O estado sinalizado é como um estado ocioso. O objeto de consulta foi gerado e está aguardando o aplicativo emitir a consulta. Depois que uma consulta for concluída e transferida de volta para o estado sinalizado, a resposta para a consulta poderá ser recuperada.
  • O estado de construção é como uma área de preparo para uma consulta. No estado de construção, uma consulta foi emitida (chamando D3DISSUE_BEGIN), mas ainda não fez a transição para o estado emitido. Quando um aplicativo emite um fim de consulta (chamando D3DISSUE_END), a consulta faz a transição para o estado emitido.
  • O estado emitido significa que o recurso que está sendo consultado tem o controle da consulta. Depois que o recurso conclui seu trabalho, o recurso faz a transição do computador de estado para o estado sinalizado. Durante o estado emitido, o aplicativo deve sondar para detectar a transição para o estado sinalizado. Depois que a transição para o estado sinalizado ocorrer, GetData retornará o resultado da consulta (por meio de um argumento) para o aplicativo.

A tabela a seguir lista os tipos de consulta disponíveis.

Tipo de consulta Evento Issue Buffer GetData Runtime Início implícito da consulta
BANDWIDTHTIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9BANDWIDTHTIMINGS Varejo/Depuração N/D
CACHEUTILIZATION D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9CACHEUTILIZATION Varejo/Depuração N/D
EVENTO D3DISSUE_END BOOL Varejo/Depuração Createdevice
INTERFACETIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9INTERFACETIMINGS Varejo/Depuração N/D
OCLUSÃO D3DISSUE_BEGIN, D3DISSUE_END DWORD Varejo/Depuração N/D
PIPELINETIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9PIPELINETIMINGS Varejo/Depuração N/D
RESOURCEMANAGER D3DISSUE_END D3DDEVINFO_ResourceManager Somente depuração Presente
timestamp D3DISSUE_END UINT64 Varejo/Depuração N/D
TIMESTAMPDISJOINT D3DISSUE_BEGIN, D3DISSUE_END BOOL Varejo/Depuração N/D
TIMESTAMPFREQ D3DISSUE_END UINT64 Varejo/Depuração N/D
VCACHE D3DISSUE_END D3DDEVINFO_VCACHE Varejo/Depuração Createdevice
VERTEXSTATS D3DISSUE_END D3DDEVINFO_D3DVERTEXSTATS Somente depuração Presente
VERTEXTIMINGS D3DISSUE_BEGIN, D3DISSUE_END D3DDEVINFO_D3D9STAGETIMINGS Varejo/Depuração N/D

 

Algumas das consultas exigem um evento de início e término, enquanto outras exigem apenas um evento final. As consultas que exigem apenas um evento final começam quando outro evento implícito ocorre (que está listado na tabela). Todas as consultas retornam uma resposta, exceto a consulta de evento cuja resposta é sempre TRUE. Um aplicativo usa o estado da consulta ou o código de retorno de GetData.

Criar uma consulta

Antes de criar uma consulta, você pode marcar para ver se o runtime dá suporte a consultas chamando CreateQuery com um ponteiro NULL como este:

IDirect3DQuery9* pEventQuery;

// Create a device pointer m_pd3dDevice

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

Esse método retornará um código de êxito se uma consulta puder ser criada; caso contrário, retornará um código de erro. Depois que CreateQuery for bem-sucedido, você poderá criar um objeto de consulta como este:

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

Se essa chamada for bem-sucedida, um objeto de consulta será criado. A consulta está essencialmente ociosa no estado sinalizado (com uma resposta não inicializada) aguardando para ser emitida. Quando terminar a consulta, libere-a como qualquer outra interface.

Emitir uma consulta

Um aplicativo altera um estado de consulta emitindo uma consulta. Aqui está um exemplo de emissão de uma consulta:

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

Uma consulta no estado sinalizado fará a transição desta forma quando emitida:

Tipo de problema Transições de consulta para o . . .
D3DISSUE_BEGIN Estado de construção.
D3DISSUE_END Estado emitido.

 

Uma consulta no estado de construção fará a transição desta forma quando for emitida:

Tipo de problema Transições de consulta para o . . .
D3DISSUE_BEGIN (Sem transição, permanece no estado de construção. Reinicia o colchete de consulta.)
D3DISSUE_END Estado emitido.

 

Uma consulta no estado emitido fará a transição desta forma quando emitida:

Tipo de problema Transições de consulta para o . . .
D3DISSUE_BEGIN Criando estado e reinicia o colchete de consulta.
D3DISSUE_END Estado emitido após abandonar a consulta existente.

 

Verificar o estado da consulta e obter a resposta para a consulta

GetData faz duas coisas:

  1. Retorna o estado da consulta no código de retorno.
  2. Retorna a resposta para a consulta em pData.

De cada um dos três estados de consulta, aqui estão os códigos de retorno GetData :

Estado de consulta Código de retorno GetData
Sinalizado S_OK
Construção Código do erro
Emitido S_FALSE

 

Por exemplo, quando uma consulta está no estado emitido e a resposta para a consulta não está disponível, GetData retorna S_FALSE. Quando o recurso conclui seu trabalho e o aplicativo emitiu uma extremidade de consulta, o recurso faz a transição da consulta para o estado sinalizado. Do estado sinalizado, GetData retorna S_OK o que significa que a resposta para a consulta também é retornada no pData. Por exemplo, aqui está a sequência de eventos para retornar o número de pixels (ou exemplos quando o multisampling está habilitado) desenhado em uma sequência de renderização:

  • Criar a consulta.
  • Emita um evento begin.
  • Desenhe alguma coisa.
  • Emitir um evento final.

Veja a seguir a sequência de código correspondente:

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.

Essas linhas de código fazem várias coisas:

  • Chame GetData para retornar o número de pixels/amostras desenhados.
  • Especifique D3DGETDATA_FLUSH para permitir que o recurso faça a transição da consulta para o estado sinalizado.
  • Sondar o recurso de consulta chamando GetData de um loop. Desde que GetData retorne S_FALSE, isso significa que o recurso ainda não retornou a resposta.

O valor retornado de GetData essencialmente informa em que estado a consulta é. Os valores possíveis são S_OK, S_FALSE e um erro. Não chame GetData em uma consulta que esteja no estado de construção.

  • S_OK significa que o recurso (GPU, driver ou runtime) foi concluído. A consulta está retornando para o estado sinalizado. A resposta (se houver) está sendo retornada por GetData.
  • S_FALSE significa que o recurso (GPU, driver ou runtime) ainda não pode retornar uma resposta. Isso pode ocorrer porque a GPU não foi concluída ou ainda não viu o trabalho.
  • Um erro significa que a consulta gerou um erro do qual não pode ser recuperada. Esse pode ser o caso se o dispositivo for perdido durante uma consulta. Depois que uma consulta tiver gerado um erro (diferente de S_FALSE), a consulta deverá ser recriada, o que reiniciará a sequência de consulta do estado sinalizado.

Em vez de especificar D3DGETDATA_FLUSH, que fornece informações mais atualizadas, você pode fornecer zero, o que é um marcar mais leve se a consulta estiver no estado emitido. O fornecimento de zero fará com que GetData não libere o buffer de comando. Por esse motivo, é necessário ter cuidado para evitar loops infinitos (consulte GetData para obter detalhes). Como as filas de runtime funcionam no buffer de comandos, D3DGETDATA_FLUSH é um mecanismo para liberar o buffer de comando para o driver (e, portanto, a GPU; consulte Criação de perfil com precisão de chamadas à API Direct3D (Direct3D 9)). Durante a liberação do buffer de comando, uma consulta pode fazer a transição para o estado sinalizado.

Exemplo: uma consulta de evento

Uma consulta de evento não dá suporte a um evento begin.

  • Criar a consulta.
  • Emitir um evento final.
  • Sondar até que a GPU esteja ociosa.
  • Emitir um evento final.
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 ))
    ;

Essa é a sequência de comandos que uma consulta de evento usa para criar o perfil de chamadas à API (interface de programação de aplicativo) (consulte Criação de perfil com precisão de chamadas à API Direct3D (Direct3D 9)). Essa sequência usa marcadores para ajudar a controlar a quantidade de trabalho no buffer de comando.

Observe que os aplicativos devem prestar atenção especial ao grande custo associado à liberação do buffer de comando porque isso faz com que o sistema operacional mude para o modo kernel, incorrendo em uma penalidade de desempenho considerável. Os aplicativos também devem estar cientes de como desperdiçar ciclos de CPU aguardando a conclusão das consultas.

As consultas são uma otimização a ser usada durante a renderização para aumentar o desempenho. Portanto, não é benéfico gastar tempo aguardando a conclusão de uma consulta. Se uma consulta for emitida e se os resultados ainda não estiverem prontos no momento em que o aplicativo os verificar, a tentativa de otimização não terá êxito e a renderização deverá continuar normalmente.

O exemplo clássico disso é durante o abate de Oclusão. Em vez do loop while acima, um aplicativo que usa consultas pode implementar o abate de oclusão para marcar para ver se uma consulta foi concluída quando precisa do resultado. Se a consulta não tiver sido concluída, continue (como o pior cenário) como se o objeto que está sendo testado em relação não estivesse visível (ou seja, ele está visível) e renderize-o. O código seria semelhante ao seguinte.

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

Tópicos Avançados