Partager via


Réduire la latence avec des chaînes d’échange DXGI 1.3

Utilisez DXGI 1.3 pour réduire la latence effective de l’image en attendant que la chaîne d’échange signale le moment approprié pour commencer à afficher une nouvelle image. Les jeux doivent généralement fournir la plus faible latence possible du moment où l’entrée du joueur est reçue, au moment où le jeu répond à cette entrée en mettant à jour l’affichage. Cette rubrique explique une technique disponible à partir de Direct3D 11.2 que vous pouvez utiliser pour réduire la latence effective des images dans votre jeu.

Comment l’attente sur la mémoire tampon back-end réduit-elle la latence ?

Avec la chaîne d’échange de modèle de retournement, les « flips » de la mémoire tampon de retour sont mis en file d’attente chaque fois que votre jeu appelle IDXGISwapChain ::P resent. Lorsque la boucle de rendu appelle Present(), le système bloque le thread jusqu’à ce qu’il soit terminé de présenter une image antérieure, ce qui rend la place pour mettre en file d’attente le nouveau frame, avant qu’il ne soit réellement présenté. Cela entraîne une latence supplémentaire entre le moment où le jeu dessine une trame et le temps que le système lui permet d’afficher ce cadre. Dans de nombreux cas, le système atteint un équilibre stable où le jeu attend toujours presque une trame supplémentaire complète entre le temps qu’il restitue et le temps qu’il présente chaque image. Il est préférable d’attendre que le système soit prêt à accepter une nouvelle trame, puis de restituer l’image en fonction des données actuelles et de mettre immédiatement en file d’attente l’image.

Créez une chaîne d’échange waitable avec l’indicateur de DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT . Les chaînes d’échange créées de cette façon peuvent notifier votre boucle de rendu lorsque le système est réellement prêt à accepter une nouvelle trame. Cela permet à votre jeu de s’afficher en fonction des données actuelles, puis de placer immédiatement le résultat dans la file d’attente actuelle.

Étape 1. Créer une chaîne d’échange pouvant être attendu

Spécifiez l’indicateur DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT lorsque vous appelez CreateSwapChainForCoreWindow.

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

Remarque

Contrairement à certains indicateurs, cet indicateur ne peut pas être ajouté ou supprimé à l’aide de ResizeBuffers. DXGI retourne un code d’erreur si cet indicateur est défini différemment du moment où la chaîne d’échange a été créée.

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

Étape 2. Définir la latence du frame

Définissez la latence d’image avec l’API IDXGISwapChain2 ::SetMaximumFrameLatency, au lieu d’appeler IDXGIDevice1 ::SetMaximumFrameLatency.

Par défaut, la latence d’image pour les chaînes d’échange pouvant être attendus est définie sur 1, ce qui entraîne la latence la moins possible, mais réduit également le parallélisme PROCESSEUR-GPU. Si vous avez besoin d’un parallélisme PROCESSEUR-GPU accru pour atteindre 60 FPS , autrement dit, si le processeur et le GPU dépensent chacun moins de 16,7 ms un travail de rendu de traitement d’images, mais leur somme combinée est supérieure à 16,7 ms — définissez la latence de l’image sur 2. Cela permet au GPU de traiter le travail mis en file d’attente par l’UC pendant l’image précédente, tout en permettant au processeur d’envoyer des commandes de rendu pour l’image actuelle indépendamment.

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

Étape 3. Obtenir l’objet pouvant être attendu à partir de la chaîne d’échange

Appelez IDXGISwapChain2 ::GetFrameLatencyWaitableObject pour récupérer le handle d’attente. Le handle d’attente est un pointeur vers l’objet pouvant être attendu. Stockez ce handle à utiliser par votre boucle de rendu.

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

Étape 4. Attendez avant de rendre chaque image

Votre boucle de rendu doit attendre que la chaîne d’échange signale via l’objet pouvant être attendu avant qu’elle commence à restituer chaque image. Cela inclut le premier cadre rendu avec la chaîne d’échange. Utilisez WaitForSingleObjectEx, en fournissant le handle d’attente récupéré à l’étape 2, pour signaler le début de chaque image.

L’exemple suivant montre la boucle de rendu de l’exemple 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’exemple suivant montre l’appel WaitForSingleObjectEx à partir de l’exemple 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’est-ce que mon jeu doit faire pendant qu’il attend que la chaîne d’échange soit présente ?

Si votre jeu n’a pas de tâches qui bloquent sur la boucle de rendu, il peut être avantageux d’attendre que la chaîne d’échange soit présente, car elle enregistre la puissance, ce qui est particulièrement important sur les appareils mobiles. Sinon, vous pouvez utiliser le multithreading pour accomplir le travail pendant que votre jeu attend que la chaîne d’échange soit présente. Voici quelques tâches que votre jeu peut effectuer :

  • Traiter les événements réseau
  • Mettre à jour l’IA
  • Physique basée sur l’UC
  • Rendu différé du contexte (sur les appareils pris en charge)
  • Chargement des ressources

Pour plus d’informations sur la programmation multithread dans Windows, consultez les rubriques connexes suivantes.