Les cartes graphiques/Les unités de gestion des primitives
À ce stade, les sommets ont été éclairés et transformés par les unités de traitement géométriques. Vient alors l'étape de gestion des primitives, durant laquelle les sommets sont assemblés en primitives, qui sont elle-mêmes éventuellement altérés.
Avant toute chose, il faut rappeler ce que le terme "primitive" veut dire dans le contexte du rendu 3D. Les primitives sont tout simplement les triangles ou les polygones utilisés pour décrire les modèles 3D, les surfaces de la scène 3D. Les moteurs de rendu acceptent aussi des primitives simples, comme des points (utiles pour les particules), ou les lignes (utiles pour le rendu 2D). Les primitives sont toutes définies par un ou plusieurs points : trois sommets pour un triangle. Le regroupement des sommets en primitives s'explique par le fait que l'étape suivante du pipeline graphique, l'étape de rastérisation, associe des primitives et des pixels. On ne peut pas faire de la rastérisation avec des sommets isolés.
L'assemblage de primitives
modifierEn sortie de l'étage précédent, on n'a que des sommets éclairés et colorisés, pas des primitives. Et les sommets ne sont pas dans l'ordre : deux sommets qui en sortent à la suite ne sont pas forcément dans le même triangle. Pour recréer des primitives, on doit lire les sommets dans l'ordre adéquat, par paquets de trois pour obtenir des triangles. C'est le rôle de l'étape d'assemblage de primitives (primitive assembly), qui regroupe les sommets appartenant au même triangle, à la même primitive. L'assemblage des primitives est réalisée par un circuit fixe, non-programmable, qui utilise le tampon d'indice pour regrouper les sommets en primitives.
L'étape d'assemblage de primitives est suivie par un tampon de primitives, dans lequel les primitives sont accumulées avant d'entrer dans le rastériseur. Le contenu du tampon de primitive varie suivant la carte graphique, mais il y a deux possibilités principales. La première est simplement un paquet de sommets avec un petit tampon d'indices associé. L'autre est simplement un paquet de sommets, avec des sommets dupliqués s'ils sont partagés par plusieurs primitives. La première solution fait un meilleur usage de la mémoire du tampon de primitive, l'autre est plus simple et plus rapide, plus simple d'utilisation pour le rastériseur.
Les geometry shaders
modifierA la suite de l'assemblage des primitives, on trouve deux étapes optionnelles, implémentée avec des shaders. La première étape est réalisée par les geometry shaders, alors que la seconde étape qui s'occupe de la tesselation est le fait de plusieurs types de shaders différents. Les geometry shaders peuvent ajouter, supprimer ou altérer des primitives dans une scène 3D. Ils prennent entrée une primitive et fournissent en sortie zéro, une ou plusieurs primitives.
Rappelons que les geometry shaders sont optionnels et que beaucoup de jeux vidéos ou de moteurs de rendu 3D n'en utilisent pas. Un point important est que les geometry shaders sont exécutés par les processeurs de shaders, qui s'occupent de tous les shaders, qu'il s'agisse des pixels shaders, des vertex shaders ou des geometry shaders. Les geometry shaders ont été introduits avec DirectX 10 et OpenGl 3.2, et c'est avec DirectX 10 que les processeurs de shaders ont étés unifiés (rendu capable d’exécuter n'importe quel shader). Les geometry shaders sont assez limités, ce qui fait qu'ils sont assez peu utilisés. Ils sont surtout utilisés pour la gestion des cubemaps, le shadow volume extrusion, la génération de particules, et quelques autres effets graphiques. Ils pourraient en théorie être utilisés pour faire de la tesselation, comme on le verra plus bas, mais leurs limitations font que ce n'est pas pratique.
L'étape d’assemblage de primitives est dupliquée
modifierLes geometry shaders sont exécutés par les processeurs de shaders normaux. Leur place dans le pipeline graphique est quelque peu étrange. En théorie, ils sont placés après l'assembleur de primitive, car ils manipulent les primitives fournies par l'étape d'assemblage des primitives. Mais le résultat fournit par les geometry shaders doit être retraité par l'assembleur de primitive.
En effet, j'ai menti plus haut en disant que les geometry shaders fournissent en entrée de 0 à plusieurs primitives : la sortie d'un geometry shader est un ensemble de sommets, non-regroupés en primitives. Le résultat est que l'assembleur de primitive doit refaire son travail après le passage d'un geometry shader, pour déterminer les primitives finales. Et il faut aussi refaire le culling, au cas où les primitives générées ne soient pas visibles depuis la caméra. Heureusement, la sortie d'un geometry shader est soit un point, soit une ligne, soit un triangle strip, ce qui simplifie la seconde phase d'assemblage des primitives.
Avec les geometry shaders, il y a donc deux phases d'assemblage des primitives : une phase avant, décrite dans la section précédente, et une seconde phase simplifiée après les geometry shaders. Il n'y a pas que la phase d'assemblage de primitives qui est dupliquée : le tampon de primitives l'est aussi. On trouve donc un tampon de primitives à l'entrée des geometry shaders et un autre à la sortie.
L'implémentation des tampons de primitive est assez compliquée par la spécification des geometry shaders. Un geometry shader fournit un résultat très variable en fonction de ses entrées. Pour une même entrée, la sortie peut aller d'une simple primitive à plusieurs dizaines. Le geometry shader précise cependant un nombre limite de sommets qu'il ne peut pas dépasser en sortie. Il peut ainsi préciser qu'il ne sortira pas plus de 16 sommets, par exemple. Mais ce nombre est généralement très élevé, bien plus que la moyenne réelle du résultat du geometry shader.
Or, le tampon de primitives de sortie a une taille finie qui doit être partagée entre plusieurs instances du geometry shader. Et cette répartition n'est pas dynamique, mais statique : chaque instance reçoit une certaine portion du tampon de primitive, égale à la taille du tampon de primitives divisée par ce nombre limite. Aussi, le nombre d'instance exécutables en parallèle est rapidement limitée par le nombre de sommets maximal que peut sortir le geometry shader, nombre qui est rarement atteint en pratique.
La fonctionnalité de stream output
modifierUne fonctionnalité des geometry shaders est la possibilité d'enregistrer leurs résultats en mémoire. Il s'agit de la fonctionnalité du stream output. On peut ainsi remplir une texture ou le vertex buffer dans la mémoire vidéo, avec le résultat d'un geometry shader. Notons que celle-ci mémorise un ensemble de primitives, pas autre chose. Cette fonctionnalité est utilisée pour certains effets ou rendu bien précis, mais il faut avouer qu'elle n'est pas très souvent utilisée. Aussi, les concepteurs de cartes graphiques n'ont pas optimisé cette fonctionnalité au maximum. Le stream output n'a généralement pas accès prioritaire à la mémoire, comparé aux ROP, et n'a souvent accès qu'à une partie limitée de la bande passante mémoire. Notons qu'il existe deux formes de stream output : une qui permet aux vertex shader d'écrire dans une texture, l'autre qui permet aux geometry shaders de le faire.
Notons que le stream output fournit un flux de primitives, pas de sommets. En clair, beaucoup de sommets sont dupliqués et ont n'a pas d'index buffer. Les résultats du stream output sont donc assez lourds et prennent beaucoup de mémoire.
La tessellation
modifierLa tessellation est une technique qui permet d'ajouter des primitives à une surface à la volée. Les techniques de tesselation décomposent chaque triangle en sous-triangles plus petits, et modifient les coordonnées des sommets créés lors de ce processus. L'algorithme de découpage des triangles et la modification des coordonnées varie beaucoup selon la carte graphique ou le logiciel utilisé. Typiquement, les cartes graphiques actuelles ont un algorithme matériel pour le découpage des triangles qui est juste configurable, mais la modification des coordonnées des nouveaux sommets est programmable depuis DirectX 11.
Elle permet d'obtenir un bon niveau de détail géométrique, sans pour autant remplir la mémoire vidéo de sommets pré-calculés. Lire des sommets depuis la mémoire vidéo est une opération couteuse, même si les caches de sommets limitent la casse. La tesselation permet de lire un nombre limité de sommets depuis la mémoire vidéo, mais ajoute des sommets supplémentaires dans les unités de gestion de la géométrie. Les détails géométriques ajoutés par la tesselation demandent donc de la puissance de calcul, mais réduisent les accès mémoire.
L'historique de la tesselation sur les cartes 3D
modifierLes premières tentatives utilisaient des algorithmes matériels de tesselation, et non des shaders. Par exemple, la première carte graphique commerciale avec de la tesselation matérielle était la Radeon 8500, de l'entreprise ATI (aujourd'hui rachetée par AMD), avec la technologie de tesselation TrueForm. Elle utilisait un circuit non-programmable, qui tessellait certaines surfaces et interpolait la forme de la surface entre les sommets.
ATI améliora ensuite le TrueForm pour que des informations de tesselation soient lues depuis une texture, ce qui permet une implémentation de la technologie dite du displacement mapping. En même temps, Matrox ajouta un algorithme de tesselation basé sur la technique de N-patch dans ses cartes graphiques. Mais ces techniques se basaient sur des algorithmes matériels non-programmables, ce qui rendait ces technologies insuffisamment flexibles et impraticables. De plus, c'était des technologies propriétaires, que les autres fabricants de cartes graphiques n'ont pas adopté. Elles sont donc tombées en désuétude.
La tesselation a eu un regain d'intérêt à l'arrivée des geometry shaders dans DirectX 10 et OpenGL 3.2. Et il y avait de quoi, de tels shaders pouvant en théorie implémenter une forme limitée de tesselation. Mais le cout en performance est trop important, sans compter que les limitations de ces shaders n'a pas permis leur usage pour de la tesselation généraliste.
Une nouvelle étape a été franchie avec l'AMD Radeon HD2000 et le GPU de la Xbox 360, qui permettaient une tesselation partiellement programmable. La tesselation se faisait en deux étapes : une étape de découpage des triangles et une étape de modification des sommets créés. La première étape était un algorithme matériel configurable mais non-programmable, alors que la seconde était programmable. Mais le manque de support logiciel, le fait qu'on ne pouvait pas utiliser la tesselation en même temps que les geometry shader, ainsi que la non-utilisation de cette technique par NVIDIA, a fait que cette technique n'a pas été reprise dans les GPU suivants.
Il fallut attendre l'arrivée des tesselation shaders dans OpenGL 4.0 pour que des shaders adéquats arrivent sur le marché commercial. La tesselation sur ces cartes graphiques se fait en trois étapes : deux shaders et un algorithme matériel fixe. En fait, DirectX 11 rajoute une étape programmable avant le découpage des triangles par l'unité matérielle configurable.