Les cartes graphiques/Avant les GPUs : les cartes accélératrices 3D
Dans le chapitre précédent, nous avons vu les bases du rendu 3D. Nous avons parlé de textures, de rastérisation, des calculs d'éclairage, et de bien d'autres choses. Vers la fin du chapitre, nous avons parlé des shaders, des programmes informatiques exécutés sur la carte graphique. Mais ils n'ont pas été toujours présents ! Les anciennes cartes graphiques faisaient sans shaders. Elles étaient autrefois appelées des cartes accélératrices 3D, encore que la terminologie ne soit pas très précise.Nous les opposerons aux cartes graphiques capables d'exécuter des shaders, qui sont couramment appelées des Graphic Processing Units, des GPUs.
L'introduction des shaders a grandement modifié l'architecture des cartes graphiques. Il a fallu ajouter des processeurs pour exécuter les shaders, qui n'étaient pas là avant. Par contre, les circuits déjà présents ont été conservés, intégrés aux processeurs de shaders, ou remplacés par ceux-ci. D'un point de vue pédagogique, il est préférable de voir les cartes accélératrices 3D, avant de voir comment elles ont évolués vers des GPUs. Et nous allons voir cela dans deux chapitres. Ce chapitre portera sur les cartes accélératrices 3D, sans shaders, alors que le suivant expliquera comment s'est passée la transition vers les GPUs.
- Nous allons nous concentrer sur les cartes graphiques à placage de texture inverse, le placage de texture direct ayant déjà été abordé dans le chapitre précédent.
L'architecture d'une carte graphique 3D
[modifier | modifier le wikicode]Une carte accélératrice 3D est un carte d'affichage à laquelle on aurait rajouté des circuits de rendu 3D. Elle incorpore donc tous les circuits présents sur une carte d'affichage : un VDC, une interface avec le bus, une mémoire vidéo, des circuits d’interfaçage avec l'écran, un contrôleur DMA, etc. Le VDC s'occupe de l'affichage et éventuellement du rendu 2D, mais ne s'occupe pas du traitement de la 3D. Du moins, c'est le cas sur les cartes à placage de texture inverse. Le placage de texture direct utilise au contraire un VDC avec accélération 2D très performant, comme nous l'avons vu au chapitre précédent. Mais nous mettons ce cas particulier de côté.
La carte accélératrice 3D reçoit des commandes graphiques, qui proviennent du pilote de la carte graphique, exécuté sur le processeur. les commandes en question sont très variées, avec des commandes de rendu 3D, de rendu 2D, de décodage/encodage vidéo, des transferts DMA, et bien d'autres. Mais nous allons nous concentrer sur les commandes de rendu 3D, qui demandent à la carte accélératrice 3D de faire une opération de rendu 3D. Pour cela, elles précisent quel tampon de sommet utiliser, quelles textures utiliser, quels shaders sont nécessaires, etc.
La carte accélératrice 3D traite ces commandes grâce à deux circuits : des circuits de rendu 3D, et un chef d'orchestre qui dirige ces circuits de rendu pour qu'ils exécutent la commande demandée. Le chef d'orchestre s'appelle le processeur de commandes, et il sera vu en détail dans quelques chapitres. Pour le moment, nous allons juste dire qu'il s'occupe de la logistique, de la répartition du travail. Pour les commandes de rendu 3D, il commande les différentes étapes du pipeline graphique et s'assure que les étapes s’exécutent dans le bon ordre.

Les circuits de rendu 3D regroupent des circuits hétérogènes, aux fonctions fort différentes. Dans le cas le plus simple, il y a un circuit pour chaque étape du pipeline graphique. De tels circuits sont appelés des unités de traitement graphique. On trouve ainsi une unité pour le placage de textures, une unité de traitement de la géométrie, une unité de rasterization, une unité d'enregistrement des pixels en mémoire appelée ROP, etc. Les anciennes cartes graphiques fonctionnaient ainsi, mais on verra que les cartes graphiques modernes font un petit peu différemment.
Pour simplifier les explications, nous allons séparer la carte graphique en deux gros circuits bien distincts. En réalité, ils sont souvent séparés en sous-circuits plus petits, mais laissons cela de côté pour le moment.
- Les unités géométriques pour les calculs géométriques ;
- Les pipelines de pixel qui rastérisent l'image, plaquent les textures, et autres.
Les unités géométriques manipulent des triangles, sommets ou polygones, donc des données géométriques. Les unités de pixel font tout le reste, mais le gros de leur travail est de manipuler des pixels ou des texels. Dans ce chapitre, on considère que les deux sont des circuits fixes, nous verrons leur évolution vers des processeurs programmables dans le prochain chapitre.
Les circuits de traitement des pixels
[modifier | modifier le wikicode]Parlons un peu plus en détail des pipelines de pixels. Pour mieux comprendre ce qu'elles font, il est intéressant de regarder ce qu'il y a dans un pipeline de pixel. Un pipeline de pixel effectue plusieurs opérations les unes à la suite, dans un ordre bien précis. Et cela explique l'usage du terme "pipeline" pour les désigner. Et ces opérations sont souvent réalisées par des circuits séparés, qui sont :
- Un rastériseur qui fait le lien entre triangles et pixels ;
- Une unité de texture qui lit les textures et les plaque sur les modèles 3D ;
- Un ROP (Raster Operation Pipeline), qui gère grossièrement le tampon de profondeur (z-buffer).
Le circuit de rastérisation prend en charge la rastérisation proprement dite. Pour rappel, la rastérisation projette une scène 3D sur l'écran. Elle fait passer d'une scène 3D à un écran en 2D avec des pixels. Lors de la rastérisation, chaque sommet est associé à un ou plusieurs pixels, à savoir les pixels qu'il occupe à l'écran. Elle fournit aussi diverses informations utiles pour la suite du pipeline graphique : la profondeur du sommet associé au pixel, les coordonnées de textures qui permettent de colorier le pixel.
L'étape de placage de texture lit la texture associée au modèle 3D et identifie le texel adéquat avec les coordonnées textures, pour colorier le pixel. On travaille pixel par pixel, on récupère le texel associé à chaque pixel. Soit l'inverse du placage de texture direct, qui traversait une texture texel par texel, pour recopier le texel dans le pixel adéquat.
Après l'étape de placage de textures, la carte graphique enregistre le résultat en mémoire. Lors de cette étape, divers traitements de post-traitement sont effectués et divers effets peuvent être ajoutés à l'image. Un effet de brouillard peut être ajouté, des tests de profondeur sont effectués pour éliminer certains pixels cachés, l'antialiasing est ajouté, on gère les effets de transparence, etc. Un chapitre entier sera dédié à ces opérations.

Les circuits d'élimination des pixels cachés
[modifier | modifier le wikicode]L'élimination des surfaces cachées élimine les triangles invisibles à l'écran, car cachés par un objet opaque. En théorie, elle est prise en charge à la toute fin du pipeline, dans les ROPs, car cela permet de gérer la transparence. En effet, on ne sait pas si une texture transparente sera plaquée sur le triangle ou non. En clair, on doit éliminer les triangles invisibles après le placage de textures, et donc dans les ROP. Les ROPs se chargent à la fois de l’élimination des pixels cachées et de la transparence, les deux s’influençant l'un l'autre.

