Partager via


Applications Direct2D multithread

Si vous développez applications Direct2D, vous devrez peut-être accéder aux ressources Direct2D à partir de plusieurs threads. Dans d’autres cas, vous pouvez utiliser le multithreading pour obtenir de meilleures performances ou une meilleure réactivité (comme l’utilisation d’un thread pour l’affichage d’écran et un thread distinct pour le rendu hors connexion).

Cette rubrique décrit les meilleures pratiques pour le développement d’applications Direct2D multithreads sans rendu Direct3D. Les défauts logiciels causés par des problèmes d’accès concurrentiel peuvent être difficiles à suivre, et il est utile de planifier votre stratégie de multithreading et de suivre les meilleures pratiques décrites ici.

Note

Si vous accédez à deux ressources Direct2D créées à partir de deux fabriques Direct2D à thread unique différentes, il n’entraîne pas de conflits d’accès tant que les direct3D sous-jacents appareils et contextes d’appareil sont également distincts. Lorsque vous parlez d'« accès aux ressources Direct2D » dans cet article, cela signifie vraiment « accéder aux ressources Direct2D créées à partir du même appareil Direct2D », sauf indication contraire.

Développement d’applications Thread-Safe qui appellent uniquement des API Direct2D

Vous pouvez créer une instance multithread Direct2D factory. Vous pouvez utiliser et partager une fabrique multithread et toutes ses ressources à partir de plusieurs threads, mais les accès à ces ressources (via des appels Direct2D) sont sérialisés par Direct2D, donc aucun conflit d’accès ne se produit. Si votre application appelle uniquement des API Direct2D, cette protection est automatiquement effectuée par Direct2D dans un niveau granulaire avec une surcharge minimale. Code permettant de créer une fabrique multithread ici.

ID2D1Factory* m_D2DFactory;

// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
    D2D1_FACTORY_TYPE_MULTI_THREADED,
    &m_D2DFactory
);

L’image ci-dessous montre comment Direct2D sérialise deux threads qui effectuent des appels à l’aide uniquement de l’API Direct2D.

diagramme de deux threads sérialisés.

Développement de Thread-Safe Applications Direct2D avec des appels Direct3D ou DXGI minimal

Il est plus que souvent qu’une application Direct2D effectue également des appels Direct3D ou DXGI. Par exemple, un thread d’affichage dessine dans Direct2D, puis présente à l’aide d’une chaîne d’échange DXGI .

Dans ce cas, garantir la sécurité des threads est plus complexe : certains Direct2D appellent indirectement accéder aux ressources Direct3D sous-jacentes, qui peuvent être simultanément accessibles par un autre thread qui appelle Direct3D ou DXGI. Étant donné que ces appels Direct3D ou DXGI sont hors de la prise en charge et de la sensibilisation de Direct2D, vous devez créer une fabrique Direct2D multithread, mais vous devez éviter les conflits d’accès.

Le diagramme montre ici un conflit d’accès aux ressources Direct3D en raison du thread T0 accédant indirectement à une ressource via un appel Direct2D et T2 accédant directement à la même ressource via un appel Direct3D ou DXGI.

Note

La protection des threads qui direct2D fournit (le verrou bleu dans cette image) n’aide pas dans ce cas.

 

diagramme de protection des threads.

Pour éviter les conflits d’accès aux ressources ici, nous vous recommandons d’acquérir explicitement le verrou qui direct2D utilise pour la synchronisation d’accès interne et d’appliquer ce verrou lorsqu’un thread doit effectuer Direct3D ou les appels DXGI susceptibles de provoquer un conflit d’accès comme indiqué ici. En particulier, vous devez prendre soin du code qui utilise des exceptions ou un système précoce basé sur les codes de retour HRESULT. Pour cette raison, nous vous recommandons d’utiliser un modèle RAII (Initialisation de l’acquisition de ressources) pour appeler les méthodes Entrée et Quitter.

Note

Il est important que vous associez des appels aux Entrer et laisser méthodes, sinon votre application peut bloquer.

 

Le code ci-dessous montre un exemple de verrouillage, puis de déverrouillage autour de direct3D ou d’appels DXGI.

void MyApp::DrawFromThread2()
{
    // We are accessing Direct3D resources directly without Direct2D's knowledge, so we
    // must manually acquire and apply the Direct2D factory lock.
    ID2D1Multithread* m_D2DMultithread;
    m_D2DFactory->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread));
    m_D2DMultithread->Enter();
    
    // Now it is safe to make Direct3D/DXGI calls, such as IDXGISwapChain::Present
    MakeDirect3DCalls();

    // It is absolutely critical that the factory lock be released upon
    // exiting this function, or else any consequent Direct2D calls will be blocked.
    m_D2DMultithread->Leave();
}

