Freigeben über


Reduzieren der Latenz mit DXGI 1.3-Swapchains

Verwenden Sie DXGI 1.3, um die effektive Framelatenz zu reduzieren, indem Sie auf die Swapchain warten, um die entsprechende Zeit zu signalisieren, um mit dem Rendern eines neuen Frames zu beginnen. Spiele müssen in der Regel die niedrigste Latenz bereitstellen, die möglich ist, ab dem Zeitpunkt, zu dem die Spielereingabe empfangen wird, bis zum Zeitpunkt, an dem das Spiel auf diese Eingabe reagiert, indem die Anzeige aktualisiert wird. In diesem Thema wird eine Technik erläutert, die ab Direct3D 11.2 verfügbar ist, um die effektive Framelatenz in Ihrem Spiel zu minimieren.

Wie reduziert das Warten auf den Hintergrundpuffer die Latenz?

Mit der Flip-Modell-Swapchain werden Hintergrundpuffer "Flips" immer in die Warteschlange gestellt, wenn ihr Spiel IDXGISwapChain::P resent aufruft. Wenn die Renderingschleife Present() aufruft, blockiert das System den Thread, bis die Präsentation eines vorherigen Frames abgeschlossen ist, wodurch Platz für die Warteschlange für den neuen Frame erstellt wird, bevor er tatsächlich präsentiert wird. Dies führt zu einer zusätzlichen Latenz zwischen dem Zeitpunkt, zu dem das Spiel einen Frame zeichnet, und der Zeit, mit der das System diesen Frame anzeigen kann. In vielen Fällen erreicht das System ein stabiles Gleichgewicht, bei dem das Spiel immer fast einen vollständigen zusätzlichen Frame zwischen dem Rendern und der Zeit wartet, in der jeder Frame angezeigt wird. Es empfiehlt sich, zu warten, bis das System bereit ist, einen neuen Frame zu akzeptieren, und dann den Frame basierend auf aktuellen Daten zu rendern und den Frame sofort in die Warteschlange zu stellen.

Erstellen Sie eine swapchain mit wartebarem Swapchain mit dem DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT-Flag. Swapchains, die auf diese Weise erstellt wurden, können Ihre Renderingschleife benachrichtigen, wenn das System tatsächlich bereit ist, einen neuen Frame zu akzeptieren. Auf diese Weise kann Ihr Spiel basierend auf aktuellen Daten gerendert werden und das Ergebnis dann sofort in der aktuellen Warteschlange platzieren.

Schritt 1. Erstellen einer swapchain mit Wartemöglichkeit

Geben Sie das DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT Flag an, wenn Sie CreateSwapChainForCoreWindow aufrufen.

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

Hinweis

Im Gegensatz zu einigen Flags kann dieses Flag nicht mithilfe von ResizeBuffers hinzugefügt oder entfernt werden. DXGI gibt einen Fehlercode zurück, wenn dieses Flag anders festgelegt wird als beim Erstellen der Swapchain.

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

Schritt 2. Festlegen der Framelatenz

Legen Sie die Framelatenz mit der IDXGISwapChain2::SetMaximumFrameLatency-API fest, anstatt IDXGIDevice1::SetMaximumFrameLatency aufzurufen.

Standardmäßig ist die Framelatenz für swapchains auf 1 festgelegt, was zu der geringsten Latenz führt, aber auch die CPU-GPU-Parallelität reduziert. Wenn Sie eine höhere CPU-GPU-Parallelität benötigen, um 60 FPS zu erreichen , d. h., wenn die CPU und GPU jeweils weniger als 16,7 ms für ein Frameverarbeitungsrendering ausgeben, aber ihre kombinierte Summe größer als 16,7 ms ist – legen Sie die Framelatenz auf 2 fest. Auf diese Weise kann die GPU Arbeitswarteschlangen verarbeiten, die während des vorherigen Frames von der CPU in die Warteschlange gestellt werden, während gleichzeitig die CPU Renderbefehle für den aktuellen Frame unabhängig voneinander übermitteln kann.

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

Schritt 3. Abrufen des wartebaren Objekts aus der Swapchain

Rufen Sie IDXGISwapChain2::GetFrameLatencyWaitableObject auf, um das Wartezeithandle abzurufen. Das Wartekästchen ist ein Zeiger auf das wartebare Objekt. Speichern Sie dieses Handle für die Verwendung durch Ihre Renderingschleife.

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

Schritt 4. Warten Sie, bevor Sie jeden Frame rendern

Die Renderingschleife sollte warten, bis die Swapchain über das wartebare Objekt signalisiert, bevor sie mit dem Rendern jedes Frames beginnt. Dies schließt den ersten Frame ein, der mit der Swapchain gerendert wird. Verwenden Sie WaitForSingleObjectEx, indem Sie das in Schritt 2 abgerufene Wartekästchen bereitstellen, um den Start jedes Frames zu signalisieren.

Das folgende Beispiel zeigt die Renderschleife aus dem DirectXLatency-Beispiel:

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

Das folgende Beispiel zeigt den WaitForSingleObjectEx-Aufruf aus dem DirectXLatency-Beispiel:

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

Was sollte mein Spiel tun, während es wartet, bis die Swapchain vorhanden ist?

Wenn Ihr Spiel keine Aufgaben aufweist, die in der Renderschleife blockiert werden, kann es vorteilhaft sein, bis die Swapchain vorhanden ist, da sie Energie spart, was besonders auf mobilen Geräten wichtig ist. Andernfalls können Sie Multithreading verwenden, um Arbeit zu erledigen, während Ihr Spiel auf die Swapchain wartet. Hier sind nur einige Aufgaben, die Ihr Spiel ausführen kann:

  • Verarbeiten von Netzwerkereignissen
  • Aktualisieren der KI
  • CPU-basierte Physik
  • Rendern von verzögertem Kontext (auf unterstützten Geräten)
  • Laden von Ressourcen

Weitere Informationen zur Multithread-Programmierung in Windows finden Sie in den folgenden verwandten Themen.