Table des matières
- Introduction
- Synchronisation du GPU sur XBOX Series X|S
- Les barrières en détail
- Configuration
- Différences avec la spécification PC
- Exemples de barrières améliorées
- Compatibilité avec les barrières de ressources héritées
- Prise en charge dans PIX et validation
Introduction
Les barrières améliorées (EB, Enhanced Barriers) sont un nouvel ensemble d’API D3D12 conçues pour résoudre certains des problèmes introduits par l’API héritée des barrières de ressources (concepts déroutants comme la promotion et la dégradation de l’état des ressources, problèmes de conception liés aux barrières d’aliasing, opérations de vidage excessives, etc.), tout en donnant aux développeurs un contrôle indépendant sur la synchronisation du travail du GPU. Elles offrent un type de barrière plus expressif que ce qui était possible avec les barrières de ressources héritées. Les fonctionnalités des barrières améliorées comprennent :
- Réduction de la latence de synchronisation et des vidages de cache excessifs.
- Aucune règle mystérieuse de promotion et de dégradation.
- Aliasing des ressources rapide et flexible.
- Abandon (discard) pendant la transition de barrière.
- Lecture/écriture simultanées et autocopie.
Synchronisation du GPU sur XBOX Series X|S
Avant d’aborder les barrières améliorées, une introduction à la synchronisation sur XBOX Series X|S est nécessaire. Il est important de comprendre les concepts et les mécanismes expliqués dans cette section, car ils constituent les éléments de base des barrières. Synchronisation du GPU sur XBOX Series X/S Nous pouvons maintenant plonger dans les barrières améliorées et expliquer plus en détail leur fonctionnement.
Les barrières en détail
Les barrières améliorées introduisent trois concepts utiles pour décider comment effectuer la transition d’une ressource (ou d’une zone mémoire) donnée. Il s’agit de Sync, Access et Layout. D’une certaine façon, ils remplacent le concept précédent d’état de ressource (Resource State) utilisé avec les barrières de ressources. Ils ne sont toutefois pas équivalents, car il est toujours possible d’exprimer un état de ressource comme une combinaison de Sync, Access et Layout, mais il n’est pas toujours possible d’exprimer une combinaison de Sync, Access et Layout comme un état de ressource. La nouvelle API est plus expressive. Sync (avant-après) sert à indiquer au pilote l’étape du pipeline dont il faut attendre la fin, et quel moteur doit être bloqué jusqu’à ce que les opérations de barrière soient terminées.
- Le tableau suivant montre comment certains indicateurs de synchronisation se traduisent en exigences de vidage (drain) ou de blocage d’étapes (consultez la section sur le pipelining). À noter : certains indicateurs de synchronisation de barrière désignent une étape générale (comme la copie), mais à l’interne, en fonction des données fournies par l’accès et la disposition, ils sont associés à des étapes précises (source de copie/destination de copie), ce qui génère des exigences de vidage/blocage plus adéquates. De plus, le tableau ci-dessous porte sur le cas des textures (pour les tampons, certaines étapes différeraient).
- Ces exigences sont ensuite analysées et transmises, avec d’autres primitives, aux fonctions SyncBottomOfPipe/SyncTopOfPipe afin de déterminer les actions de synchronisation finales (par exemple D3D12XBOX_SYNC_FLAG_PS_PARTIAL_FLUSH ou D3D12XBOX_SYNC_FLAG_IDLE). Il ne faut pas confondre ces indicateurs avec les indicateurs D3D12_BARRIER_SYNC, qui sont utilisés pour les appels de barrières améliorées.
- Ces exigences ne connaissent pas les mécanismes qui seront finalement utilisés pour les satisfaire; elles indiquent donc explicitement quel composant matériel doit être vidé/bloqué. Des composants ultérieurs pourraient constater, par exemple, que plusieurs vidages peuvent être accomplis par un seul événement EoP.
- Dans le contexte des barrières, il est utile de savoir quelles ressources correspondent à quels caches. Par exemple, si nous avons un AccessBefore de D3D12_ACCESS_RENDER_TARGET et un accès après de D3D12_ACCESS_SHADER_RESOURCE, nous voulons savoir quels caches invalider et/ou vider (si nécessaire). Le tableau suivant présente certains types de ressources fréquemment utilisés et la mémoire qu’ils utilisent.
- Les types de ressources ne sont pas le seul facteur déterminant quant aux caches utilisés par la ressource. Le type d’opération (scalaire ou vectorielle) compte également, et c’est le compilateur qui en décide. Une lecture dans un tampon de constantes, par exemple, peut passer par le chemin $K-L2 s’il s’agit d’une opération scalaire (SMEM), ou par L0-L1-L2 s’il s’agit d’une opération vectorielle (VMEM).
Layout (avant-après) décrit la disposition en mémoire de la ressource. Cela détermine si les ressources devront être décompressées/compressées lors de l’opération de barrière, ainsi que si une initialisation des métadonnées est nécessaire (pour les cibles de rendu et les tampons de profondeur/gabarit). Par exemple, une cible de rendu avec DCC activée pourrait devoir effectuer un DCC-Eliminate lors de sa transition vers SRV, et donc subir une décompression.
- Certains indicateurs de ressource et de barrière modifient les exigences de compression/décompression qui en résultent :
- Le pilote conserve deux tables qui associent le type de ressource aux opérations de compression/décompression. L’une de ces tables concerne les ressources créées avec D3D12XBOX_RESOURCE_FLAG_FORCE_TEXTURE_COMPATIBILITY, tandis que l’autre concerne les ressources non TC.
- Les EB introduisent D3D12_TEXTURE_BARRIER_FLAG_DISCARD qui, lorsqu’il est passé en argument à la méthode Barrier, force l’initialisation des métadonnées. Avant les EB, cela pouvait se faire au moyen d’une opération d’effacement effectuée avant la première utilisation de la ressource.
- Les cibles de rendu et les tampons de profondeur/gabarit doivent être créés respectivement avec D3D12_RESOURCE_FLAGS_ALLOW_RENDER_TARGET et D3D12_RESOURCE_FLAGS_ALLOW_DEPTH_STENCIL pour que les surfaces de métadonnées soient allouées correctement.
- Si vous voulez utiliser la DCC, la ressource doit être créée avec l’indicateur D3D12XBOX_RESOURCE_FLAG_ALLOW_DCC. La DCC impliquera des opérations de décompression lors de la transition d’un état d’écriture à un état de lecture, mais pas si la ressource est aussi créée avec D3D12XBOX_RESOURCE_FLAG_FORCE_TEXTURE_COMPATIBILITY.
- Le tableau suivant, qui associe les indicateurs de disposition aux indicateurs d’état de compression, aide à mieux comprendre comment les transitions d’entrée et de sortie peuvent entraîner des compressions/décompressions. Par exemple, passer de cible de rendu (rt) à ressource de nuanceur (sr) sur des ressources non TC nécessite une décompression, puisque rt possède plusieurs indicateurs de compression alors que sr est marqué uncompressedAll. Mais la même transition pour une ressource TC ne déclencherait pas de décompression, étant donné que sr possède un surensemble des indicateurs d’état de compression de rt.
Définir un avant et un après pour layout, access et sync nous donne une grande flexibilité pour faire des choses impossibles avec ResourceBarriers (sauf les barrières fractionnées, nous y reviendrons), tout en améliorant les cas où les barrières de ressources pouvaient entraîner des vidages partiels inutiles (barrières UAV) et, dans le pire des cas, mener à une corruption de la mémoire (barrières d’aliasing, lors d’une tentative de décompression sans être certain de la disposition actuelle de la mémoire). Cela correspond aussi mieux à la façon dont la synchronisation fonctionne sur le GPU, ce qui donne au développeur un contrôle plus précis.
La spécification définit également trois types de barrières :
- Barrières globales :
- S’appliquent à toute la mémoire. Leur définition ne prend pas de pointeur de ressource.
- Aucune information de disposition n’est passée en argument.
- Barrières de tampon
- Comme leur nom l’indique, les barrières de tampon servent à effectuer la transition des ressources de type tampon.
- Aucune disposition ni sous-ressource n’est spécifiée en paramètre (puisque les tampons n’utilisent ni l’une ni l’autre).
- Barrières de texture
- Transition pour les ressources de texture (cibles de rendu, tampons de profondeur/gabarit, textures SRV, textures UAV, etc.).
- La disposition est obligatoire; cette barrière peut donc déclencher des décompressions et des compressions.
- Peut spécifier une sous-ressource précise ou une plage.
- Pour les sous-ressources, il existe des limites en matière de décompression, puisqu’il est impossible de décompresser seulement une partie d’une ressource.
- LayoutBefore, LayourAfter : disposition de la mémoire avant et après la barrière. Détermine ce que nous allons compresser/décompresser. Utilisé uniquement par D3D12_TEXTURE_BARRIER.
- SyncBefore, SyncAfter : étape dont il faut attendre le vidage et étape à bloquer avant de poursuivre.
- AccessBefore, AccessAfter : indique quels caches nous cesserons d’utiliser et quels caches nous commencerons à utiliser à la suite de la transition.
- Subresource range — Permet de définir une plage de sous-ressources dont la barrière effectuera la transition. Utilisé uniquement par D3D12_TEXTURE_BARRIER.
- Resource — Le propriétaire de la mémoire dont nous effectuons la transition. Utilisé par D3D12_TEXTURE_BARRIER et D3D12_BUFFER_BARRIER.
- Texture Barrier flags — Les valeurs peuvent être D3D12_TEXTURE_BARRIER_FLAG_NONE et D3D12_TEXTURE_BARRIER_FLAG_DISCARD. Utilisé uniquement par D3D12_TEXTURE_BARRIER.
- Nous utilisons l’indicateur discard lors de l’activation d’une ressource, afin que ses métadonnées soient initialisées correctement.
- L’utilisation de cet indicateur permet aussi de regrouper l’initialisation des ressources. Les effacements peuvent se chevaucher, et les opérations de synchronisation des barrières sont regroupées.
Configuration
Pour utiliser les barrières améliorées, vous avez besoin des éléments suivants :
- GDK de mars 2023 ou plus récent.
- ID3D12GraphicsCommandList7 : nécessaire pour appeler la méthode Barrier. Prend une structure D3D12_BARRIER_GROUP et le numéro de groupe.
- Selon la spécification, l’interopérabilité entre les API de barrières améliorées et les D3D12_RESOURCE_STATES hérités est prise en charge. Cependant, les sous-ressources dans tout état hérité autre que D3D12_RESOURCE_STATE_COMMON doivent faire l’objet d’une transition vers D3D12_RESOURCE_STATE_COMMON avant d’être référencées au moyen d’une barrière améliorée.
- Créez un fichier vide dans le dossier Loose (répertoire de déploiement) et nommez-le D3D__DisableLegacyResourceBarrierTranslation.
- Déployez le jeu et assurez-vous que le fichier a bien été déployé sur la console.
- Remarque : dans ce mode, le pilote ne prend pas en charge le mélange de barrières héritées et de barrières améliorées.
Différences avec la spécification PC
- Sur XBOX, le vidage à la frontière d’ExecuteCommandList n’est pas émis; l’accès sans barrière n’est donc pas pris en charge.
- Cela s’explique par des raisons de performance, puisqu’un vidage complet du GPU est coûteux.
- Accès sans barrière dans la spécification
- Sous-ressources. Sur PC, les métadonnées sont suivies par sous-ressource, alors que ce n’est pas notre cas. Cela signifie que l’état de compression par sous-ressource ne peut pas être suivi. Par conséquent, les changements de disposition qui impliquent une compression ou une décompression donneront des résultats différents selon que la plage de ressources couvre (ou non) la ressource entière.
- Si une ressource dans une disposition compressée voit une ou plusieurs (mais pas toutes) de ses sous-ressources passer à une disposition non compressée, la ressource entière sera décompressée.
- Si une ressource dans une disposition non compressée voit une ou plusieurs (mais pas toutes) de ses sous-ressources passer à une disposition compressée, la ressource entière demeure non compressée.
- Nous prenons en charge des états de compression indépendants pour la profondeur et le gabarit, ce qui signifie que nous pouvons avoir une profondeur compressée et un gabarit non compressé, ou l’inverse.
- Les barrières fractionnées fonctionnent, mais elles ne sont pas implémentées comme des barrières fractionnées dans le pilote. Elles sont plutôt converties en barrières immédiates. Nous effectuons cette conversion parce que les barrières fractionnées, telles que définies par D3D12, imposent un coût mémoire par ressource et sont inutilement inefficaces sur notre matériel. Cependant, la conversion en barrières immédiates n’est pas aussi efficace qu’elle pourrait l’être.
- Cela est dû au fait que le pilote ne reçoit pas d’information complète sur la synchronisation avant et après. La conversion en barrière immédiate se fait du côté proche de la barrière (le côté éloigné est ignoré), de sorte que le pilote doit être pessimiste et bloquer à la fois le CE et le PFP, ce qui peut rendre les barrières fractionnées plus lentes que les barrières nativement immédiates.
- Par ailleurs, historiquement, très peu de gens utilisent les barrières fractionnées avec l’API héritée.
- Une façon de résoudre le coût mémoire par ressource à l’avenir pourrait être de permettre aux développeurs de fournir eux-mêmes cette mémoire.
- Il est prévu de revoir l’implémentation des barrières fractionnées à l’avenir.
- Nous recommandons aux utilisateurs d’implémenter leurs propres copies de textures dans des nuanceurs de calcul. Ils devront bien sûr gérer les dépendances, mais cela permettrait un travail non sérialisé et plus efficace.
- L’une des raisons pour lesquelles les copies de textures gérées par le pilote peuvent être moins performantes est qu’elles peuvent utiliser les caches CB/DB. Cela entraîne la nécessité d’effectuer une réécriture (WB) supplémentaire et d’invalider les caches CB et DB lors des transitions vers les copies de textures ou à partir de celles-ci.
Exemples de barrières améliorées
Bien que des améliorations automatiques des performances à l’exécution puissent être obtenues grâce à la capacité du pilote à traduire les appels de barrières de ressources héritées en équivalents de barrières améliorées, des gains supplémentaires peuvent être réalisés en utilisant directement les barrières améliorées. L’expressivité de l’API offre des occasions de réduire les latences de synchronisation et d’éliminer les vidages inutiles. Les exemples de code suivants montrent comment exprimer des appels de barrières de ressources héritées avec l’API des barrières améliorées afin de profiter de ces avantages supplémentaires.
- Barrières de ressources de transition
- Barrières de ressources d’accès non ordonné
- Autocopie
- Barrières de ressources d’aliasing
Barrières de ressources de transition
Dans les applications graphiques, les ressources mémoire sont souvent accédées dans différents contextes, que ce soit en tant que cibles de rendu, vues de ressources de nuanceur, vues de tampons de sommets ou dans d’autres types d’états. Cela explique le besoin fréquent de barrières de ressources de transition pour assurer une synchronisation et une cohérence de cache correctes. Toutefois, dans certains cas, les barrières de ressources de transition peuvent introduire des ralentissements inutiles, comme un vidage excessif du pipeline. Prenons l’extrait de code de barrière de ressource héritée suivant, qui montre la transition de l’état d’une ressource d’une vue de ressource de nuanceur (SRV) à une vue de cible de rendu (RTV) :


