Interopérabilité Direct3D 12

D3D12 peut être utilisé pour écrire des applications en composants.

Vue d’ensemble de l’interopérabilité

D3D12 peut être très puissant et permettre aux applications d’écrire du code graphique avec une efficacité de type console, mais toutes les applications n’ont pas besoin de réinventer la roue et d’écrire l’intégralité de leur moteur de rendu à partir de zéro. Dans certains cas, un autre composant ou bibliothèque l’a déjà fait mieux, ou dans d’autres cas, les performances d’une partie du code ne sont pas aussi critiques que son exactitude et sa lisibilité.

Cette section couvre les techniques d’interopérabilité suivantes :

  • D3D12 et D3D12, sur le même appareil
  • D3D12 et D3D12, sur différents appareils
  • D3D12 et toute combinaison de D3D11, D3D10 ou D2D sur le même appareil
  • D3D12 et toute combinaison de D3D11, D3D10 ou D2D sur différents appareils
  • D3D12 et GDI, ou D3D12 et D3D11 et GDI

Raisons de l’utilisation de l’interopérabilité

Il existe plusieurs raisons pour lesquelles une application souhaite une interopérabilité D3D12 avec d’autres API. Exemples :

  • Portage incrémentiel : souhaitant porter une application entière de D3D10 ou D3D11 vers D3D12, tout en la faisant fonctionner à des étapes intermédiaires du processus de portage (pour permettre le test et le débogage).
  • Code de zone noire : souhaitant laisser une partie particulière d’une application en l’état lors du portage du reste du code. Par exemple, il n’est peut-être pas nécessaire de porter des éléments d’interface utilisateur d’un jeu.
  • Composants immuables : nécessité d’utiliser des composants qui ne appartiennent pas à l’application et qui ne sont pas écrits dans la cible D3D12.
  • Nouveau composant : ne pas vouloir porter l’application entière, mais utiliser un nouveau composant écrit à l’aide de D3D12.

Il existe quatre techniques main pour l’interopérabilité dans D3D12 :

  • Une application peut choisir de fournir une liste de commandes ouverte à un composant, qui enregistre des commandes de rendu supplémentaires sur une cible de rendu déjà liée. Cela équivaut à fournir un contexte d’appareil préparé à un autre composant dans D3D11, et est idéal pour des choses telles que l’ajout d’interface utilisateur/texte à une mémoire tampon arrière déjà liée.
  • Une application peut choisir de fournir une file d’attente de commandes à un composant, ainsi qu’une ressource de destination souhaitée. Cela équivaut à utiliser les API ClearState ou DeviceContextState dans D3D11 pour fournir un contexte d’appareil propre à un autre composant. C’est ainsi que les composants comme D2D fonctionnent.
  • Un composant peut opter pour un modèle où il produit une liste de commandes, potentiellement en parallèle, que l’application est chargée de soumettre ultérieurement. Au moins une ressource doit être fournie au-delà des limites des composants. Cette même technique est disponible dans D3D11 à l’aide de contextes différés, bien que les performances de D3D12 soient plus souhaitables.
  • Chaque composant a ses propres files d’attente et/ou appareils, et l’application et les composants doivent partager des ressources et des informations de synchronisation entre les limites des composants. Ceci est similaire à l’hérité ISurfaceQueue, et à l’IDXGIKeyedMutex plus moderne.

Les différences entre ces scénarios sont ce qui est exactement partagé entre les limites des composants. L’appareil est supposé être partagé, mais comme il est essentiellement sans état, il n’est pas vraiment pertinent. Les objets clés sont la liste de commandes, la file d’attente de commandes, les objets de synchronisation et les ressources. Chacun d’entre eux a ses propres complications lors de leur partage.

Partage d’une liste de commandes

