Interopérabilité Direct3D 12
D3D12 peut être utilisé pour écrire des applications composantes.
- Vue d’ensemble de l’interopérabilité
- Raisons de l’utilisation de l’interopérabilité
- API d’interopérabilité
- Rubriques connexes
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é semblable à la console, mais pas toutes les applications doivent réinventer la roue et é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 décrit 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 utiliser l’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 activer le test et le débogage).
- Code de zone noire : le fait de vouloir laisser une partie particulière d’une application telle quelle 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 non modifiables : nécessité d’utiliser des composants qui ne appartiennent pas à l’application, qui ne sont pas écrits dans la cible D3D12.
- Nouveau composant : ne souhaitant pas porter l’ensemble de l’application, mais souhaitant utiliser un nouveau composant écrit à l’aide de D3D12.
Il existe quatre techniques principales 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 les éléments tels que l’ajout de l’interface utilisateur/du texte à une mémoire tampon back-tampon 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 responsable de la soumission ultérieurement. Au moins une ressource doit être fournie entre les 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 possède sa ou 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éritage
ISurfaceQueue
, et l’IDXGIKeyedMutex plus moderne.
Les différences entre ces scénarios sont celles qui sont exactement partagées entre les limites des composants. L’appareil est supposé être partagé, mais étant donné qu’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’eux a leurs propres complications lors de leur partage.
Partage d’une liste de commandes
La méthode la plus simple d’interopérabilité nécessite de partager uniquement 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 via 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 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 attente 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 de vidage D3D11 et est la seule façon dont l’application peut envoyer ses propres listes de commandes ou synchroniser des primitives.
Partage des primitives de synchronisation
Le modèle attendu d’un composant qui fonctionne sur ses propres appareils et/ou files d’attente de commandes sera d’accepter un handle ID3D12Fence ou partagé, et la paire UINT64 au début de son travail, qu’elle attendra, puis une deuxième id3D12Fence ou un handle partagé, et la paire UINT64 qu’elle signale lorsque tout le travail est terminé. Ce modèle correspond à l’implémentation actuelle d’IDXGIKeyedMutex et de la conception de synchronisation de modèle inverse DWM/DXGI.
Partage des ressources
De loin, la partie la plus compliquée de l’écriture d’une application D3D12 qui tire parti de plusieurs composants est 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 ressources. Bien que certains aspects de la conception de l’état des ressources soient destinés à traiter la synchronisation intra-commande-list, d’autres ont un impact entre les listes de commandes, affectant la disposition des ressources et les ensembles valides d’opérations ou de 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 remis dans l’état par défaut lorsque le travail est effectué » ou peut avoir des règles plus complexes pour permettre le partage d’une mémoire tampon de profondeur sans forcer la résolution de profondeur intermédiaire.
- 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 sera utilisée lorsque le composant commence à l’utiliser, et l’état dans lequel le composant doit le laisser à la fin.
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 la propriété complète de la création et de la soumission de travail, sans surcharge de mémoire supplémentaire d’avoir des files d’attente redondantes et sans l’impact de perf sur la gestion des primitives de synchronisation GPU.
Le partage de primitives de synchronisation est requis une fois que les composants doivent gérer 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 de 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 à un moteur de jeu.
API d’interopérabilité
La rubrique Direct3D 11 sur 12 vous guide tout au long de l’utilisation de la 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 Windows API graphiques.