Emporte Une Vache !!!

lundi, octobre 17, 2005

Shaders, Terre Promise...

Je travaille aujourd'hui en permanence pour essayer de tirer un maximum de points de ma carte vidéo ces derniers temps, et je tombe en admiration profonde devant les capacités des cartes vidéo.

Petite Différence :

J'ai fait une comparaison entre les capacités de la NVidia Geforce 6600GT du travail (en PCI-Express s'il vous plaît) et mon ATI Radeon 9600 (remerciez ATI pour ne pas avoir mis de chiffres sur leur spec, j'ai du fouiller...), AGP8X (Rajoutez vous-même les ©™®, merci)


CarteRadeon 9600Geforce 6600GT
Pixel Fillrate1.3 Gpixels4.0 Gpixels
Geometry Rate325 MTriangles375 MTriangles


Loin de leur capacité à coûter cher, je me suis intéressé aux statistiques et benchs de ces 2 cartes distantes d’une génération seulement, et je vois qu’en matière de pixel/fragment shader, la NVidia (plus récente) est 2 fois plus "puissante" que l’ATI (ainsi que la Geforce FX 5900). Bien sûr, ça a l’air ridicule comme augmentation, on se dit que les CPU doivent augmenter en puissance bien plus vite.
Que nenni. Les cartes 3D voient leur puissance multipliée par 2 tous les 9 mois, soit comme j’ai lu dans un article, la loi de Moore au cube, alors que les fondeurs de processeurs x86 rament pour suivre cette dernière loi, et sont quasiment obligés d'ajouter des coeurs =o)…

A ajouter à cela qu’il ne s’agit pas forcément d’une course à la fréquence, les chips tournant au maximum autour de 500MHz, avec une mémoire très très rapide (NVRAM ou autre GDDR2 ?), mais plutôt d’une conception architecturale complètement différente.

Les processeurs vidéo exécutent majoritairement des instructions SIMD. SIMD pour Single Instruction on Multiple Data. Une instruction peut-être exécutée en même temps sur plusieurs données en parallèle. On dit aussi qu’il s’agit d’une architecture vectorisée. En fait pour donner une image, les optimisations consistent à réfléchir de cette façon : « une multplication de 2 variables float est aussi rapide qu’une multiplication de 2 matrices(4x4) ».
Ainsi 1 multiplication est effectuée aussi vite que 16 multiplications ! (Après il s’agit sûrement d’un peu de marketing-talk, mais c’est assez souvent repris par les experts).


Virtuellement, l’architecture de shading nous incite à penser que chaque vertex ou fragment dispose d’un processeur, et que tous ces processeurs tournent en parallèle.

En réalité ces processeurs sont capables d’exécuter plusieurs shaders « en même temps » sur différentes données, parce qu’elles sont restreintes, que l’environnement d’exécution est restreint, et qu’on a très peu de dépendances par rapport à un traitement généraliste.

C’est l’avantage et l’inconvénient du shader : on a très peu de notion de l’environnement extérieur.

Le vertex processor

On prend en entrée du vertex processor un vertex, sur lequel on applique un traitement identique à tous ceux qui sont dans le même appel de dessin. Bien sûr on peut avoir des paramètres pour chaque vertex et donc appeler à chaque vertex dessiné la fonction de définition d’attribut, ce qui en général, rend l’application très utilisatrice d’appel de fonctions OpenGL, et donc surchargée d’appels API… On appelle ça API bounded ou API limited dans la langue de John Carmack.

On peut aussi grouper les définitions de paramètres et ainsi définir un paramètre pour un gros nombre de vertices, comme par exemple une couleur, une position de départ, une direction, des coordonnées de texture…


Il peut arriver qu’on ait besoin de définir un tableau d’attributs qui peut être mis en correspondance avec un tableau de vertices. On trouve ici une utilisation pratique supplémentaire des tableaux de vertices -Vertex Array - et aujourd’hui des Vertex Buffer Objects. On peut se servir de ces tableaux pour stocker des vertices (comme l’indique leur nom), mais aussi pour stocker des tableaux d’attributs. Je ferais sûrement un post sur les Vertex Buffer Objects).

