Pipelining
Pour mettre en œuvre la synchronisation correcte et la plus efficace, il est important de comprendre les blocs matériels du GPU XBOX Series X|S, du moment où il reçoit une commande pour lancer une action jusqu’à la fin de l’exécution de cette action. Le processeur de commandes (CP, Command Processor) est le bloc du GPU qui reçoit les commandes de la mémoire (en termes simples, les commandes que nous ajoutons dans les D3D12CommandLists) sous forme de paquets (qui peuvent être visualisés dans la vue de désassemblage des commandes de PIX). Le CP est un bloc complexe comportant de nombreux éléments, mais nous pouvons le simplifier en le divisant en trois :- Command Processor Fetcher (CPF), qui récupère les paquets en mémoire et les envoie au CPG ou au CPC pour traitement ultérieur.
- Command Processor Graphics (CPG), qui gère et traite les commandes pour le pipeline graphique.
- Command Processor for Compute (CPC), qui s’exécute en parallèle avec le CPG et gère et traite les commandes pour le pipeline de calcul (utilisé par le calcul asynchrone).

- Le PFP récupère les données des tampons d’index et gère les arguments indirects, les informations de prédication et la synchronisation avec le ME.
- Le ME peut déclencher des vidages, lancer des dessins et des répartitions, et dispose d’emplacements CPDMA pour lancer des copies.
- Le Constant Engine (CE) est un bloc à l’intérieur du CPG qui possède de la RAM servant à stocker les constantes nécessaires à chaque commande de dessin. Il copie ces constantes dans L2, puis signale au ME que les données sont prêtes (afin que le ME puisse retenir les commandes de dessin jusqu’à ce que les constantes soient en place). Le ME peut aussi signaler au CE que les dessins sont terminés afin qu’il puisse réutiliser ses ressources.
- Le CP Direct Memory Access (CPDMA) se charge d’effectuer les copies (depuis et vers la mémoire ou les registres) et peut être déclenché par le PFP, le ME ou le MEC. Il peut copier depuis et vers n’importe quelle mémoire virtuelle.
- Les événements de fin de nuanceur (EoS, End of Shader) s’exécutent une fois que toutes les vagues de la répartition ou du dessin sont terminées, mais n’attendent pas que le back-end de rendu ait terminé (dans le scénario du pipeline fixe).
- Le bas du pipeline (BoP, Bottom of Pipe) correspond au moment où tout le travail des nuanceurs et du back-end de rendu est terminé, mais ne donne aucune garantie quant aux écritures en mémoire.
- Pour le calcul, EoS et BoP sont identiques puisqu’il n’y a pas de travail du back-end de rendu.
- Clôtures (fences)
- Primitives de synchronisation composées d’au moins deux paquets. Le premier écrit une valeur de référence à un emplacement mémoire. Ce paquet est un événement qui peut être configuré pour s’exécuter comme un événement EoS ou comme un événement BoP. Le second paquet est une attente qui bloque un moteur (PFP ou ME), empêchant les paquets suivants de passer, tout en interrogeant continuellement l’emplacement mémoire jusqu’à ce que la valeur lue satisfasse une fonction de comparaison. À ce moment, le moteur bloqué est libéré et le travail peut se poursuivre.
- Vidage partiel
- Les vidages partiels font attendre le CP jusqu’à ce que tout le travail lié aux nuanceurs de sommets, de pixels ou de calcul (selon le paquet) soit terminé. Le ME bloque tous les paquets suivants jusqu’à ce qu’il reçoive un signal de la SPI.
- Pour la clôture, nous créerions une clôture EoS qui écrit en mémoire après le travail de calcul (le premier paquet de clôture viendrait donc après la répartition). Puis, le second paquet de clôture bloquerait le moteur ME jusqu’à ce que la valeur en mémoire soit mise à jour (nous ne pouvons pas bloquer sélectivement les étapes en aval du ME; dans ce scénario, le choix du moteur à bloquer sera donc ME ou PFP).
- Cela peut aussi être géré au moyen d’un vidage partiel CS, qui bloque le ME jusqu’à ce qu’il reçoive le signal que tout le travail des nuanceurs de calcul est terminé avant de débloquer le moteur. Le vidage partiel ne nécessite qu’un seul paquet, contre deux pour la clôture (il est donc moins coûteux en matière d’opérations mémoire). Mais la clôture offre plus de flexibilité quant au travail à attendre pendant que le moteur est bloqué (puisque l’écriture en mémoire peut avoir lieu après une répartition précise, plutôt qu’après tout le travail de calcul).

