Applications Direct2D multithread

Si vous développez des 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 à l’écran et d’un thread distinct pour le rendu hors connexion).

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

Notes

Si vous accédez à deux ressources Direct2D créées à partir de deux fabriques Direct2D à thread unique, cela ne provoque pas de conflits d’accès tant que les appareils Direct3D et les contextes d’appareil sous-jacents sont également distincts. Lorsque vous parlez de « l’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 les API Direct2D

Vous pouvez créer un instance de fabrique Direct2D multithread. 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) étant sérialisés par Direct2D, aucun conflit d’accès ne se produit. Si votre application appelle uniquement les 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 ici 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 Thread-Safe applications Direct2D avec un minimum d’appels Direct3D ou DXGI

Il est plus que souvent qu’une application Direct2D effectue également des appels Direct3D ou DXGI. Par exemple, un thread d’affichage dessine 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 appels Direct2D accèdent indirectement 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 connaissance et du contrôle de Direct2D, vous devez créer une fabrique Direct2D multithread, mais vous devez faire des actions pour éviter les conflits d’accès.

Le diagramme montre ici un conflit d’accès aux ressources Direct3D en raison d’un 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.

Notes

La protection des threads que Direct2D fournit (le verrou bleu de 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 que Direct2D utilise pour la synchronisation d’accès interne, et d’appliquer ce verrou lorsqu’un thread doit effectuer des appels Direct3D ou DXGI susceptibles de provoquer un conflit d’accès, comme illustré ici. En particulier, vous devez faire particulièrement attention au code qui utilise des exceptions ou un système précoce basé sur des codes de retour HRESULT. Pour cette raison, nous vous recommandons d’utiliser un modèle RAII (Resource Acquisition Is Initialization) pour appeler les méthodes Entrée et Leave .

Notes

Il est important de coupler les appels aux méthodes Entrée et Quitter , sinon, votre application peut s’interblocage.

 

Le code montre ici un exemple de verrouillage, puis de déverrouillage autour des appels Direct3D ou 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();
}

Notes

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 en être conscient 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 Leave , les appels sont protégés par le direct2D automatique et le verrou explicite, de sorte que l’application ne rencontre pas de conflit d’accès.

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

Garantie de l’atomicité des opérations avec état

Bien que les fonctionnalités de sécurité des threads de DirectX puissent vous assurer qu’aucun appel d’API individuel n’est effectué 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 à l’écran (par thread 0) et hors écran (par le thread 1) : la ligne 1 est « A est supérieure » et la ligne #2 « than B », qui seront toutes deux dessinées à l’aide d’un pinceau noir uni.
  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 petit » et « que A » respectivement, et a changé la couleur du 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 supérieur » en noir et « A » en rouge.

diagramme de threads d’écran activés et désactivés.

Dans la ligne supérieure, le thread 0 dessine avec les 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, le thread 0 répond à l’interaction utilisateur, met à jour les chaînes de texte et le pinceau, puis actualise l’écran. À ce stade, le thread 1 est bloqué. Dans la ligne inférieure, le rendu hors écran final 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, de sorte 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, comme le contenu du texte ou le pinceau de couleur unie dans l’exemple) ne changent pas lors du rendu. Dans cet exemple, vous devez conserver une copie de ces deux lignes de texte et le pinceau de couleur avant de dessiner. Ce faisant, vous garantissez que chaque thread dispose d’un contenu complet et cohérent à dessiner et à présenter.
  • Vous devez partager des ressources lourdes (comme des bitmaps et des graphiques d’effets complexes) qui sont initialisées une fois et qui ne sont jamais modifiées entre les threads pour améliorer les performances.
  • Vous pouvez partager des ressources légères (comme des pinceaux de couleur unie et des formats de texte) qui sont initialisées une fois, puis 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 des appels Direct3D ou DXGI, vous devez également acquérir explicitement, puis appliquer le verrou Direct2D pour protéger ces appels Direct3D ou DXGI. En outre, vous devez garantir l’intégrité du contexte en ayant une copie des ressources mutables pour chaque thread.