Aller au contenu

Les cartes graphiques/Le rasterizeur

Un livre de Wikilivres.
Illustration du principe de la rasterization. La surface correspondant à l'écran est subdivisée en pixels carrés, de coordonnées x et y. La caméra est placée au point e. Pour chaque pixel, on trace une droite qui part de la caméra et qui passe par le pixel considéré. L'intersection entre une surface et cette droite se fait en un point, appartenant à un triangle.

À ce stade du pipeline, les sommets ont été regroupés en primitives. Vient alors l'étape de rasterization, durant laquelle chaque pixel de l'écran se voit attribuer un ou plusieurs triangle(s). Cela signifie que sur le pixel en question, c'est le triangle attribué au pixel qui s'affichera. Pour mieux comprendre quels triangles sont associés à tel pixel, imaginez une ligne droite qui part de caméra et qui passe par un pixel sur le plan de l'écran. Cette ligne intersecte 0, 1 ou plusieurs objets dans la scène 3D. Les triangles situés ces intersections entre cette ligne et les objets rencontrés seront associé au pixel correspondant.

Il est rare qu'on ne trouve qu'un seul triangle sur la trajectoire d'un pixel : c'est notamment le cas quand plusieurs objets sont l'un derrière l'autre. Si vous tracez une demi-droite dont l'origine est la caméra, et qui passe par le pixel, il arrive qu'elle intersecte la géométrie en plusieurs points. Et n'allez pas croire que seul l'objet situé devant les autres détermine à lui seul la couleur du pixel : n'oubliez pas que certains objets sont transparents ! Même avec des objets opaques, on doit calculer un pseudo-pixel pour chaque triangle. Les pseudo-pixels en question sont appelés des fragments. Pour chaque pixel, il y a un fragment par triangle situé "sur ce pixel". Les fragments attribués à un même pixel sont combinés pour obtenir la couleur finale de ce pixel. Mais cela s'effectuera assez loin dans le pipeline graphique, et nous reviendrons dessus en temps voulu.

L'étape de rastérisation contient plusieurs étapes distinctes, que nous allons voir dans ce chapitre. C'est lors de cette phase que la perspective est gérée, en fonction de la position de la caméra. Diverses opérations de clipping et de culling, qui éliminent les triangles non-visibles à l'écran, se font aussi après la rasterization ou pendant. La quasi-totalité des cartes graphiques récentes incorporent un circuit de zastérization, appelé le rasterizeur. Les seules exceptions sont les cartes graphiques très anciennes, mais aussi certaines cartes graphiques intégrées des processeurs Intel datant des années 2010. De nos jours, aucune carte graphique, même bas de gamme ou intégrée, n'est dans ce cas.

Le viewport clipping

[modifier | modifier le wikicode]
Volume délimité par la caméra (view frustum).

À la suite l'assemblage des primitives, une phase de view frustum culling élimine tout ce qui n'est pas dans le champ de vision de la caméra. Pour rappel, le view frustrum est délimité par six plans : quatre pour les bords de l'écran, un near plane pour les objets trop proches, un far plane pour les objets lointains. Le view frustum culling élimine tout ce qui est situé en-dehors du view frustum, du champ de vision de la caméra.

La détermination de ce qui est dans le champ de vision se fait triangle par triangle. Un triangle est soit totalement dans le champ de vision, soit totalement en dehors, soit partiellement dans le champ de vision. Les deux premiers cas sont triviaux à gérer : le triangle est envoyé à la rastérisation s'il est totalement visible, éliminé s'il est totalement invisible. Les triangles partiellement visibles sont un cas à part.

Deux grandes solutions sont possibles pour les triangles partiellement visibles. La première découpe ces triangles en plusieurs triangles, tous présents intégralement dans le champ de vision. Mais il n'est pas certain que les GPU modernes aient des circuits dédiés à cette opération, aussi nous laissons le sujet de côté. La seconde est simplement de déléguer le travail au rastériseur et c'est cette solution qui est souvent utilisée sur les GPU modernes.

Clipping/View frustum culling dans le cadre d'un écran de forme carrée (en gris).

Le near et far plane clipping

[modifier | modifier le wikicode]

La gestion des near et far plane est conceptuellement la plus simple, car elle ne se préoccupe de la coordonnée de profondeur. La profondeur d'un sommet est comparé à deux seuils : un pour le near plane, un autre pour le far plane. Si sa profondeur est entre les deux seuil, le sommet est visible. La comparaison est faite pour les trois sommet d'un triangle et une décision est prise. Si les trois sommets sont visible, le triangle est intégralement visible, on l'envoie au rastériseur. Si les trois sommets sont invisibles, le triangle n'est pas dans le champ de vision, on l'élimine. Si une partie seulement des sommets est visible, alors le triangle est partiellement visible, on délègue le cas au rastériseur.

De rares applications demandent de ne pas tenir compte du near et du far plane. Le champ de vision est alors une pyramide infinie, sans limite. Un exemple est celui des ombres volumétriques, autrefois utilisées dans quelques moteurs graphiques, le meilleur exemple étant celui de DOOM 3. L'extension OpenGL GL_EXT_depth_clamp permettait de se passer de ces deux plans, afin de rendre une pyramide parfaite.

Le viewport clipping

[modifier | modifier le wikicode]

La gestion du viewport est elle plus compliquée, mais suit une sorte de dérivé de l'algorithme de Cohen–Sutherland, adapté pour des triangles. Pour comprendre ce que fait cet algorithme, il faut savoir une chose importante. En sortie des étapes de transformation, chaque sommet a trois coordonnées x, y et z. L'étape de transformation de la caméra a centré ces coordonnées avec le centre du regard. Le milieu de l'écran est aux coordonnées 0,0, pour les coordonnées x,y. Les bords de l'écran correspondent donc à quatre plan, définis par leurs coordonnées x et y.

Résultat de l'étape de transformation de la caméra.

Prenons un écran de résolution W * H, avec H pixels en hauteur et W en largeur.

  • Les bord verticaux de l'écran ont pour coordonnées respectives et
  • Les bord horizontaux de l'écran ont pour coordonnées respectives et
Bords horizontaux.
Bords verticaux.

Le clipping demande donc simplement de comparer les coordonnées du sommet avec ces quatre coordonnées et de combiner les résultats. Si le pixel a une coordonnée y dans l'intervalle et une coordonnée x dans l'intervalle , alors il est dans le champ de vision. Sinon, le sommet est non-visible.

Il y a donc quatre comparaisons à faire par sommet, dont les résultats sont codés chacun sur un bit. Les 4 bits sont combinés pour savoir si le sommet est dans le champ de vision (les 4 bits sont à 1), ou en-dehors. L valeur exacte des 4 bits de résultat donne la position du sommet à l'écran. Un simple ET logique entre les 4 bits dit s'il est totalement visible ou non.