La méthode d’interopérabilité la plus simple nécessite le partage uniquement d’une liste de commandes avec une partie du moteur. Une fois les opérations de rendu terminées, la propriété de la liste de commandes revient à l’appelant. La propriété de la liste de commandes peut être tracée à travers la pile. Étant donné que les listes de commandes sont à thread unique, il n’existe aucun moyen pour une application de faire quelque chose d’unique ou d’innovant à l’aide de cette technique.

Partage d’une file d’attente de commandes

Probablement la technique la plus courante pour plusieurs composants partageant un appareil dans le même processus.

Lorsque la file d’attente de commandes est l’unité de partage, il doit y avoir un appel au composant pour lui faire savoir que toutes les listes de commandes en suspens doivent être envoyées immédiatement à la file d’attente de commandes (et toutes les files d’attente de commandes internes doivent être synchronisées). Cela équivaut à l’API D3D11 Flush et est le seul moyen pour l’application d’envoyer ses propres listes de commandes ou synchroniser des primitives.

Partage des primitives de synchronisation

Le modèle attendu pour un composant qui fonctionne sur ses propres appareils et/ou files d’attente de commandes sera d’accepter un ID3D12Fence ou un handle partagé, et une paire UINT64 au début de son travail, qu’il attendra, puis un deuxième id3D12Fence ou un handle partagé et une paire UINT64 qu’il signalera lorsque tout le travail est terminé. Ce modèle correspond à l’implémentation actuelle d’IDXGIKeyedMutex et à la conception de synchronisation de modèle inversé DWM/DXGI.

Partage des ressources

La partie la plus compliquée de l’écriture d’une application D3D12 qui tire parti de plusieurs composants est de loin la façon de gérer les ressources qui sont partagées entre les limites des composants. Cela est principalement dû au concept d’états de ressource. Bien que certains aspects de la conception de l’état des ressources soient destinés à traiter la synchronisation intra-liste de commandes, d’autres ont un impact entre les listes de commandes, affectant la disposition des ressources et les ensembles d’opérations valides ou les caractéristiques de performances d’accès aux données de ressource.

Il existe deux modèles de traitement de cette complication, qui impliquent essentiellement un contrat entre les composants.

  • Le contrat peut être défini par le développeur du composant et documenté. Cela peut être aussi simple que « la ressource doit être dans l’état par défaut lorsque le travail est démarré, et sera remise à l’état par défaut lorsque le travail est terminé » ou pourrait avoir des règles plus compliquées pour autoriser des choses telles que le partage d’une mémoire tampon de profondeur sans forcer les résolutions de profondeur intermédiaires.
  • Le contrat peut être défini par l’application au moment de l’exécution, au moment où la ressource est partagée entre les limites des composants. Il se compose des deux mêmes informations : l’état dans lequel la ressource se trouve lorsque le composant commence à l’utiliser, et l’état dans lequel le composant doit le laisser quand il se termine.

Choix d’un modèle d’interopérabilité

Pour la plupart des applications D3D12, le partage d’une file d’attente de commandes est probablement le modèle idéal. Il permet une propriété complète de la création et de la soumission du travail, sans la surcharge de mémoire supplémentaire liée à des files d’attente redondantes, et sans l’impact perf du traitement des primitives de synchronisation GPU.

Le partage des primitives de synchronisation est requis une fois que les composants doivent traiter différentes propriétés de file d’attente, telles que le type ou la priorité, ou une fois que le partage doit couvrir les limites du processus.

Le partage ou la production de listes de commandes ne sont pas largement utilisés en externe par des composants tiers, mais peuvent être largement utilisés dans les composants internes d’un moteur de jeu.

API d’interopérabilité

La rubrique Direct3D 11 sur 12 vous guide tout au long de l’utilisation d’une grande partie de la surface d’API liée aux types d’interopérabilité décrits dans cette rubrique.

Consultez également la méthode ID3D12Device::CreateSharedHandle , que vous pouvez utiliser pour partager des surfaces entre les API graphiques Windows.