Il y a cependant des cas où on sait d'avance que les textures ne sont pas transparentes. Dans ce cas, la carte graphique utilise les circuits d'élimination des pixels cachés juste après la rastérisation. Cela permet d'éliminer à l'avance les triangles dont on sait qu'ils ne seront pas rendus.

Les deux possibilités coexistent sur les cartes graphiques modernes. Une carte graphique moderne peut éliminer les surfaces cachées avant et après la rastérisation, grâce à des techniques d'early-z dont nous parlerons plus tard, dans un chapitre dédié sur la rastérisation.
Les circuits d'éclairage
[modifier | modifier le wikicode]Les explications précédentes décrivent une carte graphique qui ne gère pas les techniques d'éclairage, et nous allons remédier à cela immédiatement. L'éclairage a été pris en charge avant même l'arrivée des shaders, dès les années 2000. Par contre, les cartes accélératrices pour PC géraient uniquement l'éclairage par sommet. Elles utilisaient un circuit non-programmable, appelé le circuit de Transform & Lightning, qui effectue les calculs d'éclairage par sommet (le L de T&L), en plus des calculs de transformation (le T de T&L). La première carte graphique à avoir intégré un circuit de T&L était la Geforce 256, la Geforce 1. L'unité de T&L a rapidement été remplacée par les vertex shader, dont nous reparlerons d'ici quelques chapitres. Dès la Geforce 3, ce remplacement été effectué.
L'unité de T&L calcule une couleur RGB pour chaque sommet/triangle, appelée la couleur de sommet. Une fois calculée par l'unité de T&L, la couleur de sommet est envoyée à l'unité de rastérisation. L'unité de rastérisation calcule la couleur des pixels à partir des trois couleurs de sommet. Pour cela, il y a deux méthodes principales, qui correspondent à l'éclairage plat et l'éclairage de Gouraud, qu'on a vu dans le chapitre précédent. Les cartes accélératrices utilisaient généralement l'éclairage de Gouraud.
L'éclairage de Gouraud effectue une interpolation, à savoir une sorte de moyenne pondérée de la couleur des trois sommets. L'éclairage de Gouraud demande donc d'ajouter un circuit d'interpolation pour les couleurs des sommets. Il fait normalement partie du circuit de rastérisation, comme on le verra dans le chapitre dédié sur la rastérisation. Pour donner un exemple, la console de jeu Playstation 1 gérait l'éclairage de Gouraud directement en matériel, mais seulement partiellement. Elle n'avait pas de circuit de T&L, ni de vertex shaders, mais intégrait une unité de rastérisation qui interpolait les couleurs de chaque sommet.
Enfin, il faut prendre en compte les textures. Pour cela, le pixel texturé est multiplié par la luminosité/couleur calculée par l'unité géométrique. Il y a donc un circuit de combinaison situé après l'unité de texture qui effectue la combinaison/multiplication. Le circuit de combinaison est parfois configurable, à savoir qu'on peut remplacer la multiplication par une addition ou d'autres opérations. Un tel circuit de combinaison s'appelle alors un combiner, dans la vieille nomenclature graphique de l'époque des années 90-2000.

Il a existé quelques rares cartes graphiques capables de faire de l'éclairage par pixel en matériel. Un exemple de carte graphique capable de faire cela est celle de la Nintendo DS, la PICA200. Créée par une startup japonaise, elle incorporait un circuit de T&L, un éclairage de Phong, du cel shading, des techniques de normal-mapping, de Shadow Mapping, de light-mapping, du cubemapping, de nombreux effets de post-traitement (bloom, effet de flou cinétique, motion blur, rendu HDR, et autres).
Pour l'éclairage de Phong, il faut ajouter une unité qui fasse les calculs d'éclairage par pixel, et renvoie son résultat. La couleur de pixel calculée est ensuite combinée avec une texture, avec un combiner. Du moins, si la carte accélératrice supporte les textures... Il faut aussi que le rastériseur interpole les normales, et non des couleurs de sommets comme avec l'éclairage de Gouraud. Les normales sont fournies par l'unité de T&L, ce qui demande une modification assez importante des unités de T&L et du rastériseur.

Voyons maintenant le bump-mapping et le normal-mapping. Pour rappel, les deux dernières mémorisent des informations d'éclairage dans une texture en mémoire vidéo. La texture contient des informations de relief pour le bump-mapping, des normales précalculées pour le normal-mapping. Pour cela, l'unité d'éclairage par pixel doit être reliée à l'unité de texture, mais l'implémentation matérielle n'est pas aisée.

Les cartes graphiques avec plusieurs unités parallèles
[modifier | modifier le wikicode]Plus haut, nous avons décrit une carte graphique basique, très basique, avec seulement quatre unités. Une unité pour les calculs géométriques, un rastériseur, une unité pour les pixels/textures et un ROP. Cependant, les cartes graphiques ayant cette architecture sont très rares, pour ne pas dire inexistantes. Il n'est pas impossible que les toutes premières cartes graphiques aient suivi à la lettre cette architecture, mais même cela n'est pas sur. La raison : toutes les cartes graphiques dupliquent les circuits précédents pour gagner en performance, mais aussi pour s'adapter aux contraintes du rendu 3D.
L'amplification des pixels et son impact sur les cartes graphiques
[modifier | modifier le wikicode]Un triangle prend une certaine place à l'écran, il recouvre un ou plusieurs pixels lors de l'étape de rastérisation. Le nombre de pixels recouvert dépend fortement du triangle, de sa position, de sa profondeur, etc. Un triangle peut donner quelques pixels lors de l'étape de rastérisation, alors qu'un autre va couvrir 10 fois de pixels, un autre seulement trois fois plus, un autre seulement un pixel, etc. Le cas où un triangle ne recouvre qu'un seul pixel est rare, encore que la tendance commence à changer avec les jeux vidéos récents de la décennie 2020 utilisant l'Unreal Engine et la technologie Nanite.
La conséquence est qu'il y a plus de travail à faire sur les pixels que sur les sommets, ce qui a reçu le nom d'amplification des pixels. La conséquence est qu'une unité géométrique prendra un triangle en entrée, l'enverra au rastériseur, qui fournira en sortie un ou plusieurs pixels à éclairer/texturer. Et cette règle un triangle = 1,N pixels fait qu'il y a un déséquilibre entre les calculs géométriques et ce qui suit, que ce soit le placage de textures, l'éclairage par pixel ou l'enregistrement des pixels dans le framebuffer. Et ce déséquilibre a un impact sur la manière dont un conçoit une carte graphique, ancienne comme moderne.
S'il y a une seule unité de texture/pixels, alors le rastériseur envoie chaque pixel à texturer/éclairé un par un à l'unité de pixel. Le rastériseur produits ces pixels un par un, avec un algorithme adapté pour. L'unité géométrique attendra le temps que la rastérisation ait fini de traiter tous les pixels du triangle précédent. Elle calculera le prochain triangle pendant ce temps, mais cela ne fera que limiter la casse si beaucoup de pixels sont générés.
Mais il est possible de profiter de l'amplification des pixels pour gagner en performances. L'idée est que le rastériseur produit plusieurs pixels en même temps, qui sont envoyés à plusieurs unités de texture et d'éclairage par pixel. Un exemple est illustré ci-dessous, avec une seule unité géométrique, mais quatre unités de texture, quatre unités d'éclairage par pixel, et quatre ROPs. Le rastériseur est conçu pour générer quatre pixels d'un seul coup si nécessaire.