Barrières de ressources d’accès non ordonné
Les barrières de ressources héritées créées pour synchroniser les lectures et les écritures dans une vue d’accès non ordonné (UAV) sont également sujettes à des vidages superflus du pipeline du GPU, pour des raisons liées au pilote semblables à celles des barrières de ressources de transition héritées. Prenons un scénario où des textures avec mipmaps sont générées par un nuanceur de calcul, toutes écrites et lues dans une UAV de texture, et où le code de barrière de ressource d’accès non ordonné suivant est utilisé pour éviter les risques :

Autocopie
Auparavant, il était impossible d’écrire et de lire simultanément une sous-ressource, puisque la conception des barrières de ressources héritées interdit de combiner des bits de lecture seule et d’écriture. Cela signifiait que déplacer des données dans une ressource entre deux régions qui ne se chevauchent pas nécessitait la création d’une texture temporaire, davantage de barrières de transition pour passer de D3D12_RESOURCE_STATE_COPY_SOURCE à D3D12_RESOURCE_STATE_COPY_DEST, et davantage d’opérations de copie de sous-ressources. L’exemple de code ci-dessous illustre le travail nécessaire pour copier le quart supérieur gauche d’une texture dans son quart inférieur droit.

Barrières de ressources d’aliasing
Comme on le voit dans le code ci-dessous, tiré de la documentation MSDN publique de D3D12, l’aliasing de ressources placées avec l’API des barrières de ressources héritées consistait historiquement à spécifier uniquement les deux ressources qui partageaient la mémoire du tas. L’API des barrières améliorées peut aider à éviter d’avoir à bloquer tout le travail du GPU et à vider toutes les écritures, tout en fournissant des états de disposition explicites pour que le pilote ne décompresse pas de la mémoire non compressée (consultez ce lien pour en savoir plus sur ce dernier problème, sous « Aliasing Barriers Are Very Expensive »).Compatibilité avec les barrières de ressources héritées
L’API de barrières précédente (Resource Barriers) est toujours prise en charge, mais le pilote les traduit en barrières améliorées en arrière-plan. Plusieurs tests ont été effectués sur des captures PIX pour mesurer l’incidence de la traduction (par rapport à l’utilisation de l’implémentation précédente). Les résultats montrent que la traduction en EB offre des gains importants.