Résultat des 4 comparaison en fonction de la position à l'écran du sommet.

Mais cela ne fonctionne que pour un sommet. Or, le clipping se fait en tenant compte des trois sommets d'un triangle. Reprenons : l'étape précédente a testé chaque sommet, ce qui a donné un résultat codé sur un bit, pour chaque sommet. Le bit en question dit si le sommet est dans le champ de vision ou non. Tester les trois sommets demande de combiner ces trois bits pour obtenir le résultat final.

Si les trois bits sont à 1, les trois sommets sont tous visibles, le triangle est totalement visible, il passe à la rastérisation. Si les trois bits sont à zéro, alors le triangle a ses trois sommets en dehors du champ de vision, le triangle est invisible, tout calcul est abandonné. Dans tout autre cas, une partie des sommets est dans le champ de vision et pas l'autre, le triangle est partiellement visible. Les triangles partiellement visibles sont traités à part, mais la grosse majorité est envoyée à la rastérisation.

Le scanline converter, ou rastériseur proprement dit

[modifier | modifier le wikicode]
Pixels couverts par un triangle.

Une fois tous les triangles non-visibles éliminés, la carte graphique attribue les primitives restantes à des pixels : c'est l'étape de rastérisation proprement dite. Pour une carte graphique, seuls des triangles sont utilisés, ce qui fait qu'elle est appelée étape de Triangle Setup. L'idée est que pour chaque triangle, la rastérisation détermine quels pixels sont dans le triangle. Le tout est illustré ci-contre, pour un triangle.

Les règles de rastérisation

[modifier | modifier le wikicode]

Avant toute chose, il faut préciser ce qu'on entend quand on dit qu'un pixel est dans un triangle. Le cas où un pixel n'est pas exactement dans un triangle est assez compliqué à gérer. En effet, il arrive qu'un bord de triangle traverse un pixel à l'écran. En clair, si vous prenez le petit carré que représente votre pixel à l'écran, le bord du triangle passe dedans.

La solution la plus simple considère que le pixel n'est pas un petit carré de couleur unie, mais est un point situé à une coordonnée x,y. Le point est placé à une coordonnée bien précise dans le carré, et c'est cette coordonnée bien précise qui est utilisée pour la rastérisation. le point est généralement pris au le centre du pixel, au centre du carré. C'est la solution choisie par les API graphiques modernes, comme Direct X. Mais il existe d'autres méthodes alternatives. Par exemple, il est possible de choisir un des coins du pixel ! Le coin inférieur gauche, par exemple. Tout cela pour dire que la scène 3D est échantillonnée par la rastérisation.

Différence entre rastérisation au centre du pixel et sur le coin inférieur gauche.

Il faut aussi tenir compte du cas le centre d'un fragment/pixel est pile entre deux triangles. Par exemple, si le centre du pixel est sur un segment, il faut contact avec deux triangles. Idem si le centre d'un pixel/fragment est sur un sommet, donc un point où plusieurs triangles font contact. Dans ce cas, il faut attribuer le pixel/fragment à un seul triangle, pas à plusieurs. Là encore, les API graphiques définissent des règles de rastérisation assez complexe, que le hardware doit respecter.

Top-left triangle rasterization rule

Une méthode alternative calcule quelle proportion du pixel est dans le triangle. Par exemple, si le bord du triangle coupe le pixel en deux parties égales, alors 50% du pixel est dans le triangle, 50% ne l'est pas. Ces proportions sont transmises à la suite du pipeline pour gérer les calculs de couleur finaux, dans les ROPs. L'avantage est que si un pixel carré est partiellement couvert par plusieurs triangles, chacun attribuant une couleur au pixel, la couleur finale du pixel sera une moyenne pondérée de ces couleurs. Un tel système est une forme spécifique d'antialiasing appelée alpha antialiasing.

La génération des pixels à rastériser

[modifier | modifier le wikicode]

Le rastériseur prend en entrée un triangle et détermine quels sont les pixels qui sont dans ce triangle. La méthode naïve regarde, pour chaque pixel de l'écran, s'il est dans le triangle. En logiciel, la manière la plus simple est d'utiliser une boucle qui traite l'écran pixel par pixel. On commence par le pixel tout en haut à gauche de l’écran, et on balaye l'écran pixel par pixel, ligne par ligne. Pour chaque pixel, on détermine s'il est dans le triangle ou en-dehors. S'il est dedans, on effectue ensuite des opérations d'interpolation pour calculer sa coordonnée de profondeur, sa couleur, ses coordonnées de texture, ses normales, etc.

Dans sa version la plus naïve, tous les pixels de l'écran sont testés pour chaque triangle. Mais si le triangle est assez petit, une grande quantité de pixels seront testés inutilement. Diverses optimisations ont été inventées pour limiter le nombre de pixels à tester.

L'optimisation la plus importante consiste à déterminer le plus petit rectangle possible qui contient le triangle, appelé la bounding boxe. L'idée est de ne tester que les pixels dans ce rectangle. Non seulement on calcule moins de pixels, mais les pixels calculés sont assez proches les uns des autres, bien que ce ne soit pas parfait. L'économie de calcul est assez large, surtout pour les petits triangles. Pour calculer la bounding boxe, il suffit de prendre les trois sommets et de garder les plus grandes et plus petites coordonnées x et y. Les quatre coordonnées sont celles de la bounding boxe.

Smallest rectangle traversal
Notez qu'on utilise un rectangle car la rastérisation se fait ligne par ligne, pixel par pixel. Il faut donc que le tout soit aligné sur des lignes horizontales.

L'algorithme naïf peut être implémenté en matériel. La rastérisation se fait alors pixel par pixel, assez simplement. Pour générer les pixels, rien de plus simple : quelques compteurs suffisent ! Une implémentation sans bounding boxe a juste besoin d'un compteur de ligne et d'un compteur de colonne. Et de circuits pour transformer ces coordonnées entières en coordonnées flottantes, car les coordonnées des sommets à l'écran sont des flottants. Une implémentation avec a besoin d'ajouter des comparateurs et des comparateurs. La complexité réelle se trouve dans les circuits d'interpolation et pour tester si le pixel est dans le triangle. Il est même possible d'adapter le tout de manière à générer plusieurs pixels à la fois.

Les autres méthodes de rastérisation fonctionnent sur le même principe : elles génèrent des pixels, testent si ceux-ci sont dans le triangle, puis effectuent des opérations d'interpolation. La seule différence est qu'elles ne testent pas tous les pixels de l'écran, mais tentent de se limiter à un sous-ensemble des pixels, qui ont de bonnes chances d'être dans le triangle. L'usage de bounding boxe est repris dans certains algorithmes qui vont suivre.