La carte graphique précédente a des performances optimales quand un triangle recouvre 4 pixels : tout est fait en une seule passe. Si un triangle ne recouvre que 1, 2 ou 3 pixels, alors le rastériseur produira 1, 2 ou 3 et certaines unités suivant le rastériseur seront inutilisées. Mais si un triangle recouvre plus de 4 pixels, alors les pixels sont générés, texturés, éclairés et enregistrés en RAM par paquets de 4. En clair, la carte graphique peut s'adapter à l'amplification des pixels, mais pas parfaitement. Les GPU récents ont résolu partiellement ce problème avec un système de shaders unifiés, mais qu'on ne peut pas expliquer pour le moment.
Pour donner un exemple du monde réel, les premières cartes graphique de l'entreprise SGI était de ce type. SGI a été une entreprise pinière dans le domaine du rendu en 3D, qui a opéré dans les années 80-90, avant de progressivement décliner et fermer. Elle a conçu de nombreux systèmes de type workstation, donc destinés aux professionnels, avec des cartes graphiques dédiées. le grand public n'avait pas accès à ce genre de matériel, qui était très cher, vu qu'on n'était qu'au tout début de l'informatique. Nous ne détaillerons pas ces systèmes, car ils géraient leur mémoire vidéo d'une manière assez bizarre : elle était éclatée en plusieurs morceaux fusionnés chacun avec un ROP... Mais ils avaient tous une unité géométrique unique reliée à un rastériseur, qui alimentait plusieurs unités de texture/pixel et ROPs.
Plus proche de nous, certaines cartes graphiques pour PC étaient aussi dans ce cas. Les toutes premières cartes graphiques pour PC n'avaient même pas de circuits géométriques, et se contentaient d'un rastériseur, d'unités de texture et de ROPs. Par la suite, la Geforce 256 a introduit une unité géométrique appelée l'unité de T&L. Les cartes graphiques de l'époque ont suivi le mouvement et ont aussi intégrée une unité géométrique presque identique. La Geforce 256 avait une unité géométrique, mais 4 unités de texture, 4 unités d'éclairage par pixel et 4 ROPs.
Le multitexturing : dupliquer les unités de texture
[modifier | modifier le wikicode]Le multi-texturing est une technique très importante pour le rendu 3D moderne. L'idée est de permettre à plusieurs textures de se superposer sur un objet. Divers effets graphiques demandent d'ajouter des textures par-dessus d'autres textures, pour ajouter des détails, du relief, sur une surface pré-existante. Un exemple intéressant vient des jeux de tir : ajouter des impacts de balles sur les murs. Pour cela, on plaque une texture d'impact de balle sur le mur, à la position du tir. Il s'agit là d'un exemple de decals, des petites textures ajoutées sur les murs ou le sol, afin de simuler de la poussière, des impacts de balle, des craquelures, des fissures, des trous, etc.
Le multi-texturing implique que calculer un pixel implique de lire plusieurs textures. En général, un pixel avec multi-texturing demande de lire deux textures, rarement plus. La carte graphique doit alors être capable d'accéder à deux textures en même temps, ou du moins faire semblant que. De plus, elle doit combiner les deux textures pour générer le pixel voulu, ce qui demande d'ajouter un circuit qui combine deux texels (des pixels de texture) pour donner un pixel. La solution la plus simple est de doubler les unités de texture et de combiner les textures dans l'unité d'éclairage par pixel. Résultat : pour une unité d'éclairage par pixel, on a deux unités de textures.
La Geforce 2 et 3 utilisaient cette solution, dont le seul défaut est que la seconde unité de texture était utilisée seulement pour les objets sur lesquels le multi-texturing était utilisé. Les cartes ATI, le concurrent de l'époque de NVIDIA, aujourd'hui racheté par AMD, triplait les unités de texture. Mais cette possibilité était peu utilisée, la majorité des jeux se dépassant pas deux texture max par pixel. C'est sans doute pour cette raison que ce triplement a été abandonné à la génération suivante, les Radeon 9000 et 8500 se contentant de doubler les unités de texture.
| Nom de la carte graphique | Unités géométriques | Unité de texture | Unités de pixel | ROPs |
|---|---|---|---|---|
| Geforce 2 d'entrée de gamme | 1 | 2 | 4 | 2 |
| Geforce 2 milieu/haut de gamme, Geforce 3 | 1 | 4 | 8 | 4 |
| Radeon R100 bas de gamme | 1 | 1 | 3 | 1 |
| Radeon R100 autres | 1 | 2 | 6 | 2 |
L'usage de plusieurs unités géométriques
[modifier | modifier le wikicode]Pour encore augmenter les performances, il est possible d'utiliser plusieurs circuits de calcul géométriques, plusieurs unités géométriques. Et ce peu importe que ces unités soient des processeurs ou des circuits fixes non-programmables. Et pour cela, il existe deux grandes implémentations : utiliser plusieurs processeurs placés en série, ou les mettre en parallèle. Comprendre la première implémentation demande de faire quelques rappels sur les calculs géométriques.
L'usage d'un pipeline géométrique proprement dit
[modifier | modifier le wikicode]Pour rappel, le pipeline géométrique regroupe les quatre étapes suivantes :
- L'étape de chargement des sommets/triangles, qui sont lus depuis la mémoire vidéo et injectés dans le pipeline graphique.
- L'étape de transformation effectue deux changements de coordonnées pour chaque sommet.
- Premièrement, elle place les objets au bon endroit dans la scène 3D, ce qui demande de mettre à jour les coordonnées de chaque sommet de chaque modèle. C'est la première étape de calcul : l'étape de transformation des modèles 3D.
- Deuxièmement, elle effectue un changement de coordonnées pour centrer l'univers sur la caméra, dans la direction du regard. C'est l'étape de transformation de la caméra.
- La phase d'éclairage (en anglais lighting) attribue une couleur à chaque sommet, qui définit son niveau de luminosité : est-ce que le sommet est fortement éclairé ou est-il dans l'ombre ?
- La phase d'assemblage des primitives regroupe les sommets en triangles.
- Les phases de clipping ou le culling agissent sur des sommets/triangles/primitives, même si elles sont souvent regroupées dans l'étape de rastérisation.
Si on met de côté le chargement des sommets/triangles, il est possible de faire tous ces calculs en bloc, dans un seul processeur ou une seule unité de T&L. Mais une autre idée, plus simple, attribue un processeur/circuit pour chaque étape. En faisant cela, on peut traiter plusieurs triangles/sommets en même temps, chacun étant dans une étape différente, chacun dans un processeur/circuit. Ceux qui auront déjà lu un cours d'architecture des ordinateurs reconnaitront la fameuse technique du pipeline, mais appliquée ici à un algorithme plus conséquent.
Les processeurs sont en série, et chaque processeur reçoit les résultats du processeur précédent, et envoie son résultat au processeur suivant. Sauf en début ou en bout de chaine, évidemment. Pour donner un exemple, les premières cartes graphiques de SGI utilisaient 10/12 processeurs enchainés l'un à la suite de l'autre. Les 4 premiers géraient les étapes de transformation, les 6 suivants faisaient les opérations de clipping/culling, les deux derniers faisaient la rastérisation proprement dite.
Pour lisser les transferts de données, il est possible d'ajouter des mémoires FIFOs entre les processeurs. Comme ça, si un processeur est bloqué par un calcul un peu trop long, cela ne bloque pas les processeurs précédents. A la place, le processeur précédent accumule des résultats dans la mémoire FIFOs, qui seront consommé ultérieurement.
En théorie, on peut s'attendre à ce que la performance soit multipliée par le nombre de processeurs. En réalité, les étapes sont rarement équilibrées, certaines étapes prennent beaucoup plus de temps que les autres, ce qui fait que la répartition des calculs n'est pas idéale : certains processeurs attendent que le processeur suivant ait finit son travail. De plus, l'organisation en pipeline entraine des couts de transmission/communication entre étapes, notamment si on utilise des mémoires FIFOs entre processeurs, ce qui est toujours le cas.
Cette implémentation n'a été utilisée que sur les toutes premières cartes graphiques, avant l'apparition des PC grand public. Les systèmes SGI, utilisés pour des stations de travail, utilisaient cette architecture, par exemple. Mais elle est totalement abandonnée depuis les années 90.
L'usage de plusieurs unités géométriques en parallèle
[modifier | modifier le wikicode]La seconde solution utilise plusieurs unités géométriques en parallèle. Chaque unité géométrique traite un triangle/sommet de bout en bout, en faisant transformation, éclairage, etc. Mais vu qu'il y en a plusieurs, on peut traiter plusieurs triangles/sommets : un dans chaque unité géométrique. C'est la solution retenue sur toutes les cartes graphiques depuis les années 90. Mais la présence de plusieurs unités géométriques a deux conséquences : il faut alimenter plusieurs unités géométriques en triangles/sommets, il faut gérer l'envoi des triangles au rastériseur. Les deux demandent des solutions distinctes.
La répartition du travail sur les unités géométriques est déléguée au processeur de commandes. Il utilise les unités géométriques à tour de rôle : on envoie le premier triangle à la première unité, le second triangle à la seconde unité, le troisième triangle à la troisième, etc. Il s'agit de ce que l'on appelle l'algorithme du tourniquet, qui est assez efficace malgré sa simplicité. Il marche assez bien quand tous les triangles/sommets mettent approximativement le même temps pour être traités. Si le temps de calcul varie beaucoup d'un triangle/sommet à l'autre, une solution toute simple détecte quels sont les processeurs de shaders libres et ceux occupés. Il suffit alors d'appliquer l'algorithme du tourniquet seulement sur les processeurs de shaders libres, qui n'ont rien à faire.
Un autre problème survient cette fois-ci en sortie des unités géométriques. Comment connecter plusieurs unités géométriques au reste de la carte graphique ? Évidemment, la carte graphique contient plusieurs unités de texture/pixel et plusieurs ROPs. Elle tient compte de l'amplification des pixels, ce qui fait qu'il y a moins d'unités géométriques que d'autres circuits, entre 2 à 8 fois moins environ. Pour créer une carte graphique avec plusieurs unités géométriques, il y a plusieurs solutions, que nous allons détailler dans ce qui suit. Pour les explications, nous allons prendre l'exemple de cartes graphiques avec 2 unités géométriques et 8 unités de texture/pixel, et autant de ROPs.
La première solution serait simplement de dupliquer les circuits précédents, en gardant leurs interconnexions. Pour l'exemple, on aurait 2 unités géométriques, chacune connectée à 4 unités de textures/pixels. L'unité géométrique est suivie par un rastériseur qui alimente 4 unités de texture/pixel, comme c'était le cas dans la section précédente. L'implémentation est alors très simple : on a juste à dupliquer les circuits et à modifier le processeur de commande. Il faut aussi modifier les connexions des ROPs à la mémoire vidéo. Mais les interconnexions avec le rastériseur ne sont pas modifiées.
Un désavantage est que l'amplification des pixels n'est pas gérée au mieux. Imaginez que l'on ait deux triangles à rastériser, qui génèrent 8 pixels en tout : un qui génère 6 pixels à la rastérisation, l'autre seulement 2. Il n'est pas possible de traiter les 8 pixels générés. Le triangle générant deux pixels va alimenter deux unités de texture/pixels et en laisser deux inutilisées, l'autre triangle sera traité en deux fois (4 pixels, puis 2). La duplication bête et méchante n'utilise donc pas à la perfection les unités de texture/pixel.
Une autre solution permet de gérer à la perfection l'amplification des pixels. Elle consiste à utiliser un seul rastériseur à haute performance, sur lequel on connecte les unités géométriques et les unités de texture/pixel. L'idée est que le rastériseur peut recevoir N triangles à la fois et alimenter M unités de texture/pixels. Le rastériseur unique s'occupe de faire plusieurs rastérisations de triangles à la fois, et répartit automatiquement les pixels générés sur les unités de texture/pixel. Pour donner un exemple, le GPU Geforce 6800 de NVIDIA avait 6 unités géométriques, 16 unités faisant à la fois placage de textures et éclairage par pixel, et 16 ROPs. Un point important avec ce GPU est qu'il n'avait qu'un seul rastériseur, détail sur lequel on reviendra dans ce qui suit !
Les cartes graphiques en mode immédiat et à tuile
[modifier | modifier le wikicode]Il est courant de dire qu'il existe deux types de cartes graphiques : celles en mode immédiat, et celles avec un rendu en tuiles (tiles). Il s'agit là des deux types principaux de cartes graphiques à l'heure actuelle, mais quelques architectures faisaient autrement dans le passé. Une autre classification, plus générale, sépare les cartes graphiques en cartes graphiques sort-last, sort-first et sort-middle. Les cartes graphiques en mode immédiat correspondent aux cartes graphiques en mode immédiat, alors que le rendu à tuile est une sous-catégorie des cartes graphiques sort-middle.
La différence entre les deux est liée à la manière dont les pixels/primitives sont réparties sur l'écran. Leur existence est liée au fait que les API graphiques imposent que les triangles envoyées à la carte graphique soient traités dans l'ordre. Le tampon de sommets contient en effet une liste de sommets/triangles, qui sont censés être traités dans l'ordre d'arrivée. Et si je dis censé être, c'est parce que la carte graphique ne va pas forcément traiter les triangles/pixels dans l'ordre.
A la place, elle va traiter des triangles/pixels en parallèle, et il n'est pas garantit que les résultats sortent des circuits dans l'ordre d'arrivée. Après tout, certains triangles sont traités plus rapidement que d'autres, idem pour les pixels. La carte graphique doit donc remettre les résultats dans l'ordre. L'endroit du pipeline où se fait cette remise en ordre est ce qui fait la différence entre cartes graphioques sort last et sort middle.
Les trois types de cartes graphiques : sort-first, sort-middle et sort-last
[modifier | modifier le wikicode]Les cartes graphiques sort-first ont plusieurs pipelines séparés, chacun traitant une partie de l'écran. Ils déterminent la position des triangles à l'écran, puis répartissent les triangles dans les pipelines adéquats. Par exemple, on peut imaginer un GPU sort-first avec quatre unités séparées, chacune traitant un quart de l'écran. Au tout début du rendu, une unité de répartition détermine la position d'un triangle à l'écran, et l'envoie à l'unité adéquate. Si le triangle est dans le coin inférieur gauche, il sera envoyé à l'unité dédiée à ce coin. S'il est situé au milieu de l'écran, il sera envoyé aux quatre unités, chacune ne traitant les pixels que pour son coin à elle.
Les cartes graphiques sort-middle découpent l'écran en carrés de 4, 8, 16, 32 pixels de côté , qui sont rendus séparément les uns des autres. Les morceaux d'image en question sont appelés des tiles en anglais, mot que nous avons décidé de ne pas traduire pour ne pas le confondre avec les tuiles du rendu 2D. Il y a une assignation stricte entre une unité de pixel/texture et une tile. Par exemple, sur un système avec deux unités de texture/pixel, la première unité traitera les tiles paires, l'autre unité les tiles impaires.
Les cartes graphiques sort-last sont l'extrême inverse. Ils ont des unités banalisées qui se moquent de l'endroit où se trouve un pixel à l'écran. Leurs unités géométriques traitent des polygones sans se préoccuper de leur place à l'écran. Le rastériseur envoie les pixels aux unités de textures/ROPs sans se soucier de leur place à l'écran. Encore que quelques optimisations s'en mêlent pour profiter au mieux des caches de texture et des caches intégrés aux ROPs, mais l'essentiel est qu'il n'y a pas de répartition fixe. Il n'y a pas de logique du type : ce pixel ou ce triangle est à tel endroit à l'écran, on l'envoie vers telle unité de texture/ROP. Ce sont les ROPs qui se chargent d'enregistrer les pixles finaux au bon endroit dans le framebuffer. La gestion de la place des pixels à l'écran se fait donc à la toute fin du pipeline, d'où le nom de sort-last.
Pour résumer, les trois types de cartes graphiques se distinguent suivant l'endroit où les triangles/pixels sont répartis suivant leur place à l'écran. Avec le sort-first, ce sont les triangles qui sont triés suivant leur place à l'écran. Le tri a donc lieu avant les unités géométriques. Avec le sort-middle, ce sont les fragments générés par la rastérisation qui sont triés suivant leur place à l'écran, d'où l'existence de tiles. Le tri a lieu entre les unités géométriques et le rastériseur. Les unités géométriques se moquent de la place à l'écran des primitives qu'ils traitent, mais pas les rastériseurs et les unités de texture. Enfin, avec le sort-last, ce sont les pixels finaux qui sont triés selon leur place à l'écran, seuls les ROPs se préoccupent de cette place à l'écran.
Concrètement, les cartes graphiques de type sort-first sont très rares, l'auteur de ce cours n'en connait aucun exemple. Les deux autres types de cartes graphiques sont eux beaucoup plus communs. Reste à voir ce qu'il y a à l'intérieur d'une carte graphique sort-middle et/ou sort-last. Pour simplifier les explications, nous allons regrouper les circuits de traitement des pixels dans un seul gros circuits appelé le rastériseur, par abus de langage. La carte graphique est donc composée de deux circuits : l'unité géométrique et le mal-nommé rastériseur. Les cartes graphiques ajoutent des mémoires caches pour la géométrie et les textures, afin de rendre leur accès plus rapide.