À noter : les gains dépendront grandement de la façon dont le moteur graphique est construit en matière de synchronisation et de placement des barrières.
Exemples où la traduction a apporté des améliorations par rapport aux barrières de ressources héritées :
Exemple 1 - Génération de vidages partiels par les barrières UAV
Barrière de ressource héritée

Exemple 2 - Aliasing
Barrière de ressource héritée

Tout ceci montre les gains que les développeurs peuvent obtenir uniquement grâce aux barrières améliorées, sans modifier leur code. Pour voir les gains possibles lors du passage à la nouvelle API Barrier, consultez la section Exemples de barrières améliorées.
Prise en charge dans PIX et validation
PIX prend en charge à la fois les barrières améliorées et les barrières de ressources héritées traduites. Pour les barrières améliorées, un événement PIX nommé Barrier est créé; il contient les paquets CP générés.





Validation
La validation sera offerte à la fois pour les barrières améliorées et pour les barrières de ressources héritées qui passent par la couche de traduction. La validation obtenue dépendra de l’API utilisée :- Pour les barrières améliorées, la parité de validation porte sur les scénarios couverts. Les barrières améliorées sont différentes des barrières de ressources; la validation ne reproduira donc pas exactement ce qu’elle faisait pour l’API héritée, mais garantira que toutes les propriétés avant et après sont correctes pour la transition. Par exemple :
- S’assurer que les ressources sont dans la bonne disposition au début d’une transition.
- Valider l’exactitude de l’utilisation de l’API (plus de détails ci-dessous).
- Pour les barrières de ressources héritées (traduites), la validation offre la parité avec la validation actuelle des barrières de ressources.
- Le périphérique sous-jacent prend en charge les barrières améliorées
- Il ne s’agit pas d’une liste de commandes de type bundle
- Avertissement si un compte (de groupes ou de barrières par groupe) est nul
- Vérification que les sous-ressources de la barrière de texture correspondent aux limites des sous-ressources de la texture
- Le type de barrière correspond au type de ressource
- LayoutBefore et AccessBefore correspondent à la disposition et à l’accès connus ou présumés de la ressource.
- SyncBefore, AccessBefore et LayoutBefore sont compatibles.
- SyncAfter, AccessAfter et LayoutAfter sont compatibles.
- Les barrières de fin de fractionnement correspondent aux barrières de début de fractionnement précédentes.
- Les barrières fractionnées de tampon et D3D12_BARRIER_LAYOUT_COMMON ne franchissent pas les frontières d’ExecuteCommandLists.
- Probablement un avertissement plutôt qu’une erreur, en raison des tolérances héritées