Mécanismes de synchronisation de niveau matériel offerts sur XBOX Series X|S :
Il est utile de connaître les paquets produits par le pilote lorsque nous utilisons des clôtures ou des vidages partiels (ou une API de synchronisation comme les barrières), puisque ceux-ci apparaissent dans les captures PIX chaque fois que nous examinons un événement de synchronisation (et plus de détails sur le masque de bits de chaque paquet sont visibles dans l’onglet de désassemblage des commandes).- ACQUIRE_MEM : ce paquet est utilisé par le pilote pour s’assurer qu’une surface n’est plus utilisée (commandes de dessin actives) et que tous les caches qui y sont associés sont vidés avant les opérations suivantes. Le pilote l’utilise pour les opérations qui nécessitent le vidage du CB et du DB (tout le travail CB/DB terminé) ainsi que l’invalidation d’un ou de plusieurs des caches suivants ($K, $I, L0, L1, L2, LM, caches CB, caches DB), et/ou la réécriture pour L2 (suivez ce lien pour la définition des caches). Son fonctionnement est le suivant : il attend d’abord que l’adresse mémoire en question ne soit plus référencée par du travail en cours, puis procède au vidage des caches qui y sont associés. Il peut bloquer le PFP ou le ME (ce paquet s’exécute en haut du pipeline).
- RELEASE_MEM : permet d’insérer un événement (fin de pipeline ou fin de nuanceur) dans le pipeline. Par exemple, il peut servir à mettre à jour une valeur de clôture ou à effectuer des opérations mémoire (invalidation ou vidage de caches).
- WAIT_REG_MEM : ce paquet sert à attendre qu’un emplacement dans l’espace des registres ou de la mémoire satisfasse une fonction de comparaison. Il peut servir à évaluer des clôtures. Le paquet permet de définir la fonction de comparaison (par exemple, equal ref vérifie si la valeur en mémoire correspond à la valeur de référence), et la référence est la valeur avec laquelle nous voulons comparer. Il définit aussi quel moteur attendra (PFP, ME ou CE), un masque pour définir les bits à comparer, et l’intervalle d’interrogation (la fréquence à laquelle le moteur vérifiera l’espace mémoire ou de registres, en cycles d’horloge).
- EVENT_WRITE : permet d’insérer des événements dans le pipeline. Les vidages partiels VS, PS et CS sont de type EVENT_WRITE.
- PFP_SYNC_ME : force le PFP à se synchroniser avec le bloc ME. Le PFP transmet le paquet au ME et retient les paquets entrants jusqu’à ce qu’il soit libéré par le ME (le ME a terminé le travail et la file FIFO entre le ME et le PFP est vide).
- DMA_DATA : permet au PFP ou au ME de demander au CP DMA de copier vers ou depuis des registres ou la mémoire (en un seul paquet atomique). Le moteur qui demande la copie (PFP ou ME) peut, de façon facultative, attendre les résultats. Il est possible de préciser si la copie est synchrone ou asynchrone.


Cohérence des caches
Ce qui précède ne raconte qu’une partie de l’histoire, puisque nous devons encore nous assurer que les accès mémoire sont effectués correctement. Le GPU possède une hiérarchie de mémoire beaucoup plus complexe que celle d’un processeur, étant donné sa charge de travail hautement parallèle. Ses nombreux caches ne sont pas cohérents; il faut donc en faire davantage en matière de synchronisation. Concepts importants lorsqu’il est question de caches :- Réécriture (WB, Writeback) : opération par laquelle une plage de lignes de cache (ou toutes) est réécrite dans la mémoire principale (ou dans un cache de niveau supérieur dans la hiérarchie).
- Invalidation : lorsqu’un cache est invalidé, une plage de ses lignes de cache (ou toutes) est marquée comme non valide, et les lectures futures sont transmises au cache suivant dans la hiérarchie pour récupérer une valeur à jour.
- Écriture immédiate (write-through) : les écritures ont lieu dans le cache et sont aussi transmises au niveau suivant.
- Bit modifié (dirty bit) : si une ligne de cache est marquée comme modifiée, cela signifie que le cache a été modifié et que la mémoire principale (et les autres caches) possèdent une copie périmée. Un vidage (WB) sera nécessaire (ainsi que le marquage de ces données comme non valides dans les autres caches afin qu’ils puissent récupérer la valeur à jour).

Les caches pertinents sur XBOX Series X|S sont
De plus, les différents caches adoptent des stratégies différentes quant au moment de la réécriture ou de l’invalidation :
Pour revenir à l’exemple de la transition d’une écriture CS à une lecture PS, nous pouvons maintenant examiner les caches. Les écritures UAV utilisent les caches L0-L1-L2 et LM. Les SRV utilisent aussi ces mêmes caches. Puisque nous passons d’un état d’écriture à un état de lecture, L0 et L1 seront invalidés. LM sera aussi invalidé puisque le nuanceur de pixels pourrait potentiellement y lire. (L’invalidation de LM aura lieu seulement si la ressource possède des métadonnées.)