Rendu en Scan-line.

Toujours est-il qu'avec ce qu'on vient de voir, la rastérisation se fait pixel par pixel. Mais il y a des méthodes alternatives. L'une d'entre elle traite l'écran ligne par ligne avec un algorithme de type rendu par scanlines (scanline rendering). Malheureusement, le rendu par scanline n'est pas du tout adapté pour une implémentation en matériel. Une des raisons est qu'idéalement, le rendu doit se faire par carrés de 2 pixels de côté, pour des raisons qu'on abordera dans le chapitre sur les textures. Il existe néanmoins quelques consoles de jeux qui ont implémenté cet algorithme en matériel. Un bon exemple est la console Nintendo 3DS et ses dérivés, qui utilisaient ce genre de rastérisation. Mais la quasi-totalité du matériel récent utilise une autre méthode de rastérisation, plus compatible avec des circuits et plus facilement parallélisable.

Le test de couverture et les équations de droite

[modifier | modifier le wikicode]

Nous venons de voir comment la carte graphique génère les pixels/fragments à tester/rastériser. Il s'agit là de la première étape de la rastérisation. Elle est suivie, pour chaque pixel, par un test qui vérifie si le pixel/fragment est dans le triangle. Le test en question est appelé un test de couverture. C'est lors de cette étape que les règles de rastérisation vues plus haut sont prises en compte. Cette seconde étape est prise en charge par une ou plusieurs unités de rastérisation, chacune testant un pixel contre un triangle. Sur le matériel moderne, ce test utilise des équations de droite ! J'explique.

Par définition, un triangle est une portion du plan délimitée par trois droites, chaque droite passant par un côté. Et chaque droite coupe le plan en deux parties : une à gauche de la droite, une autre à sa droite. Un triangle est définit par l'ensemble des points qui sont du bon côté de chaque droite. Par exemple, si je prend un triangle délimité par trois droites d1, d2 et d3, les points de ce triangle sont ceux qui sont situé à droite de d1, à gauche de d2 et à droite de d3. La rastérisation matérielle profite de cette observation pour déterminer si un pixel appartient à un triangle.

L'idée est de calculer si un pixel est du bon côté de chaque droite, et de combiner les trois résultats pour prendre une décision. Pour chaque droite, on crée une fonction de contours, qui indique de quel côté de la droite se situe le pixel. La fonction de contours va, pour chaque point sur l'image, renvoyer un nombre entier :

  • zéro si le point est placé sur la droite ;
  • un nombre négatif si le point est placé du mauvais côté de la droite ;
  • un nombre positif si le point est placé du bon côté.
Fonction de contours : le bleu est hors triangle, le rose dedans, un point est en-dehors de l'écran.

Comment calculer cette fonction ? Tout d'abord, nous allons dire que le point que nous voulons tester a pour coordonnées sur l'écran. La droite passe quant à elle par deux sommets : le premier de coordonnées et l'autre de coordonnées . La fonction est alors égale à :

Pour savoir si un pixel appartient à un triangle, il suffit de tester le résultat des trois fonctions de contours, une pour chaque droite. À l'intérieur du triangle, les trois fonctions (une par côté) donneront un résultat positif. À l'extérieur, une des trois fonctions donnera un résultat négatif.

Le back face culling

[modifier | modifier le wikicode]

Après le viewport clipping, une étape de back-face culling élimine les primitives qui tournent le dos à la caméra. Ces primitives appartiennent aux faces à l'arrière d'un objet opaque, qui sont cachées par l'avant. Elles ne doivent donc pas être rendues et sont donc éliminées du rendu. Le back face culling réutilise les résultats des fonctions de contours pour déterminer si le triangle est visible ou non.

Le back face culling calcule, pour un triangle, la normale de la surface. Pour rappel, la normale est un vecteur qui est perpendiculaire à la surface, ici le triangle. L'idée est de regarder l'orientation de cette normale par rapport à la ligne de vue, un vecteur qui part de la caméra et atterit sur le triangle. Si la normale est perpendiculaire par rapport à cette ligne de vue, alors la surface est pile à l'horizontale et est presque invisible. Si la normale est penchée vers l'arrière, le triangle est là aussi invisible. Mais si elle est penchée vers l'avant, le triangle est visible.

Le calcul de l'angle avec la ligne de vue est un simple produit scalaire entre les deux vecteurs (normale, ligne de vue). Et la normale est calculée à partir des fonctions de contours.

La rastérisation sur les toutes premières cartes graphiques

[modifier | modifier le wikicode]

Au tout début du rendu 3D, dans les années 70-80, les cartes graphiques ne géraient pas que des triangles, voire n'en gérait pas du tout ! Le rendu 3D de l'époque gérait des polygones arbitraires, qui étaient découpés en polygones plus simples lors de la rastérisation. La rastérisation se faisait alors en deux étapes : on découpait les polygones en polygones de base, qui eux-même étaient rastérisés. C'était possible car les cartes graphiques de l'époque implémentaient le rastériseur avec des processeurs couplés à un firmware/microcode dédié, pas des circuits fixes.

Les polygones de base étaient parfois des triangles, mais aussi des trapézoïdes. Chaque carte graphique faisait à sa sauce, l'époque, il n'y avait pas de standardisation. Mais un cas très intéressant à étudier est celui des "trapézoïdes plats", à savoir des trapézoïdes alignés à l'horizontale. En clair, les segments du haut et du bas étaient à l'horizontale, alignés avec l'axe x.

Intuitivement, un trapézoïde est définit par 4 fonctions de contours, quatre droites. Mais le fait d'aligner le haut et le bas à l'horizontale simplifie grandement le tout : deux droites disparaissent ! Les droites pour le segment du haut et du bas sont être remplacées par les coordonnées y du segment du haut et du bas. Il ne reste que les segments penchés, sur les bords. Au final : deux droites, deux coordonnées y. Le tout est plus simple que ce qu'on a avec les triangles, qui demandent trois droites.

Un autre avantage est que la rastérisation est plus rapide si on utilise une rastérisation par scanline. À l'écran, le trapézoïde plat est composé en empilant des lignes de pixels les unes sur les autres. Les lignes changent juste de longueur et de position à chaque scanline. Le rastériseur a juste à calculer le point de départ à chaque ligne, et la longueur. Quelques compteurs font le reste pour dessiner le trapézoïde ligne par ligne, pixel par pixel. Le tout se mariait particulièrement bien avec le placage de texture direct, de type forward.

Les circuits d'interpolation

[modifier | modifier le wikicode]