Note

Certains appels Direct3D ou DXGI (notamment IDXGISwapChain ::P resent) peuvent acquérir des verrous et/ou déclencher des rappels dans le code de la fonction ou de la méthode appelante. Vous devez être conscient de cela et vous assurer que ce comportement ne provoque pas d’interblocages. Pour plus d’informations, consultez la rubrique vue d’ensemble de DXGI.

 

diagramme de verrouillage de thread direct2d et direct3d.

Lorsque vous utilisez les méthodes Entrée et Quitter, les appels sont protégés par les direct2D automatiques et le verrou explicite, de sorte que l’application n’a pas atteint le conflit d’accès.

Il existe d’autres approches pour contourner ce problème. Toutefois, nous vous recommandons de protéger explicitement direct3D ou d’appels DXGI avec le verrou Direct2D, car il offre généralement de meilleures performances car il protège la concurrence à un niveau beaucoup plus fin et avec une surcharge inférieure sous la couverture de Direct2D.

Garantir l’atomicité des opérations avec état

Bien que les fonctionnalités de sécurité des threads de DirectX puissent vous assurer qu’aucun des deux appels d’API individuels ne sont effectués simultanément, vous devez également vous assurer que les threads qui effectuent des appels d’API avec état n’interfèrent pas entre eux. Voici un exemple.

  1. Il existe deux lignes de texte que vous souhaitez afficher à la fois à l’écran (par thread 0) et hors écran (par thread 1) : la ligne #1 est « A est supérieure » et la ligne #2 est « supérieure à B », qui sera dessinée à l’aide d’un pinceau noir unie.
  2. Le thread 1 dessine la première ligne de texte.
  3. Le thread 0 réagit à une entrée utilisateur, met à jour les deux lignes de texte sur « B est plus petite » et « than A » respectivement, et change la couleur de pinceau en rouge unie pour son propre dessin ;
  4. Le thread 1 continue de dessiner la deuxième ligne de texte, qui est maintenant « than A », avec le pinceau de couleur rouge ;
  5. Enfin, nous obtenons deux lignes de texte sur la cible de dessin hors écran : « A est plus grand » en noir et « than A » en rouge.

un diagramme des threads d’écran activés et désactivés.

Dans la ligne supérieure, thread 0 dessine avec des chaînes de texte actuelles et le pinceau noir actuel. Le thread 1 termine uniquement le dessin hors écran sur la moitié supérieure.

Dans la ligne centrale, thread 0 répond à l’interaction utilisateur, met à jour les chaînes de texte et le pinceau, puis actualise l’écran. À ce stade, thread 1 est bloqué. Dans la ligne inférieure, le rendu final hors écran après le thread 1 reprend le dessin de la moitié inférieure avec un pinceau modifié et une chaîne de texte modifiée.

Pour résoudre ce problème, nous vous recommandons d’avoir un contexte distinct pour chaque thread afin que :

  • Vous devez créer une copie du contexte de l’appareil afin que les ressources mutables (c’est-à-dire les ressources qui peuvent varier pendant l’affichage ou l’impression, telles que le contenu du texte ou le pinceau de couleur unie dans l’exemple) ne changent pas lorsque vous affichez. Dans cet exemple, vous devez conserver une copie de ces deux lignes de texte et du pinceau de couleur avant de dessiner. Ainsi, vous garantissez que chaque thread dispose d’un contenu complet et cohérent pour dessiner et présenter.
  • Vous devez partager des ressources volumineuses (telles que des bitmaps et des graphiques d’effet complexes) qui sont initialisées une fois et qui ne sont jamais modifiées entre les threads pour augmenter les performances.
  • Vous pouvez partager des ressources légères (telles que des pinceaux de couleur unie et des formats de texte) qui sont initialisées une seule fois et qui ne sont jamais modifiées entre les threads ou non

Résumé

Lorsque vous développez des applications direct2D multithread, vous devez créer une fabrique Direct2D multithread, puis dériver toutes les ressources Direct2D de cette fabrique. Si un thread effectue appels Direct3D ou DXGI, vous devez également acquérir explicitement le verrou Direct2D pour protéger ces appels Direct3D ou DXGI. De plus, vous devez garantir l’intégrité du contexte en ayant une copie de ressources mutables pour chaque thread.