Reducir la latencia con cadenas de intercambio de DXGI 1.3
Use DXGI 1.3 para reducir la latencia efectiva de fotogramas esperando a que la cadena de intercambio indique el tiempo adecuado para empezar a representar un nuevo fotograma. Los juegos normalmente necesitan proporcionar la menor cantidad de latencia posible desde el momento en que se recibe la entrada del jugador, a cuando el juego responde a esa entrada actualizando la pantalla. En este tema se explica una técnica disponible a partir de Direct3D 11.2 que puedes usar para minimizar la latencia efectiva de fotogramas en tu juego.
¿Cómo se reduce la latencia del búfer de reserva?
Con la cadena de intercambio de modelos flip, el búfer de retroceso "volteas" se pone en cola cada vez que el juego llama a IDXGISwapChain::P resent. Cuando el bucle de representación llama a Present(), el sistema bloquea el subproceso hasta que se realiza la presentación de un marco anterior, haciendo espacio para poner en cola el nuevo fotograma, antes de que se presente realmente. Esto provoca una latencia adicional entre el momento en que el juego dibuja un fotograma y la hora en que el sistema le permite mostrar ese fotograma. En muchos casos, el sistema alcanzará un equilibrio estable donde el juego siempre espera casi un marco adicional completo entre el tiempo que representa y el tiempo que presenta cada fotograma. Es mejor esperar hasta que el sistema esté listo para aceptar un nuevo fotograma y, a continuación, representar el marco en función de los datos actuales y poner en cola el fotograma inmediatamente.
Cree una cadena de intercambio esperable con la marca DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT. Las cadenas de intercambio creadas de esta manera pueden notificar al bucle de representación cuando el sistema está listo para aceptar un nuevo fotograma. Esto permite que el juego se represente en función de los datos actuales y, a continuación, coloque el resultado en la cola actual inmediatamente.
Paso 1. Creación de una cadena de intercambio esperable
Especifique la marca DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT al llamar a CreateSwapChainForCoreWindow.
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // Enable GetFrameLatencyWaitableObject().
Nota:
A diferencia de algunas marcas, esta marca no se puede agregar ni quitar mediante ResizeBuffers. DXGI devuelve un código de error si esta marca se establece de forma diferente a cuando se creó la cadena de intercambio.
// 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().
);
Paso 2. Establecimiento de la latencia de fotogramas
Establezca la latencia de fotogramas con la API IDXGISwapChain2::SetMaximumFrameLatency, en lugar de llamar a IDXGIDevice1::SetMaximumFrameLatency.
De forma predeterminada, la latencia de fotogramas para las cadenas de intercambio esperables se establece en 1, lo que da como resultado la menor latencia posible, pero también reduce el paralelismo de CPU-GPU. Si necesita un aumento del paralelismo de CPU-GPU para lograr 60 FPS, es decir, si la CPU y la GPU gastan menos de 16,7 ms, un trabajo de representación de procesamiento de fotogramas, pero su suma combinada es mayor que 16,7 ms, establezca la latencia de fotogramas en 2. Esto permite que la GPU procese el trabajo en cola por la CPU durante el fotograma anterior, al mismo tiempo que permite que la CPU envíe comandos de representación para el marco actual de forma independiente.
// 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)
// );
Paso 3. Obtener el objeto esperable de la cadena de intercambio
Llame a IDXGISwapChain2::GetFrameLatencyWaitableObject para recuperar el identificador de espera. El identificador de espera es un puntero al objeto que se puede esperar. Almacene este identificador para usarlo en el bucle de representación.
// 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();
Paso 4. Espere antes de representar cada fotograma
El bucle de representación debe esperar a que la cadena de intercambio señale a través del objeto que se puede esperar antes de que comience a representar cada fotograma. Esto incluye el primer fotograma representado con la cadena de intercambio. Use WaitForSingleObjectEx, proporcionando el identificador de espera recuperado en el paso 2, para indicar el inicio de cada fotograma.
En el ejemplo siguiente se muestra el bucle render del ejemplo 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);
}
}
En el ejemplo siguiente se muestra la llamada WaitForSingleObjectEx del ejemplo 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
);
}
¿Qué debe hacer mi juego mientras espera a que la cadena de intercambio presente?
Si el juego no tiene tareas que bloqueen el bucle de representación, dejar que espere a que la cadena de intercambio presente puede ser ventajosa porque ahorra energía, lo que es especialmente importante en los dispositivos móviles. De lo contrario, puedes usar multithreading para realizar el trabajo mientras tu juego espera a que la cadena de intercambio esté presente. Estas son solo algunas tareas que tu juego puede completar:
- Procesar eventos de red
- Actualización de la inteligencia artificial
- Física basada en CPU
- Representación de contexto diferido (en dispositivos compatibles)
- Carga de recursos
Para obtener más información sobre la programación multiproceso en Windows, consulte los siguientes temas relacionados.