Une fois l'étape de triangle setup terminée, on sait donc quels sont les pixels situés à l'intérieur d'un triangle donné. Reste que ces pixels, il faut les remplir. Pour le moment, les seules informations que nous avons sont pour les sommets. On connait leur couleur, leur coordonnée de profondeur, leurs coordonnées de texture, et éventuellement d'autres informations. Mais les pixels/fragments à l'intérieur du triangle, ou sur ses bords, sont aussi censés avoir une couleur, des coordonnées de texture, et une coordonnée de profondeur. Reste à les calculer.

Exemple d'interpolation de la couleur dans un triangle.

Pour les pixels situés exactement sur les sommets, on peut reprendre la coordonnée de texture et la profondeur du sommet associé. Mais pour les autres pixels, nous sommes obligés d'extrapoler les coordonnées et la profondeur à partir des données situées aux sommets. C'est le rôle de l'étape d'interpolation, qui calcule les informations des pixels qui ne sont pas pile-poil sur un sommet. Par exemple, si j'ai un sommet vert, un sommet rouge, et un sommet bleu, le triangle résultant doit être colorié comme indiqué dans le schéma de droite.

L'interpolation dans un triangle : les coordonnées barycentriques

[modifier | modifier le wikicode]
Coordonnées barycentriques.

Pour interpoler une valeur dans un triangle, il est possible d'utiliser ce qui s'appelle les coordonnées barycentriques. Elles sont au nombre de trois coordonnées et sont notées u, v et w. Pour les déterminer, nous allons devoir relier le fragment aux trois autres sommets du triangle, ce qui découpe le triangle initial en trois triangles. Les coordonnées barycentriques sont proportionnelles aux aires de ces trois triangles. Par proportionnelles, il faut comprendre que les coordonnées barycentriques ne dépendent pas de la valeur absolue de l'aire des trois triangles. À la place, ces trois aires sont divisées par l'aire totale du triangle, et c'est ce rapport qui est utilisé pour calculer les coordonnée barycentriques.

La carte graphique calcule ces trois coordonnées en commençant par normaliser l'aire du triangle. C'est à dire qu'elle fait en sorte que l'aire totale du triangle soit d'une unité d'aire, qu'elle fasse 1. Les aires des trois triangles sont alors calculées en proportion de l'aire totale, ce qui fait que leur valeur est comprise dans l'intervalle [0, 1]. Cela signifie que la somme de ces trois coordonnées vaut 1 :

Le calcul exact des coordonnées barycentriques peut se faire à partir des fonctions de contours. C'est pour cette raison qu'elles sont utilisées pour l'interpolation.

Les trois coordonnées permettent de faire l'interpolation directement. Prenons l'exemple de l'interpolation de la couleur des trois sommets. Il suffit de multiplier la couleur d'un sommet par la coordonnée barycentrique associée, et de faire la somme de ces produits. Si l'on note , , et les couleurs des trois sommets, la couleur d'un pixel vaut :

Le seul problème est que l'interpolation avec des coordonnées barycentriques ne fonctionne tout simplement pas pour le rendu 3D ! Nous allons expliquer pourquoi dans ce qui suit. Mais sachez cependant que nous n'avons pas introduit ces coordonnées barycentriques pour rien. Les coordonnées barycentriques sont bien utilisées par la carte graphique, le rastériseur calcule ces coordonnées barycentriques avant de faire le moindre calcul d'interpolation. Simplement, elles ne sont pas utilisées naïvement pour faire l'interpolation avec la formule précédente.

L'interpolation de la coordonnée de profondeur

[modifier | modifier le wikicode]

L'usage de coordonnées barycentriques ne fonctionne pas bien, car il ne tient pas compte de la perspective. Lors de l'étape de transformation et de rastérisation, la perspective utilisée donne un résultat idéal à l’œil. Sauf qu'elle altère les distances. Si je prend un bord de triangle, il correspond à un segment à l'écran. La coordonnée de profondeur ne varie pas linéairement sur ce segment. Or, l'usage de coordonnées barycentriques est par nature linéaire.

Pour tenir compte de la perspective, il faut d'abord interpoler correctement la coordonnée de profondeur. La coordonnée de profondeur est interpolée avant tout le reste. La profondeur interpolée est en effet nécessaire pour interpoler les couleurs des sommets, les coordonnées de texture, et autres. Donc nous allons voir les deux méthodes pour interpoler la coordonnée de texture avant le reste.

Il existe deux solutions pour interpoler la coordonnée de profondeur. La plus simple utilise malgré tout une interpolation linéaire, aussi appelée interpolation affine. La coordonnée de profondeur est alors calculée avec les coordonnées barycentrique, avec la même formule d'interpolation que celle vue plus haut. La coordonnée de profondeur est alors incorrecte, ce qui se répercute sur le calcul des coordonnées de texture.

, avec la profondeur interpolée au point x,y.

Il est aussi possible d'utiliser une interpolation quadratique, qui est bien plus proche d'une perspective correcte. Quelques ordinateur ont utilisé cette technique, notamment de vielles stations de travail des années 80, à une époque où une correction de perspective digne de ce nom était trop gourmande en circuits. Je la mentionne pour la complétude, elle n'a presque pas été utilisée dans les cartes graphiques anciennes comme modernes.

Une autre solution utilise une interpolation tenant compte de la perspective. Sans rentrer dans les détails mathématiques du calcul de la perspective, l'idée est d'utiliser non pas la coordonnée de profondeur z, mais son inverse 1/z. En effet, cette valeur suit une évolution linéaire vu de l'écran, à savoir qu'un objet qui parait deux fois plus loin à l'écran sera situé à une distance de 2/z.

Et c'est ce qui rend l'interpolation linéaire possible avec 1/z. Pour faire l'interpolation, on calcule 1/z pour les trois sommets, on effectue une interpolation de 1/z avec des coordonnées barycentriques, puis on inverse le résultat pour trouver le z voulu. L'interpolation se fait donc avec le calcul suivant :

Le GPU contient un circuit pour interpoler 1/z, et il est assez complexe si on en croit la formule précédente. Trois divisions, deux additions. Autant les additions sont tout sauf un problème, autant les divisions sont très gourmandes en circuits, particulièrement les divisions flottantes. Et ce sont en plus des opérations très lentes ! Il n'est pas étonnant que les rastériseurs soient implémentés en matériel sachant cela, et encore : on n'a pas vu les autres interpolations...

La formule précédente permet de calculer z si besoin, en prenant l'inverse. Mais le truc est que l'inverse n'est pas utilisé très souvent. Il se trouve que les calculs dans la suite de ce chapitre font surtout usage de 1/z, pas de z directement. Aussi, les GPU se débrouillent pour utiliser le plus possible la valeur de 1/z, et non z directement. Par exemple, le tampon de profondeur ne mémorise pas la valeur de z pour chaque pixel ! À la place, il mémorise les valeurs de 1/z ! Et cela a de nombreux avantages, qui sont exploités par les circuits liés au tampon de profondeur.

