Condividi tramite


Ridurre la latenza con catene di scambio DXGI 1.3

Utilizzare DXGI 1.3 per ridurre la latenza effettiva dei frame attendendo che la catena di scambio segnali il tempo appropriato per iniziare a eseguire il rendering di un nuovo frame. I giochi in genere devono fornire la minore latenza possibile dal momento in cui viene ricevuto l'input del giocatore, a quando il gioco risponde a tale input aggiornando la visualizzazione. Questo argomento spiega una tecnica disponibile a partire da Direct3D 11.2 che è possibile utilizzare per ridurre al minimo la latenza effettiva dei frame nel gioco.

In che modo l'attesa nel buffer posteriore riduce la latenza?

Con la catena di scambio del modello flip, i "flip" del buffer posteriore vengono messi in coda ogni volta che il gioco chiama IDXGISwapChain::Present. Quando il ciclo di rendering chiama Present(), il sistema blocca il thread fino a quando non viene completato presentando un frame precedente, creando spazio per mettere in coda il nuovo frame, prima di presentarlo effettivamente. Ciò causa una latenza extra tra il momento in cui il gioco disegna un frame e il tempo in cui il sistema gli consente di visualizzare tale frame. In molti casi, il sistema raggiungerà un equilibrio stabile in cui il gioco resta sempre in attesa di quasi un intero frame extra tra il tempo di rendering e il tempo in cui presenta ciascun frame. È preferibile attendere che il sistema sia pronto ad accettare un nuovo frame, quindi eseguire il rendering del frame in base ai dati correnti e mettere in coda immediatamente il frame.

Creare una catena di scambio waitable con il flag DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT. Le catene di scambio create in questo modo possono notificare al ciclo di rendering quando il sistema è effettivamente pronto ad accettare un nuovo frame. Ciò consente al gioco di eseguire immediatamente il rendering in base ai dati correnti e quindi di inserire immediatamente il risultato nella coda presente.

Passaggio 1: Creare una catena di scambio waitable

Specificare il flag DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT quando si chiama CreateSwapChainForCoreWindow.

swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // Enable GetFrameLatencyWaitableObject().

Nota

A differenza di alcuni flag, questo flag non può essere aggiunto o rimosso utilizzando ResizeBuffers. DXGI restituisce un codice di errore se questo flag è impostato in modo diverso da quando è stata creata la catena di scambio.

// 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().
    );

Passaggio 2: Impostare la latenza dei frame

Impostare la latenza dei frame con l'API IDXGISwapChain2::SetMaximumFrameLatency invece di chiamare IDXGIDevice1::SetMaximumFrameLatency.

Per impostazione predefinita, la latenza dei frame per le catene di scambio waitable è impostata su 1, che comporta la minore latenza possibile, ma riduce anche il parallelismo CPU-GPU. Se è necessario aumentare il parallelismo CPU-GPU per ottenere 60 FPS, ovvero se la CPU e la GPU impiegano ciascuna meno di 16,7 ms per elaborare il lavoro di rendering di ogni frame, ma la loro somma combinata è maggiore di 16,7 ms, impostare la latenza dei frame su 2. Ciò consente alla GPU di elaborare il lavoro in coda dalla CPU durante il frame precedente, consentendo allo stesso tempo alla CPU di inviare comandi di rendering per il frame corrente in modo indipendente.

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

Passaggio 3: Ottenere l'oggetto waitable dalla catena di scambio

Chiamare IDXGISwapChain2::GetFrameLatencyWaitableObject per recuperare la wait handle. La wait handle è un puntatore all'oggetto waitable. Salvare questa handle per l'uso nel ciclo di rendering.

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

Passaggio 4: Attendere prima di eseguire il rendering di ogni frame

Il ciclo di rendering deve attendere la segnalazione della catena di scambio tramite l'oggetto waitable prima di iniziare a eseguire il rendering di ogni fotogramma. Ciò comprende il primo frame renderizzato con la catena di scambio. Utilizzare WaitForSingleObjectEx, specificando la wait handle recuperata nel Passaggio 2, per segnalare l'inizio di ogni frame.

L'esempio seguente mostra il ciclo di rendering dell'esempio 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);
    }
}

L'esempio seguente mostra la chiamata WaitForSingleObjectEx dall'esempio 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
        );
}

Cosa dovrebbe fare il gioco mentre attende che la catena di scambio presenti il frame?

Se il gioco non dispone di attività che bloccano il ciclo di rendering, lasciare che attenda che la catena di scambio presenti il frame può essere vantaggioso perché consente di risparmiare energia, cosa particolarmente importante nei dispositivi mobili. In caso contrario, è possibile utilizzare il multithreading per eseguire operazioni mentre il gioco attende che la catena presenti il frame. Ecco alcune attività che il gioco può completare:

  • Elaborare eventi di rete
  • Aggiornare l'intelligenza artificiale
  • Fisica basata sulla CPU
  • Rendering in contesto differito (nei dispositivi supportati)
  • Caricamento di asset

Per maggiori informazioni sulla programmazione multithreading in Windows, vedere i seguenti argomenti correlati.