Les cartes graphiques sort-last, en mode immédiat
[modifier | modifier le wikicode]Les cartes graphiques en mode immédiat implémentent le pipeline graphique d'une manière assez évidente. L'unité géométrique envoie des triangles au rastériseur, qui lui-même envoie les pixels à l'unité de texture, qui elle-même envoie le pixel texturé au ROP. Elles effectuent le rendu 3D triangle par tringle, pixel par pixel. Un point important est que pendant que le pixel N est dans les ROP, les pixels N+1 est dans l'unité de texture, le pixel N+2 est dans le rastériseur et le triangle suivant est dans l'unité géométrique. En clair, on n'attend pas qu'un triangle soit affiché pour en démarrer un autre.
Un problème est qu'un triangle dans une scène 3D correspond souvent à plusieurs pixels, ce qui fait que la rastérisation prend plus de temps de calcul que la géométrie. En conséquence, il arrive fréquemment que le rastériseur soit occupé, alors que l'unité de géométrie veut lui envoyer des données. Pour éviter tout problème, on insère une petite mémoire entre l'unité géométrique et le rastériseur, qui porte le nom de tampon de primitives. Elle permet d'accumuler les sommets calculés quand le rastériseur est occupé.

Le tout peut s'adapter à la présence de plusieurs unités géométriques, de plusieurs unités de texture ou processeurs de shaders, tant qu'on conserve un rastériseur unique. Il suffit alors d'adapter le tampon de primitive et le rastériseur. Si on veut rajouter des unités de texture ou des processeurs de pixel shaders, le tampon de primitives n'est pas concerné : il suffit que le rastériseur ait plusieurs sorties, une par unité de texture/pixel shader. Par contre, la présence de plusieurs unités géométriques impacte le tampon de primitive.
Avec plusieurs unités géométriques, il y a deux solutions : soit on garde un tampon de primitive unique partagé, soit il y a un tampon de primitive par unité géométrique. Avec la première solution, toutes les unités géométriques sont reliées à un tampon de primitives unique. Le tampon de primitive est conçu pour qu'on puisse écrire plusieurs primitives dedans en même temps. Le rastériseur n'a pas à être modifié. Une autre solution utilise un tampon de primitive par unité géométrique. Le rastériseur peut alors piocher dans plusieurs tampons de primitive, ce qui demande de modifier le rastériseur. Il y a alors un système d'arbitrage, pour que le rastériseur pioche des primitives équitablement dans tous les tampons de primitive, pas question que l'un d'entre eux soit ignoré durant trop longtemps.
Les cartes graphiques sort-middle des années 90
[modifier | modifier le wikicode]Voyons maintenant les architectures sort-middle utilisée dans les années 80-90, à une époque où les cartes graphiques grand public n'existaient pas encore. Les cartes graphiques de l’entreprise SGI sont dans ce cas, mais aussi le Pixel Planes 5, et de nombreux autres systèmes graphiques. Elles utilisaient un rendu à tile assez original. Dans ce qui suit, nous allons décrire l'architecture des systèmes SGI, qui sont représentatifs.
L'idée était que l'image était découpée en un nombre de tiles qui variait selon le système utilisé, mais qui était au minimum de 5 et pouvait aller jusqu'à 20. Et chaque tile avait sa propre unité de traitement, qui contenait un rastériseur, une unité de texture, un ROP, etc. En clair, la carte graphique contenait entre 5 et 20 unités de traitement séparées, chacune dédiée à une tile.
Les triangles sortant des unités géométriques étaient envoyés à toutes les unités de traitement, sans exception. Une fois le triangle réceptionné, l'unité de traitement déterminait si le triangle s'affichait dans la tile associée ou non. Si c'est le cas, le rastériseur rastérise le triangle, génère les pixels, les textures sont lues, puis le tout est enregistré en mémoire vidéo. Si ce n'est pas le cas, elle abandonne le polygone/triangle reçu. Si le triangle est partiellement dans la tile, le rastériseur génère les pixels qui sont dans la tile, par les autres.
Précisons que les cartes de ce style incorporaient un tampon de primitive, ce qui permettait de simplifier la conception de la carte graphique. Sur la carte Infinite Reality, le tampon de primitive faisait 4 méga-octets de RAM, ce qui permettait de mémoriser 65 536 sommets. Sur la carte Reality Engine, il y avait même plusieurs tampons de primitives, un par unité géométrique. Les polygones sortaient des unités géométriques, étaient accumulés dans les tampons de primitives, puis étaient broadcastés à toutes les unités de traitement. Pour cela, le bus en bleu dans le schéma précédent est en réalité un réseau crossbar avec un système de broadcast.
Une caractéristique de ces architectures est qu'elles mettent le framebuffer à part de la mémoire vidéo. De plus, ce framebuffer est lui-même découpée en tile. Sur la carte Reality Engine, le framebuffer est découpé en 5 à 20 sous-framebuffer, un par tile. Et chaque mini-framebuffer est placé dans l'unité de traitement de la tile associée ! Ainsi, au lieu de connecter 5-20 ROPs à une mémoire vidéo unique, chaque ROP contient une RAM tile, qui mémorise la tile en cours de traitement. Évidemment, cela pose quelques problèmes pour la connexion au VDC, en raison de l'absence de framebuffer unique, mais rien d'insurmontable. L'architecture est illustrée ci-dessous.
- Le Pixel Planes 5 avait un système similaire, mais avait en plus un framebuffer complet, dans lequel les sous-framebuffer étaient recopiés pour obtenir l'image finale.