Dans le vertex processor, on peut agir sur la géométrie d’un vertex, sur sa couleur (ses couleurs avant et arrière), sur son illumination… On peut effectuer toutes les computations qu’on souhaite en gardant bien à l’esprit que l’on est dans un architecture où tous les vertices sont « censés » être traités en même temps, et donc qu’on ne peut savoir où se trouve le vertex voisin ou le vertex N. On n’a qu’un notion « atomique » du vertex.


Le fragment processor

La sortie du vertex processor est généralement envoyée au fragment processor. Entre les 2 processeurs, on a une étape dite de « rastérisation ». Il s’agit en fait d’interpoler les paramètres « varying » entre les différents vertex dont on demande le dessin. Un paramètre varying est défini dans le vertex processor et est passé au fragment processor, mais interpolé suivant la matrice de projection en place.

Si on dessine d’un triangle, on va donner trois vertices, avec chacun un attribut de couleur, et qu’on fait passer ces couleurs en varying dans le fragment processor, on aura des couleurs interpolées sur tous les pixels (autrement dit tous les fragments) que forment la surface de dessin du triangle.

Bien sûr le fragment shader permet d’effectuer des traitements bien plus lourds que le vertex processor. Il est en général beaucoup plus puissant en calcul, peut « regarder » des textures (ce que le vertex processor ne peut faire que depuis très peu de temps) et effectuer des calculs pour changer la couleur d’un pixel dessiné. C’est ainsi que des graphistes parviennent à créer des textures dites procédurales. Ils recréent avec un fragment shader des objets qu’on pourrait avoir en texture autrement. L’exemple de base du fragment shader est la « brique », où l’on recrée un « pattern » simple de brique dans un shader, c’est-à-dire qu’on dessine certaines lignes en blanc et le reste en rouge de façon périodique.

En général, on fait effectuer au fragment processor ce que le vertex processor ne peut pas faire. On peut modifier les couleurs dans le vp (vertex processor), mais on n’aura pas le niveau de détail et de finesse du fp (fragment processor).

Si vous avez compris quelque chose, et que vous vous demandez si on est au sommet des fonctionnalités et de ce qu’on voudrait faire avec la carte vidéo, la réponse est non. Au delà de ces merveilleuses fonctionnalités, on se rend compte que les shaders deviennent les briques de base de toute une classe d’applications. Et paradoxalement, les utilisations les plus intéressantes des cartes graphiques ne sont plus les jeux vidéo mais celles qui n’ont strictement rien à voir. Toutes ces nouvelles applications poussent les cartes vidéo au maximum, montrent leurs limitations, et font miroiter qu’en investissant dans un tel pari, on peut doubler les performances de ses applications de calcul lourd tous les 9 mois =o).

Enthousiasmant

Ainsi, en flânant sur gpgpu.org , on peut trouver une certain nombre d’articles assez techniques pour, entre autres, implémenter un moteur de base de données (désolé c’est au format PowerPoint) sur la carte vidéo en utilisant à la fois les shaders mais aussi les fonctions 3d habituelles à des fins totalement différentes. C’est absolument fascinant.

On trouvera aussi des travaux de recherche sur des algorithmes de tri ultraperformants, rendus encore plus rapides par les capacités de traitement et l’architecture des shaders.

Et si on fouille un peu, on trouve de la documentation sur les limitations des shaders et les nouvelles architectures mises en place pour y pallier, dont je parlerai dans un prochain post.

Conclusion : déjà 2 ans ?

Cela fait plus de deux ans que toutes ces merveilles sont disponibles dans des cartes vidéo grand public. Et je découvre seulement aujourd'hui toute la puissance de ce monde =o). Et encore quand je dis 2 ans, c’est de cette époque que datent les headers OpenGL que j’utilisais récemment encore pour développer mon appli. J’imagine le progrès depuis =o).