Interopérabilité direct3D 12
D3D12 peut être utilisé pour écrire des applications composantées.
- vue d’ensemble de l’interopérabilité
- raisons d’utiliser d’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é de type 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 d’utilisation de l’interopérabilité
Il existe plusieurs raisons pour lesquelles une application souhaite utiliser l’interopérabilité D3D12 avec d’autres API. Voici quelques exemples :
- Portage incrémentiel : souhaitant porter une application entière de D3D10 ou D3D11 vers D3D12, tout en le faisant fonctionner à des étapes intermédiaires du processus de portage (pour permettre le test et le débogage).
- Code de zone noire : le fait de vouloir laisser une partie particulière d’une application as-is lors du portage du reste du code. Par exemple, il se peut qu’il n’y ait pas besoin de porter des éléments d’interface utilisateur d’un jeu.
- Composants non modifiables : besoin d’utiliser des composants qui ne appartiennent pas à l’application, qui ne sont pas écrits dans la cible D3D12.
- Un 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 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 ClearState ou API 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 dans 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'
ISurfaceQueue
héritée, et les IDXGIKeyedMutex plus modernes.
Les différences entre ces scénarios sont exactement partagées entre les limites du composant. 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’eux a leurs propres complications lors du partage.
Partage d’une liste de commandes
La méthode d’interopérabilité la plus simple 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 d’effectuer 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 D3D11 Flush 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 pour un composant qui fonctionne sur ses propres appareils et/ou files d’attente de commandes sera d’accepter une ID3D12Fence ou de handle partagé, et la paire UINT64 au début de son travail, qu’elle attendra, puis une deuxième id3D12Fence ou handle partagé, et la paire UINT64 qu’elle signale quand tout le travail est terminé. Ce modèle correspond à l’implémentation actuelle des deux IDXGIKeyedMutex et la conception de synchronisation de modèle de basculement DWM/DXGI.
Partage de ressources
De loin, la partie la plus complexe 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 d’é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 est remis dans l’état par défaut lorsque le travail est effectué » ou peut avoir des règles plus complexes pour autoriser 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 du composant. 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 la propriété complète de la création et de la soumission de travail, sans la 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.
Les primitives de synchronisation de partage sont requises 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 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 à 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 les API graphiques Windows.