Un autre détail de l'architecture est lié à la mémoire pour les textures. Les concepteurs de SGI ont décidé de séparer les textures dans une mémoire à part du reste de la mémoire vidéo. Il n'y a pour ainsi dire pas de mémoire vidéo proprement dit : la géométrie à rendre est dans une mémoire à part, idem pour les textures, et pour le framebuffer. On s'attendrait à ce que la mémoire de texture soit reliée aux 5-20 unités de texture, mais les concepteurs ont décidé de faire autrement. A la place, chaque unité de texture contient une copie de la mémoire de texture, qui est donc dupliquée en 5-20 exemplaires ! Difficile de comprendre la raison de ce choix, mais cela simplifiait sans doute les interconnexions internes de la carte graphique, au prix d'un cout en RAM assez important.
Les cartes graphiques à rendu à tile
[modifier | modifier le wikicode]Les cartes graphiques de SGI, vus précédemment, disposent d'une unité de traitement par tile. Faire ainsi permet de nombreuses optimisations, comme éclater le framebuffer en plusieurs RAM tile. Mais le cout en matériel est conséquent. Pour économiser des circuits, l'idéal serait d'utiliser moins d'unités de traitement pour les pixels/fragments/textures. Mais pour cela, il faut profondément modifier l'architecture précédente. On perd forcément le lien entre une unité de traitement et une tile. Et cela impose de revoir totalement la manière dont les unités géométriques communiquent avec les unités de traitement.
La solution retenue est celle des cartes graphiques à rendu en tile proprement dit, aussi appelés cartes graphiques TBR (Tile Based Rendering). Les plus simples n'utilisent qu'une seule unité de traitement et n'ont qu'une seule RAM tile. En conséquence, les tiles sont rendues l'une après l'autre. Au lieu de rendre chaque triangle/polygone l'un après l'autre, la géométrie est intégralement rendue avant de faire la rastérisation. Les triangles sont enregistrés dans la mémoire vidéo et regroupés par tile, avant la rastérisation. La mémoire vidéo contient donc plusieurs paquets de triangles, avec un paquet par tile. Les paquets/tiles sont envoyées au rastériseur un par un, la rastérisation se fait tile par tile.
La RAM tile existe toujours, même si son utilité est différente. La RAM tile accélère le rendu d'une tile, car tout ce qui est nécessaire pour rendre une tile est mémorisé dedans : la tile, le tampon de profondeur, le tampon de stencil et plein d'autres trucs. Pas besoin d’accéder à un gigantesque z-buffer pour toute l'image, juste d'un minuscule z-buffer pour la tile en cours de traitement, qui tient totalement dans la SRAM.
- Il faut noter que les tiles sont généralement assez petites : 16 ou 32 pixels de côté, rarement plus. En comparaison, les tiles faisaient 128 pixels de côté pour les cartes de SGI.

