Reduzir a latência com cadeias de troca DXGI 1.3
Use o DXGI 1.3 para reduzir a latência efetiva do quadro aguardando que a cadeia de troca sinalize o momento apropriado para começar a renderizar um novo quadro. Os jogos normalmente precisam fornecer a menor quantidade de latência possível desde o momento em que a entrada do jogador é recebida até quando o jogo responde a essa entrada atualizando a exibição. Este tópico explica uma técnica disponível a partir do Direct3D 11.2 que você pode usar para minimizar a latência efetiva de quadros em seu jogo.
Como a espera no buffer traseiro reduz a latência?
Com a cadeia de troca de modelo de inversão, os "flips" do buffer traseiro são enfileirados sempre que o jogo chama IDXGISwapChain::P resent. Quando o loop de renderização chama Present(), o sistema bloqueia o thread até que ele termine de apresentar um quadro anterior, abrindo espaço para enfileirar o novo quadro, antes que ele realmente se apresente. Isso causa latência extra entre o momento em que o jogo desenha um quadro e o momento em que o sistema permite que ele exiba esse quadro. Em muitos casos, o sistema alcançará um equilíbrio estável em que o jogo está sempre esperando quase um quadro extra completo entre o tempo que renderiza e o tempo que apresenta cada quadro. É melhor esperar até que o sistema esteja pronto para aceitar um novo quadro e, em seguida, renderizar o quadro com base nos dados atuais e enfileirar o quadro imediatamente.
Crie uma cadeia de troca de espera com o sinalizador DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT. As cadeias de troca criadas dessa maneira podem notificar seu loop de renderização quando o sistema estiver realmente pronto para aceitar um novo quadro. Isso permite que seu jogo seja renderizado com base nos dados atuais e, em seguida, coloque o resultado na fila atual imediatamente.
Etapa 1. Criar uma cadeia de troca de espera
Especifique o sinalizador DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT ao chamar CreateSwapChainForCoreWindow.
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // Enable GetFrameLatencyWaitableObject().
Observação
Ao contrário de alguns sinalizadores, esse sinalizador não pode ser adicionado ou removido usando ResizeBuffers. DXGI retornará um código de erro se esse sinalizador for definido de forma diferente de quando a cadeia de troca foi criada.
// If the swap chain already exists, resize it.
HRESULT hr = m_swapChain->ResizeBuffers(
2, // Double-buffered swap chain.
static_cast<UINT>(m_d3dRenderTargetSize.Width),
static_cast<UINT>(m_d3dRenderTargetSize.Height),
DXGI_FORMAT_B8G8R8A8_UNORM,
DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT // Enable GetFrameLatencyWaitableObject().
);
Etapa 2. Definir a latência do quadro
Defina a latência do quadro com a API IDXGISwapChain2::SetMaximumFrameLatency, em vez de chamar IDXGIDevice1::SetMaximumFrameLatency.
Por padrão, a latência de quadros para cadeias de troca aguardáveis é definida como 1, o que resulta na menor latência possível, mas também reduz o paralelismo CPU-GPU. Se você precisar de maior paralelismo CPU-GPU para atingir 60 FPS - ou seja, se a CPU e a GPU gastarem menos de 16,7 ms por trabalho de renderização de processamento de quadros, mas sua soma combinada for maior que 16,7 ms - defina a latência do quadro como 2. Isso permite que a GPU processe o trabalho enfileirado pela CPU durante o quadro anterior, ao mesmo tempo em que permite que a CPU envie comandos de renderização para o quadro atual de forma independente.
// Swapchains created with the DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT flag use their
// own per-swapchain latency setting instead of the one associated with the DXGI device. The
// default per-swapchain latency is 1, which ensures that DXGI does not queue more than one frame
// at a time. This both reduces latency and ensures that the application will only render after
// each VSync, minimizing power consumption.
//DX::ThrowIfFailed(
// swapChain2->SetMaximumFrameLatency(1)
// );
Etapa 3. Obter o objeto de espera da cadeia de troca
Chame IDXGISwapChain2::GetFrameLatencyWaitableObject para recuperar o identificador de espera. O identificador de espera é um ponteiro para o objeto de espera. Armazene esse identificador para uso pelo loop de renderização.
// Get the frame latency waitable object, which is used by the WaitOnSwapChain method. This
// requires that swap chain be created with the DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT
// flag.
m_frameLatencyWaitableObject = swapChain2->GetFrameLatencyWaitableObject();
Etapa 4. Aguarde antes de renderizar cada quadro
Seu loop de renderização deve aguardar que a cadeia de troca sinalize por meio do objeto de espera antes de começar a renderizar cada quadro. Isso inclui o primeiro quadro renderizado com a cadeia de troca. Use WaitForSingleObjectEx, fornecendo o identificador de espera recuperado na Etapa 2, para sinalizar o início de cada quadro.
O exemplo a seguir mostra o loop de renderização do exemplo DirectXLatency :
while (!m_windowClosed)
{
if (m_windowVisible)
{
// Block this thread until the swap chain is finished presenting. Note that it is
// important to call this before the first Present in order to minimize the latency
// of the swap chain.
m_deviceResources->WaitOnSwapChain();
// Process any UI events in the queue.
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
// Update app state in response to any UI events that occurred.
m_main->Update();
// Render the scene.
m_main->Render();
// Present the scene.
m_deviceResources->Present();
}
else
{
// The window is hidden. Block until a UI event occurs.
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
O exemplo a seguir mostra a chamada WaitForSingleObjectEx do exemplo DirectXLatency :
// Block the current thread until the swap chain has finished presenting.
void DX::DeviceResources::WaitOnSwapChain()
{
DWORD result = WaitForSingleObjectEx(
m_frameLatencyWaitableObject,
1000, // 1 second timeout (shouldn't ever occur)
true
);
}
O que meu jogo deve fazer enquanto aguarda a apresentação da cadeia de troca?
Se o seu jogo não tiver nenhuma tarefa que bloqueie no loop de renderização, deixá-lo aguardar a apresentação da cadeia de troca pode ser vantajoso porque economiza energia, o que é especialmente importante em dispositivos móveis. Caso contrário, você pode usar o multithreading para realizar o trabalho enquanto o jogo aguarda a apresentação da cadeia de troca. Aqui estão apenas algumas tarefas que seu jogo pode concluir:
- Processar eventos de rede
- Atualize a IA
- Física baseada em CPU
- Renderização de contexto adiado (em dispositivos compatíveis)
- Carregamento de ativos
Para obter mais informações sobre programação multithread no Windows, consulte os tópicos relacionados a seguir.