Mieux encore, les cartes graphiques modernes utilisent en fait une formule plus compliquée, pour tenir compte du near plane et du far plane, qui délimitent l'avant et l'arrière du view frustum. La formule exacte est :
, avec a et b deux constantes liées à la position dunear plane et du far plane.

L'interpolation tenant compte de la perspective

[modifier | modifier le wikicode]

Maintenant, voyons comment interpoler les autres valeurs aux sommets : couleur, coordonnées de textures, normales, etc. Nous allons prendre l'exemple de la couleur, pour plusieurs raisons. Premièrement, la couleur est un simple nombre (on met de côté son caractère RGB), ce qui simplifie les explications. Et la couleur est un paramètre plus intuitif qu'une normale, par exemple. Ensuite, le cas des coordonnées de texture sera vu à part, car je tiens à faire un aparté sur le placage de texture affine.

Pour l'interpolation de la couleur, le calcul procède comme suit. L'idée est de multiplier chaque couleur par 1/z, z étant pris sur le sommet adéquat. On a alors les trois coordonnées suivantes :

L'interpolation utilise ces coordonnées, au lieu des coordonnées barycentriques :

Puis on multiplie ce résultat par z, la coordonnée de profondeur mesurée sur le pixel interpolé.

Une formule équivalente à la précédente ne multiplie pas par z, mais divise par 1/z. La valeur de 1/z est facile à calculer, il suffit de l'interpoler comme expliqué plus haut.

Le matériel pour faire ces calculs est mine de rien assez gourmand en circuits.

  • La première étape utilise un circuit diviseur, ou équivalent.
  • La seconde étape utilise deux circuits : un pour calculer les coordonnées barycentriques, et un circuit d'interpolation proprement dit.
  • La troisième étape utilise un diviseur pour diviser par 1/z.

Nous avons donc une formule pour faire une interpolation correcte niveau perspective, que l'on peut implémenter en circuit. Quelques circuits diviseurs, multiplieurs et additionneurs suffisent. Le bilan niveau circuits est : trois divisions pour la première étape, trois multiplications et deux additions pour la seconde étape, une multiplication pour la troisième étape. Une optimisation effectue chaque division de la première étape dans un circuit qui calcule l'inverse d'un nombre, et un circuit multiplieur. Le circuit qui calcule l'inverse d'un nombre peut être utilisé dans les calculs d'interpolation, pour faire le calcul 1/z. C'est donc une forme de redondance exploitable.

L'interpolation des coordonnées de texture

[modifier | modifier le wikicode]

Lors de la rastérisation, chaque fragment se voit attribuer un triangle, et les coordonnées de texture qui vont avec. Si un pixel est situé pile sur un sommet, les coordonnées de texture de ce sommet sont attribuées au pixel. Si ce n'est pas le cas, les coordonnées de texture sont interpolées à partir des trois sommets du triangle rastérisé. Et cette interpolation peut ou non tenir compte de perspective, tenir compte de la coordonnée de profondeur.

Le placage de texture affine interpole les coordonnées de texture sans tenir compte de la coordonnée de profondeur. Concrètement, on fait une moyenne pondérée des coordonnées de texture u et v des trois sommets pour obtenir les coordonnées de textures finales, sans prendre en compte la coordonnée de profondeur, avec des coordonnées barycentriques. Par contre, en faisant cela, la perspective n'est pas correctement rendue, comme illustré ci-dessous.

Le placage de texture avec perspective correcte tient compte de la coordonnée de profondeur pour interpoler les coordonnées de texture. Mais pour cela, il faut que la coordonnée de profondeur soit interpolée avec une perspective correcte, c'est à dire en interpolant 1/z, pour ensuite inverser le tout. Plus précisément, il faut :

  • remplacer les coordonnées u,v,z (les deux coordonnées de texture u,v et la profondeur) par les coordonnées suivantes : u/z, v/z et 1/z ;
  • interpoler ces quantités avec des coordonnes barycentriques ;
  • de multiplier le tout par z pour obtenir le résultat final.

En faisant cela, on s'assure que la perspective est rendue à la perfection. L'explication mathématique de pourquoi cette formule fonctionne est cependant assez compliquée...

Correction de perspective.

L'interpolation affine était utilisée sur la console Playstation 1 de Sony, d'où des textures un peu bizarres sur cette console. D'autres consoles utilisaient l'interpolation affine, mais s'en sortaient mieux car elles utilisaient non pas des triangles, mais des quads (des rectangles). Avec des primitives rectangulaires, le résultat a l'air visuellement, meilleur, car l'interpolation donne un bon résultat pour ce qui va à l'horizontal, seule les objets à la verticale de la caméra donnant une perspective légèrement déformée. Tout cela est bien illustré ci-dessous. Cependant, l'interpolation est alors plus lourde en calculs, car elle demande d'interpoler quatre sommets au lieu de trois. Le cout en calculs n'est pas négligeable.

Affine texture mapping tri vs quad

Le circuit rastériseur au complet

[modifier | modifier le wikicode]

Nous venons de voir que le rastériseur effectue beaucoup de choses. Il doit générer des pixels, vérifier s'il sont dans le triangle traité, puis effectuer plusieurs interpolations. Mais la complexité du rastériseur dépend de s'il utilise la correction de perspective ou non. Au tout début du rendu 3D, les rastériseurs se passaient souvent de correction de perspective. On parle des années 70-80, sur les stations de travail professionnelles. Mais les cartes accélératrices des PC ont directement utilisé la correction de perspective.

Il a donc existé deux types de rastériseurs : avec et sans correction de perspective. Par exemple, la Playstation 1 a utilisé la rastérisation affine, sans correction de perspective. Et les deux types de rastériseurs n'ont pas la même conception. C'est ce qui explique pourquoi l'absence de correction de perspective était autrefois utilisée, mais a disparu. Pour cela, comparons un rastériseur avec et un sans correction de perspective.

Les rastériseurs sans correction de perspective

[modifier | modifier le wikicode]

Un rastériseur sans correction de perspective est un peu plus simple que l'autre type. Il prend en entrée un triangle à rastériser, à savoir trois sommets. Il génère les pixels soit un par un, soit en parallèle. Nous allons supposer que des circuits génèrent les pixels un par un, pour tout simplifier. Une fois qu'il dispose du pixel à tester, et des trois ommets, il effectue la suite d'actions suivantes :

  • Il calcule les fonctions de contour et accepte ou rejette le triangle.
  • Il calcule les coordonnées barycentriques à partir des fonctions de contour.
  • Il effectue les interpolations à partir des coordonnées barycentriques.

Les trois étapes sont réalisées par des circuits séparés. Le tout est illustré ci-dessous.