Il est possible pour une carte graphique TBR de traiter plusieurs tiles en même temps, en parallèle, dans des unités séparées. Un exemple est celui du GPU ARM Mali 400, qui dispose d'une unité géométrique (un processeur de vertex), mais 4 processeurs de pixels. Il peut donc traiter quatre tiles en même temps, chacune étant rendue dans un processeur de pixel dédié. Les 4 processeurs de pixels ont chacun leur propre RAM tile rien qu'à eux.
La présence d'une RAM tile a de nombreux avantages et impacte grandement l'architecture de la carte graphique. En premier lieu, les ROPs sont drastiquement modifiés. De nombreux GPU TBR n'ont même pas de ROPs ! A la place, les ROPs sont émulés par les processeurs de pixel shader. Les pixel shaders peuvent lire ou écrire directement dans le framebuffer, sur les GPU TBR, ce qui leur permet d'émuler les ROPs avec des instructions mathématique/mémoire. Le driver patche automatiquement les pixel shader pour ajouter de quoi émuler les ROPs à la fin des pixel shaders. Cela garantit une économie de circuits non-négligeable.
La présence d'une RAM tile fait que le tampon de profondeur disparait. Par contre, les cartes graphiques de type TBR doivent enregistrer les triangles en mémoire vidéo, et les trier par paquets. Cela compense partiellement, totalement, ou sur-compense, les économies liées à la RAM tile. Le regroupement des triangles par tile s'accompagne de quelques optimisations assez sympathiques. Par exemple, les GPU TBR modernes peuvent trier les triangles selon leur profondeur, directement lors du regroupement en paquets. L'avantage est que cela permet à l'élimination des pixels cachés de fonctionner au mieux. L'élimination des pixels cachés fonctionne à la perfection quand les triangles sont triés du plus proche au plus lointain, pour les objets opaques. Les cartes graphiques en mode immédiat ne peuvent pas faire ce tri, mais les cartes graphiques TBR peuvent le faire, soit totalement, soit partiellement.
Un autre avantage est que l’antialiasing est plus rapide. Pour ceux qui ne le savent pas, l'antialiasing est une technique qui améliore la qualité d’image, en simulant une résolution supérieure. Une image rendue avec antialiasing aura la même résolution que l'écran, mais n'aura pas certains artefacts liés à une résolution insuffisante. Et l'antialiasing a lieu dans et après la rastérisation, et augmente la résolution du tampon de profondeur et du z-buffer. Les cartes graphiques en mode immédiat disposent d'optimisations pour limiter la casse, mais les ROP font malgré tout beaucoup d'accès mémoire. Avec le rendu en tiles, l'antialising se fait dans la RAM tile, n'a pas besoin de passer par la mémoire vidéo et est donc plus rapide.
Des compromis différents
[modifier | modifier le wikicode]Les cartes graphiques des ordinateurs de bureau ou portables sont toutes en mode immédiat, alors que celles des appareils mobiles, smartphones et autres équipements embarqués ont un rendu en tiles. Les raisons à cela sont multiples, mais la principale est que le rendu en tiles marche beaucoup mieux pour le rendu en 2D, comparé aux architectures en mode immédiat, ce qui se marie bien aux besoins des smartphones et autres objets connectés.
La performance d'une carte graphique est limitée par la quantité d'accès mémoire par seconde. Autant dire que les économiser est primordial. Et les cartes en mode immédiat et par tile ne sont pas égales de ce point de vue. En mode immédiat, le tampon de primitives évite de passer par la mémoire vidéo, mais le z-buffer et le framebuffer sont très gourmand en accès mémoire. Avec les architectures à tile, c'est l'inverse : la géométrie est enregistrée en mémoire vidéo, mais le tampon de profondeur n'utilise pas la RAM vidéo.
Au final, les deux architectures sont optimisées pour deux types de rendus différents. Les cartes à rendu en tile brillent quand la géométrie n'est pas trop compliquée, et que la résolution est grande ou que l'antialising est activé. Les cartes en mode immédiat sont douées pour les scènes géométriquement lourdes, mais avec peu d'accès aux pixels. Le tout est limité par divers caches qui tentent de rendre les accès mémoires moins fréquents, sur les deux types de cartes, mais sans que ce soit une solution miracle.
La performance des anciennes cartes graphiques 3D
[modifier | modifier le wikicode]Intuitivement, la performance d'une carte graphique dépend de la performance de chacun de ses circuits : processeur de commande, mémoire vidéo, circuits de rendu 3D, VDC, etc. En pratique, il est rare qu'on soit limité par le VDC ou le processeur de commande. Les seules limitations viennent des circuits de rendu 3D et de la mémoire vidéo.
Nous ne pouvons pas aborder la performance de la mémoire vidéo pour le moment. Tout ce que l'on peut dire est qu'il faut qu'elle soit assez rapide pour alimenter le rendu 3D en données. Les circuits de rendu 3D doivent lire des triangles et textures en mémoire vidéo, qui doit être assez rapide pour ça et ne pas les faire attendre. Pour le reste, voyons la performance des circuits de rendu 3D.
Il ne nous est là aussi pas possible de détailler ce qui impacte la performance d'un GPU moderne. Dès que des processeurs de shaders sont impliqués, parler de performance demande de connaitre sur le bout des doigts les processeurs de shaders, ce qu'on n'a pas encore vu à ce stade du cours. Par contre, on peut détailler ce qu'il en était pour les anciennes cartes 3D, sans processeurs de shaders. Elles contenaient des ROPs, des unités de texture, un rastériseur et une unité géométrique (l'unité de T&L).
Étudions d'abord la performance des unités de texture et des ROPs. Cela nous permettra de parler d'un paramètre qui avait son importance sur les anciennes cartes graphiques, avant les années 2000 : le fillrate. Le fill rate, ou taux de remplissage, est une ancienne mesure de performance autrefois utilisée pour comparer les cartes graphiques entre elles. Il s'agit d'une mesure assez approximative, au même titre que la fréquence d'horloge. Concrètement, plus il est élevé, meilleures seront les performances, en théorie. Mais attention : les petites différences de fillrate ne suffisent pas à rendre un verdict. De plus, il existe deux types distincts de fillrate : le Texture Fillrate et le Pixel Fillrate. Voyons d'abord le Pixel Fillrate.
Le pixel fillrate : la performance des ROPs
[modifier | modifier le wikicode]Le pixel fillrate est le nombre maximal de pixels que la carte graphique peut écrire en mémoire vidéo par seconde. Il est exprimé en Méga-Pixels par seconde ou en Giga-Pixels par seconde, souvent abréviés en GP/s et MP/s. C'est une unité que vous croisez sans doute pour la première fois et qui mérite quelques explications.
Premièrement, dans méga-pixels par seconde, il y a mégapixels. Il s'agit d'une unité pour compter le nombre de pixels d'une image. Un mégapixel signifie tout simplement un million de pixels, un gigapixel signifie un milliard de pixels. Je précise bien un million et un milliard, ce ne sont pas des multiples de 1024, comme on est habitué à en voir en informatique. Le nombre de pixels d'une image augmente avec la résolution utilisée, mais il reste de l'ordre du mégapixel, guère plus. Voici un tableau avec les résolutions les plus utilisées et le nombre de pixels associé.
| Résolution | Nombre de pixels |
|---|---|
| Résolutions anciennes en 4:3 | |
| 640 × 480 | 307 200 0,3 MP |
| 800 × 600 | 480 000 = 0,48 MP |
| 1 024 × 768 | 786 432 0,8 MP |
| 1 280 × 960 | 1 228 800 1,2 MP |
| 1 600 × 1 200 | 1 920 000 = 1,92 MP |
| Résolutions modernes en 16:9 | |
| 1 920 × 1 080 | 2 073 600 2 MP |
| 3 840 × 2 160 (4k) | 8 294 400 8.3 MP |
Maintenant, regardons ce qui se passe si on veut rendre plusieurs images par secondes. Intuitivement, on se dit qu'il faudra un pixel fillrate minimal pour cela. Et il se trouve qu'on peut le calculer aisément. Prenons par exemple une image en 1600 × 1200, de 1,92 mégapixels. Si on veut avoir 60 images par secondes, avec cette résolution, cela fait 1,92 * 60 mégapixels par secondes. En clair, le pixel fillrate minimal se calcule en multipliant la résolution par le framerate. Le pixel fillrate minimal tourne autour de la centaine de mégapixels par seconde, voire approche le gigapixel par seconde en haute résolution. Les images font entre 1 et 10 mégapixels, pour environ 100 FPS, l'intervalle colle parfaitement.
Maintenant, comparons un peu avec ce dont sont capables les GPUs. Les toutes premières cartes graphiques commerciales avaient un pixel fillrate proche de la centaine de méga-pixels par seconde. Pour donner un exemple, la Geforce 256 avait un pixel fillrate de 480 MP/s, la Geforce 3 faisait entre 700 et 960 MP/s selon le modèle. De nos jours, le pixel fillrate est de l'ordre de la centaine de Gigapixels. Pour donner un exemple, les Geforce RTX 5000 ont un pixel fillrate de 82.3GP/s pour la RTX 5050, à 423.6 GP/S pour la RTX 5090. Les GPU ont un pixel fillrate qui dépasse de très loin la valeur minimale, ce qui est franchement étrange.
La raison à cela est que le pixel fillrate minimal se calcule sous l'hypothèse que chaque pixel de l'image finale ne sera écrit qu'une seule fois. Mais dans les faits, il est fréquent qu'un pixel soit dessiné plusieurs fois avant d'obtenir l'image finale. La raison principale est liée aux surfaces cachées. Si un objet est derrière un autre, il arrive que celui-ci soit dessiné dans le framebuffer, avant que l'objet devant soit re-dessiné par-dessus. Des pixels ont alors été écrits, puis ré-écrits.
Le fait de dessiner un pixel plusieurs fois porte un nom. Il s'agit d'un phénomène d'overdraw, ou sur-dessinage en français. Le sur-dessinage fait que le pixel fillrate minimal ne suffit pas en pratique. Pour éviter tout problème, le pixel fillrate du GPU doit être supérieur au pixel fillrate minimal, d'environ un ordre de grandeur. L'élimination des surfaces cachées réduit l'overdraw, mais elle ne fait pas de miracles. En pratique, le sur-dessinage ne concerne qu'une partie assez mineure des pixels de l'image, et un pixel est rarement écrit plus d'une dizaine de fois. Et les GPus modernes ont un pixel fillrate tellement démentiel qu'il n'est presque jamais un facteur limitant.
Le pixel fillrate d'un GPU dépend de plusieurs choses : le nombre de ROPs, leur fréquence d'horloge exprimée en MHz/GHz, la bande passante mémoire, et bien d'autres. En théorie, la bande passante mémoire n'est pas un point limitant, les concepteurs du GPU prévoient une mémoire suffisamment rapide pour qu'elle puisse encaisser le pixel fillrate maximal, tout en ayant encore de la marge pour lire des textures et la géométrie. En clair, le pixel fillrate est surtout dépendant des ROPs, de leur nombre, de leur vitesse, de leur implémentation.
Le pixel fillrate du GPU est difficile à calculer, mais l'approximation la plus utilisée est la suivante. Elle part du principe qu'un ROP peut écrire un pixel par cycle d'horloge. Ce n'est pas forcément le cas, tout dépend de l'implémentation des ROPs. Certains GPU performants ont des ROPs capables d'écrire des blocs de 8*8 pixels d'un seul coup en mémoire vidéo, alors que d'anciens GPU font avec des ROPs limités, seulement capables d'écrire un pixel tout les 10 cycles d'horloge. Toujours est-il qu'avec cette hypothèse, le pixel fillrate est égal au nombre de ROPs, multiplié par leur fréquence d'horloge.
Je précise "leur" fréquence d'horloge, car il est possible de faire fonctionner l'unité de T&L, les ROPs, les unités de texture et le rastériseur à des fréquences différentes. C'est parfaitement possible, le cout en performance est parfois assez faible, mais le gain en consommation d'énergie est souvent important. Et justement, il a existé des GPU sur lesquels les ROPs avaient une fréquence inférieure à celle du reste du GPU. Dans ce cas, c'est la fréquence des ROPs qui est importante. Mais rassurez-vous : sur la majorité des GPUs actuels, les ROPs vont à la même fréquence que le reste du GPU.
Le texture fillrate : la performance des unités de texture
[modifier | modifier le wikicode]Le texture fillrate est l'équivalent du pixel fillrate, mais pour les textures. Pour rappel, une texture est avant tout une image, composée de pixels. Pour éviter toute confusion, ces pixels de textures sont appelés des texels. Le texture fillrate est le nombre de texels que la carte graphique peut plaquer par seconde, dans le meilleur des cas. Il est mesuré en mégatexels par secondes, voire en gigatexels par secondes.
L'interprétation de ce chiffre dépend de si on le mesure en entrée ou en sortie des unités de texture. En effet, les unités de texture intègrent des fonctionnalités de filtrage de texture, qui lissent les textures. Ces techniques lisent plusieurs texels et les mélangent pour fournir le texel final, celui envoyé aux unités de shader ou aux ROPs. La coutume est de le mesurer en sortie des unités de texture. Le nombre en entrée dépend grandement de la bande passante mémoire et du filtrage de texture utilisé, pas celui en sortie.
Le texture fillrate en sortie est le nombre maximal d'opérations de placage de texture par seconde. Là encore, on peut l'estimer en multipliant le nombre d'unités de texture par leur fréquence. Il s'agit évidemment d'une approximation assez peu fiable, car les unités de texture peuvent mettre plusieurs cycles pour plaquer une texture, les filtrer, etc.
Le texture fillrate est bien plus important que le pixel fillrate, surtout pour les GPU modernes. Un point important est que le texture fillrate a longtemps été égal au pixel fillrate. C'était le cas avant la Geforce 2 de NVIDIA. Les cartes graphiques avaient autant d'unités de texture que de ROP, et les deux fonctionnaient à la même fréquence. Les deux ont commencés à diverger quand le multi-texturing est arrivé, avec la Geforce 2, justement. Le nombre d'unités de texture a doublé comparé aux ROPs, ce qui fait que le texture fillrate est rapidement devenu le double du pixel fillrate. Sur les GPU modernes, le texture fillrate est le triple, quadruple, voire octuple du pixel fillrate.
La performance de l'unité géométrique
[modifier | modifier le wikicode]Pour l'unité géométrique, l'équivalent au fillrate est le polygon throughput. C'est nombre de sommets que l'unité géométrique peut traiter par seconde, exprimé en méga-sommets par secondes, en millions de sommets par seconde. Il dépend de la fréquence et du nombre d'unités géométriques, mais n'est pas exactement le produit des deux. Il varie beaucoup d'une carte graphique à l'autre, mais une approximation souvent utilisée prend le quart du produit fréquence * nombre d'unités géométriques.
Il faut noter que cette mesure de performance a survécu à l'arrivée des shaders. Les GPU anciens, avant DirectX 10, avaient des processeurs séparés pour les vertex shaders et les pixel shaders. Mais les calculs géométriques restaient séparés des autres calculs, ils avaient des unités géométriques dédiées. Quand les processeurs de shaders dit unifiés sont arrivés, la séparation entre géométrie et autres calculs a cédé et cet indicateur a simplement disparu.
Les autres circuits
[modifier | modifier le wikicode]Pour les autres circuits, il n'y a malheureusement pas d'indicateur de performance clair et net comme peut l'être le fillrate. La raison à cela se comprend assez bien quand on regarde comment se calcule le fillrate. C'est juste le produit de la fréquence et d'un nombre d'unités, en l’occurrence des unités de texture ou des ROPs. Le produit signifie que ces unités travaillent en parallèle et qu'elles peuvent chacune traiter un pixel/texel indépendamment des autres. Par contre, sur les anciens GPUs de l'époque, le rastériseur et l'unité géométrique sont un seul et unique circuit. Le nombre d'unité est donc égal à 1, et il ne nous reste plus que la fréquence.