Rasteriseur avec interpolation affine

Les circuits de génération des pixels, de fonction de contours et de calcul des coordonnées barycentriques sont identiques avec et sans correction de perspective. Par contre, les circuits d'interpolation seront différents. Les circuits d'interpolation sont relativement simples sans correction de perspective. L'interpolation demande de faire quelques multiplications et additions flottantes ou entières. Quelques circuits de MAD (Multiply And Accumulate) sont tout indiqués.

Les rastériseurs avec correction de perspective

[modifier | modifier le wikicode]

Le rastériseur avec correction de perspective fonctionne de manière similaire au rastériseur précédent. Comparé au rastériseur précédent, rien ne change pour la génération des pixels, des fonctions de contour et des coordonnées barycentriques. Par contre, tout change pour ce qui est de l'interpolation.

L'implémentation exacte arrive à économiser de nombreux circuits diviseurs, pour les remplacer par des circuits plus simples. En effet, les valeurs , et sont utilisés dans tous les calculs d'interpolation. Il est possible de calculer ces termes une fois pour toute, pour ensuite les utiliser dans les calculs d'interpolation.

Les circuits d'interpolation prennent alors en entrée : les coordonnées barycentriques, les valeurs , et les attributs à interpoler. Le résultat est que les circuits diviseurs sont remplacés par des circuits multiplieurs ou MAD (Multiply And Accumulate). Il s'agit d'une économie en circuits non-négligeable.

Implémentation matérielle d'un rastériseur avec correction de perspective.

Ce n'est pas évident dans le schéma précédent, mais la correction de perspective et les interpolations demandent d'effectuer des divisions flottantes, ce qui est très gourmand en calcul et en circuits. Les anciennes cartes graphiques préféraient l'interpolation affine, pour économiser ces circuits de division flottante. Le résultat était acceptable pour l'époque, en termes de ratio performance/qualité d'image. De nos jours, les GPU utilisent une perspective correcte, le placage de texture affine est définitivement abandonné.

Les rastériseurs des GPU modernes

[modifier | modifier le wikicode]

Les GPU modernes vont encore plus loin : ils se passent de circuits d'interpolation. À la place, les calculs d'interpolation sont réalisés dans les pixel shaders. Le driver de la carte graphique ajoute de quoi faire l'interpolation quand il compile les pixels shaders, tout au début du shader.

L'avantage est que cela économise pas mal de circuits, tout en ayant un cout en performance dérisoire. Le rastériseur se contente de générer les pixels, les coordonnées barycentriques, et éventuellement les valeurs . Les opérations d'interpolation ne demandant que des opérations MAD (Multiply And Accumulate), les pixels shaders ont déjà le matériel pour cela. A vrai dire, même le calcul des valeurs peut être fait dans les shaders, si le GPU supporte l'instruction RECP (Reciproqual, à savoir le calcul de 1/x). Et niveau performance, les processeurs de shaders sont tellement puissants de nos jours que leur rajouter quelques opérations d'interpolation basiques ne le fait presque rien.

Un autre avantage est que cela permet de gérer divers types d'interpolation sans avoir de hardware dédié. Les GPU modernes supportent à la fois l'interpolation affine ou avec correction de perspective. Les anciens GPU devaient avoir des rastériseurs matériels capable de faire les deux types d'interpolation. Le gros du rastériseur était commun aux deux types d'interpolation, il y avait beaucoup de redondance, certes. Mais il fallait rajouter des circuits pour rendre le rastériseur configurable, capable de gérer les deux types d'interpolation. Avec une interpolation réalisée dans les shaders, pas besoin : on ne garde que ce qui est vraiment commun, à savoir la génération des coordonnées barycentriques et la génération des pixels/fragments.

Les rastériseurs à deux niveaux/parallèles

[modifier | modifier le wikicode]

Résumons rapidement ce qu'on vient de voir. Le rastériseur rastérise des triangles, c'est une évidence. Pour économiser des calculs, il détermine une bounding box rectangulaire, qui contient ce triangle. Tout cela n'est pas faux en soi, mais drastiquement incomplet. Les GPU, anciens comme modernes, utilisent en réalité une méthode de rastérisation améliorée, bien plus performante et plus adaptée à une implémentation en circuits.

Les GPU utilisent précisément un algorithme dit de tiled traversal, que nous allons expliquer dans ce qui suit. Elle a de nombreux avantages. Le premier est qu'elle permet de tester plusieurs pixels séparés en parallèle. En clair, la rastérisation d'un triangle ne se fait pas pixel par pixel. Elle se fait en réalité plusieurs pixels à la fois, le GPU teste et interpole plusieurs pixels en même temps. Le second avantage est que les pixels non-couverts par le triangle sont rejetés plus rapidement, en blocs.

Le tiled traversal

[modifier | modifier le wikicode]

Le principe consiste à découper le framebuffer en morceaux carrés de 4, 8, 16 pixels de côté, qui sont appelés des tiles. Les tiles sont de taille et de position fixes. N'allez cependant pas croire qu'il s'agit des mêmes tiles que celles utilisées sur les architectures à tile. Un point important est que les tiles ne sont pas forcément alignées sur la bounding box, mais sont en réalité alignées avec l'écran, le framebuffer. Mais c'est là un détail.

L'idée est qu'un premier rastériseur découpe la bounding box en tiles, puis détermine quelles tiles sont recouvertes par un triangle. La rastérisation se fait alors en deux temps, en deux niveaux. Un premier niveau découpe la bounding box en tiles, et décide lesquelles sont couvertes par un triangle. Un second niveau rastérise les tile nécessaires, une par une. Le test de couverture d'une tile est assez simple : il suffit de tester les quatre sommets de la tile et de combiner les résultats.

Un avantage de cette méthode se comprend bien avec l'exemple illustré dans le schéma ci-dessous, où un triangle recouvre trois tiles, alors que sa bounding box en recouvre 4 tiles. Si on se basait uniquement sur la bounding box pour éliminer les pixels/fragments inutiles, tous les pixels de la bounding box seraient testés. Avec ce système de tiles, le GPU va remarquer qu'une tile ne recouvre pas le triangle, seules les trois autres seront traitées. En clair, seules 3 tiles sur 4 vont générer des pixels, la tile inutile n'est pas prise en compte.

Tiled traversal.

L'avantage se manifeste surtout pour les gros triangles, couplés à des tiles assez petites. Plus la tile est petite et plus le triangle est grand, plus l'avantage sera important. L'avantage théorique est d'environ la moitié des pixels éliminés. Prenons un cas favorable : un triangle dont la base est horizontale. Dans ce cas, la bounding box rectangulaire a une largeur égale à la base du triangle et une hauteur égale à celle du triangle. La géométrie de collège nous dit que le triangle occupe la moitié de la bounding box, pour ce qui est de l'aire. Donc, la moitié des pixels est recouverte. Le fait de découper l'aire en tile fait cependant passer sous les 1/2 de pixels éliminés.

Mais le tiled traversal a un autre avantage, bien plus intéressant...

La parallélisation de la rastérisation

[modifier | modifier le wikicode]

La tiled rasterisation permet d'exploiter l'amplification des pixels simplement. Avec la rastérisation à deux niveaux, il est possible de traiter tous les pixels/fragments d'une tile en parallèle, en une seule fois. Par exemple, pour une tile de 8x8 pixels, le rastériseur fin est capable de traiter les 64 pixels/fragments de la tile en une seule fois. Pour cela, les circuits vus précédemment, qui forment un rastériseur complet, sont dupliqués en autant d'exemplaires qu'il y a de pixels dans une tile. Beaucoup de duplication de circuits, mais le gain en performance n'est pas négligeable. Tous les GPU actuels utilisent ce genre de duplication.

Le tiled traversal a pour autre avantage qu'il se marie très bien avec la gestion des textures. En effet, les textures sont stockées en mémoire d’une manière particulière : elles sont découpées en carrés de quelques pixels de côté, et les carrés sont répartis dans la mémoire d'une manière assez spécifique. Les carrés des textures ont la même taille que les carrés de la rastérisation. Cela garantit que la rastérisation d'un carré de pixel a de bonnes chances de tomber sur un carré de texture, ce qui permet de profiter parfaitement du cache de texture.

Une solution complémentaire traite plusieurs tiles en parallèle. Le problème de cette optimisation est qu'elle n'est pas utile. Elle gaspille beaucoup de ressources pour les petits triangles, mais ne donne pas de gros gains pour les gros triangles.

Notons que cette optimisation se marie particulièrement bien avec des circuits séparés pour les triangles et les pixels. Les anciennes cartes graphiques étaient dans ce cas. Elles avaient une unité pour la géométrie, et plusieurs unités pour les pixels/textures. Le rastériseur recevait les triangles provenant de l'unité géométrique, générait plusieurs pixels/fragments, qui étaient distribués sur les unités de texture/pixel. L'implémentation est plus compliquée quand on a des processeurs de shaders unifiés, mais laissons cela à plus tard.

Architecture d'un GPU avec rastérisation parallèle (qui utilise l'amplification des pixels).

L'élimination précoce des fragments cachés

[modifier | modifier le wikicode]

La coordonnée de profondeur permet de savoir si un fragment doit être rendu ou non. Logiquement, si un objet est derrière un autre, il n'est pas visible et ses fragments n'ont pas à être calculés/rendus. Les concepteurs de cartes graphiques usuelles ont donc inventé des techniques d'élimination précoce pour éliminer certains fragments dès qu'on connait leur coordonnée de profondeur, à savoir une fois l'étape de rastérisation/interpolation terminée. Ainsi, on est certain que le fragment en question n'est pas texturé et ne passe pas dans les pixels shaders, ce qui est un gain en performance non-négligeable. Il faut certes prendre en compte la transparence des fragments qui sont devant, mais rien d'insurmontable.

Les pixels shaders et le early-z

[modifier | modifier le wikicode]

Mais ces techniques peuvent causer un rendu anormal quand un pixel shader modifie la coordonnée de profondeur ou de transparence d'un pixel. C'est rare, mais cela peut arriver. C'est pour cela que l’élimination des fragments invisibles est traditionnellement réalisé à la toute fin du pipeline graphique, dans les ROPs, juste avant d’enregistrer les pixels dans le framebuffer. Pour éliminer tout problème, on doit activer ou désactiver l'élimination précoce des pixels suivant les besoins. Depuis DirectX 11, les APIs graphiques permettent de marquer certains shaders comme étant compatibles ou incompatibles avec l'élimination précoce. Avant, les drivers du GPU analysaient les shaders pour décider de faire le test de profondeur précoce.

Rappelons que la carte graphique change régulièrement de shader à exécuter. Et il arrive qu'on passe d'un shader compatible avec l'élimination précoce à un shader incompatible ou inversement. Passer d'un shader qui est compatible avec l'élimination précoce à un qui ne l'est n'est pas un problème. Il suffit de désactiver l'unité d'élimination précoce lors du changement de shader. Mais dans le cas inverse, quelques problèmes de synchronisation peuvent apparaitre.

Il faut activer l'élimination précoce quand les pixels du nouveau shader sortent du circuit de rastérisation, ce qui n'est pas exactement le même temps que le changement de shader. En effet, le shader précédent a encore des pixels qui traversent le pipeline et qui sont en cours de calcul dans les pixels shaders ou dans les ROP. Le processeur de commande doit donc faire attendre les processeurs de shader et quelques autres circuits. Typiquement, il faut attendre que la commande précédente se termine, avant d'en relancer une autre avec le nouveau shader.

Plus d'information dans cet article de blog : To Early-Z, or Not To Early-Z.

L'élimination des pixel cachés hiérarchique

[modifier | modifier le wikicode]

Le tiled traversal est à l'origine de beaucoup d’optimisations, qu'on détaillera dans ce qui suit. Elle élimine beaucoup de pixels inutiles dans une bounding box, elle permet de traiter tous les pixels d'une tile en parallèle, permet d'utiliser plusieurs rastériseurs. Mais une optimisation potentielle améliore l'élimination des pixels cachésen tenant compte des tiles. Il y a alors une élimination des pixels cachés à deux niveaux : au niveau des tiles, puis par pixel dans une tile. On parle d'élimination des pixels cachés précoce hiérarchique, que nous allons abrévier en z-hiérarchique.

Il existe plusieurs techniques d'élimination précoce (Early-Z), qui peuvent être classées en deux catégories : le zmax, et le zmin. Dans les deux cas, la carte graphique vérifie, pour chaque pixel, s'il est affiché ou masqué. Parmi tous les pixels d'une tile, il y en aura un dont la profondeur sera plus élevée ou plus petite que les autres. L'unité d'Early-Z mémorise cette profondeur maximale ou minimale pour chaque tile. Dans le cas du zmax, c'est la profondeur la plus grande qui est mémorisé, alors que le zmin mémorise la plus petite profondeur.

Avce le z-max, le rastériseur mémorise la coordonnée z maximale de la tile, celle la plus loin de l'écran. Lorsqu'un triangle est rendu dans une tile, le rastériseur calcule la coordonnée de profondeur minimale, celle la plus proche de l'écran. Les deux coordonnées sont alors comparées : si la coordonnée minimale du triangle est plus grande que celle de la tile, cela veut dire que tout le triangle est situé derrière la tile. Donc qu'il est caché, on n'a pas à rendre ce triangle et faire la rastérisation, on abandonne tout. Pour calculer la valeur maximale des pixels de la tile, une solution calcule les coordonnées z des quatre coins de la tile et de prend la plus proche (la plus petite). Mais ces coordonnées doivent être interpolées, ce qui demande pas mal de calculs.

Avec le zmin, on utilise la profondeur maximale des sommets du triangle et la profondeur minimale dans la tile. Si la profondeur du pixel à rendre est plus petite, cela veut dire que le pixel n'est pas caché et qu'il n'y a pas besoin d'effectuer de test de profondeur dans les ROPs. Il est parfaitement possible d'utiliser le zmax conjointement avec le zmin. On obtient alors des techniques hybrides, relativement puissantes. On pourrait citer l'exemple de l'adaptive tile depth filter.

Les profondeurs de chaque tile, maximale ou minimale, sont mémorisées dans une mémoire SRAM intégrée au rastériseur grossier, celui qui gère les tiles. En théorie, on peut se débrouiller avec une seule SRAM pour tout le GPU, la SRAM étant partagée entre les différents rastériseurs grossiers, et les GPU modernes font sans douter ainsi. La SRAM en question a une taille limitée et diverses optimisations visent à réduire sa taille. Par exemple, au lieu d'utiliser des coordonnées de profondeur de 24 bits, d'usage dans les ROPs, la SRAM ne mémorise que les 16 bits de poids fort. Au pire, ce manque de précision réduira un peu l'efficacité de l'élimination des pixels cachés au niveau des tiles. Et malgré cela, pour des grandes résolution, la SRAM est souvent trop petite malgré tout.

L'avantage de cette SRAM est que le z-hiérarchique peut éliminer des pixels cachés sans avoir à lire le tampon de profondeur. Et lire le tampon de profondeur utilise beaucoup de bande passante en RAM vidéo. L'élimination des pixels cachés précoce, au niveau des pixels, demande de lire le tampon de profondeur. Mais celle au niveau des tiles n'accède pas à la mémoire vidéo, seulement à la SRAM mentionnée au paragraphe précédent.

La parallélisation de la rastérisation

[modifier | modifier le wikicode]

La performance de la rastérisation est particulièrement importante. Pour l'améliorer, la solution retenue est le parallélisme, à savoir faire des calculs indépendants en parallèle. Utiliser correctement le parallélisme est un classique quand on conçoit des processeurs, des GPU, ou n'importe quel circuit électronique. C'est un des grand avantage du matériel par rapport au logiciel : exécuter des calculs en parallèle est assez intuitif, simple, facile.

Reste à concevoir des rastériseurs qui effectuent des calculs en parallèle. Pour cela, on a deux grandes solutions. La première utilise l'amplification des pixels, à savoir le fait qu'un triangle est "traduit" en plusieurs pixels/fragments. L'idée est de traiter un triangle à la fois, mais de générer plusieurs pixels/fragments en même temps, qui sont testés et interpolés séparément. La seconde solution traite plusieurs triangles séparés dans des rastériseurs séparés.

La tiled rasterisation permet d'exploiter l'amplification des pixels simplement, ce qui est très utile. Une solution complémentaire rastérise plusieurs triangles en parallèle, dans des circuits rastériseurs séparés. Pour cela, les GPU actuels incorporent plusieurs rastériseurs. La raison à cela est assez simple : quand on a une centaine de processeurs de shaders à alimenter, il faut rastériser un grand nombre de pixels par seconde. Un seul circuit rastériseur n'est pas suffisant, on doit en utiliser plusieurs. Cependant, utiliser plusieurs rastériseurs pose pas mal de problèmes pratiques. Le premier est qu'il faut connecter les rastériseurs aux processeurs de shaders, et aux autres circuits de la carte graphique.

La solution la plus performante utilise un réseau d'interconnexion qui relie les R rastériseurs aux P processeurs de shaders. L'idée est que n'importe quel processeur de shader peut envoyer des données à n'importe quel rastériseur et inversement. Mais un tel réseau est très compliqué à mettre en place. Il y a beaucoup de fils à câbler, sans compter que le transfert des données dans les fils consomme beaucoup de courant et chauffe beaucoup. Et il faut ajouter des circuits d'arbitrage pour décider quel triangle va dans quel rastériseur.

En général, il y a moins de rastériseurs que de processeurs de shaders, c'est le cas sur les GPU modernes. La raison est une question d'amplification des pixels. Par exemple, supposons qu'un triangle couvre en moyenne 20 pixels. Dans ce cas, un rastériseur va produite environ 20 pixels par cycle, et alimentera 20 processeurs de pixel shaders. En conséquence, il y a souvent moins de rastériseurs que processeurs de shaders, même quand ces derniers peuvent traiter des paquets de N pixels en une seule fois, grâce au SIMD et au FGMT. Le circuit d'arbitrage peut se manifester, si tous les processeurs de shaders sont occupés et qu'aucun ne peut prendre en charge de nouveau fragment/pixel, mais c'est assez rare.

Par contre, cela pose des problèmes dans le sens inverse, à savoir quand les processeurs de shaders veulent envoyer des triangles aux rastériseurs. Dans ces conditions, il arrive que tous les processeurs de shaders veuillent envoyer des triangles au rastériseur, mais il n'y a pas assez de rastériseurs pour tous les recevoir. Dans ce cas, le circuit d'arbitrage sélectionne N triangles et met les autres en attente. Une telle situation est assez fréquente quand on exécute un vertex shader très basique, ce qui est fréquent dans les pré-passes z, une optimisation des moteurs 3D "récents" que je ne détaillerais pas ici. Le vertex shader est très simple, donc le GPU peut calculer beaucoup de triangles par secondes, ce qui sature les rastériseurs.

Une solution alternative remplace le réseau d'interconnexion par un bus, qui relie les rastériseurs et les processeurs de shaders. Le cablage est alors plus simple, mais les performances du bus sont inférieures. La solution a les mêmes problèmes d'arbitrage, si ce n'est pire, car plusieurs processeurs de shaders tentent d'accéder au bus en même temps. Il faut que le bus fonctionne à très haute fréquence pour que cela ne pose pas de problèmes. Quelques systèmes anciens de l'entreprise SGI utilisaient un tel 'triangle bus, mais ils étaient bien les seuls.

Le réseau d'interconnexion est plus simple avec des processeurs séparés pour les vertex et les pixel shaders. Dans ce cas, la solution la plus simple est d'utiliser autant de rastériseurs qu'il y a de processeurs de vertex shader. Chaque processeur de vertex shader alimentera un rastériseur, qui lui-même alimentera plusieurs processeurs pour les pixels shaders. Il suffit de relier les rastériseurs aux processeurs de pixel shader. Mais au