Les cartes graphiques/Le multi-GPU

Combiner plusieurs cartes graphiques dans un PC pour gagner en performances est la base des techniques dites de multi-GPU, tels le SLI et le Crossfire. Ces technologies sont surtout destinées aux jeux vidéo, même si les applications de réalité virtuelle, l'imagerie médicale haute précision ou les applications de conception par ordinateur peuvent en tirer profit. C'est ce genre de choses qui se cachent derrière les films d'animation ou les effets spéciaux créés par ordinateur : Pixar ou Disney ont vraiment besoin de rendre des images très complexes, avec beaucoup d'effets, ce qui demande la coopération de plusieurs cartes graphiques.

Illustration du multi-GPU où deux cartes graphiques communiquent via un lien indépendant du bus PCIExpress. On voit que le débit du lien entre les deux cartes graphique est ajouté au débit du bus PCIExpress.

Contrairement à ce qu'on pourrait penser, le multi-GPU n'est pas une technique récente. Pensez donc qu'en 1998, il était possible de combiner dans un même PC deux cartes graphiques Voodoo 2, de marque 3dfx (un ancien fabricant de cartes graphiques, aujourd'hui racheté par NVIDIA). Autre exemple : dans les années 2006, le fabricant de cartes graphiques S3 avait introduit cette technologie pour ses cartes graphiques Chrome.

Le multi-GPU peut se présenter sous plusieurs formes, la plus simple consistant à placer plusieurs GPU sur une même carte graphique. Mais il est aussi possible d'utiliser plusieurs cartes graphiques séparées, connectées à la carte mère via PCI-Express. Si les deux cartes ont besoin d’échanger des informations, les transferts passent par le bus PCI-Express ou par un connecteur qui relie les deux cartes (ce qui est souvent plus rapide). Il n'y a pas de différences de performances avec la solution utilisant des cartes séparées reliées avec un connecteur. Tout le problème des solutions multi-GPU est de répartir les calculs sur plusieurs cartes graphiques, ce qui est loin d'être chose facile. Il existe diverses techniques, chacune avec ses avantages et ses inconvénients, que nous allons aborder de suite.

Le Split Frame Rendering modifier

Le Split Frame Rendering découpe l'image en morceaux, qui sont répartis sur des cartes graphiques différentes. Ce principe a été décliné en plusieurs versions, et nous allons les passer en revue. Nous pouvons commencer par faire la différence entre les méthodes de distribution statiques et dynamiques. Avec les méthodes statiques, la manière de découper l'image est toujours la même : celle-ci sera découpée en blocs, en lignes, en colonnes, etc; de la même façon quel que soit l'image. Avec les techniques dynamiques, le découpage s'adapte en fonction de la complexité de l'image. Nous allons commencer par aborder les méthodes statiques.

Le Scanline interleave modifier

Historiquement, la première technique multi-GPU inventée s'appelait le Scan Line Interleave et elle fût utilisée par les cartes graphiques Voodoo 2. Avec cette technique, chaque carte graphique calculait une ligne sur deux, la première carte rendait les lignes paires et l'autre les lignes impaires. Notons que cette technique ressemble à la technique de l'entrelacement vue dans le tout premier chapitre. Cependant, on peut adapter la technique à un nombre arbitraire de GPU, en faisant calculer par chaque GPU une ligne sur 3, 4, 5, etc.

 
Scanline interleave

Cette technique avait un avantage certain quand la résolution des images était limitée par la quantité de mémoire vidéo, ce qui était le cas de la Voodoo 2, qui ne pouvait pas dépasser une résolution de 800 * 600. Avec le scan line interleave, les deux framebuffers des deux cartes étaient combinés en un seul framebuffer plus gros, capable de supporter des résolutions plus élevées. Cette technique a toutefois un gros défaut : l’utilisation de la mémoire vidéo n'est pas optimale. Comme vous le savez, la mémoire vidéo sert à stocker les objets géométriques de la scène à rendre, les textures, et d'autres choses encore. Avec le scan line interleave, chaque objet et texture est présent dans la mémoire vidéo de chaque carte graphique. Il faut dire que ces objets et textures sont assez grands : la carte graphique devant rendre une ligne sur deux, il est très rare qu'un objet doive être rendu totalement par une des cartes et pas l'autre. Avec d'autres techniques, cette consommation de mémoire peut être mieux gérée.

Le Checker board modifier

La technique du Checker Board découpe l'image non en lignes, mais en carrés de plusieurs pixels. Dans le cas le plus simple, les carrés ont une taille fixe, de 16 pixels de largeur par exemple. Si les carrés sont suffisamment gros, il arrive qu'ils puissent contenir totalement un objet géométrique. Dans ces conditions, une seule carte graphique devra calculer cet objet géométrique et charger ses données, qui ne seront donc pas dupliquées dans les deux cartes. Le gain en terme de mémoire peut être appréciable si les blocs sont suffisamment gros. Mais il arrive souvent qu'un objet soit à la frontière entre deux blocs : il doit donc être rendu par les deux cartes, et sera stocké dans les deux mémoires vidéos.

Pour plus d'efficacité, on peut passer d'un découpage statique, où tous les carrés ont la même taille, à un découpage dynamique, dans lequel on découpe l'image en rectangles dont la longueur et la largeur varient. En faisant varier le mieux possible la taille et la longueur de ces rectangles, on peut faire en sorte qu'un maximum de rectangles contiennent totalement un objet géométrique. Le gain en terme de mémoire et de rendu peut être appréciable. Néanmoins, découper des blocs dynamiquement est très complexe, et le faire efficacement est un casse-tête pour les développeurs de drivers.

Le Screen spiting modifier

Il est aussi possible de simplement couper l'image en deux : la partie haute de l'image ira sur un GPU, et la partie basse sur l'autre. Cette technique peut être adaptée avec plusieurs GPU, en découpant l'image en autant de parties qu'il y a de GPU. Vu que de nombreux objets n'apparaissent que dans une portion de l'image, le drivers peut ainsi répartir les données de l'objet pour éviter toute duplication entre cartes graphiques. Cela demande du travail au driver, mais cela en vaut la peine, le gain en terme de mémoire étant appréciable.

 
Screen spliting

Le découpage de l'image peut reposer sur une technique statique : la moitié haute de l'image pour le premier GPU, et le bas pour l'autre. Ceci dit, quelques complications peuvent survenir dans certains jeux, les FPS notamment, où le bas de l'image est plus chargé que le haut. C'est en effet dans le bas de l'image qu'on trouve un sol, des murs, les ennemis, ou d'autres objets géométriques complexes texturés, alors que le haut représente le ciel ou un plafond, assez simple géométriquement et aux textures simples. Ainsi, le rendu de la partie haute sera plus rapide que celui du bas, et une des cartes 3D finira par attendre l'autre.

Mieux répartir les calculs devient alors nécessaire. Pour cela, on peut choisir un découpage statique adapté, dans lequel la partie haute envoyée au premier GPU est plus grande que la partie basse. Cela peut aussi être fait dynamiquement : le découpage de l'image est alors choisi à l’exécution, et la balance entre partie haute et basse s'adapte aux circonstances. Comme cela, si vous voulez tirer une roquette sur une ennemi qui vient de prendre un jumper (vous ne jouez pas à UT ou Quake ?), vous ne subirez pas un gros coup de lag parce que le découpage statique était inadapté. Dans ce cas, c'est le driver qui gère ce découpage : il dispose d'algorithmes plus ou moins complexes capables de déterminer assez précisément comment découper l'image au mieux. Mais il va de soit que ces algorithmes ne sont pas parfaits.

L'Alternate Frame Rendering modifier

L'alternate Frame Rendering consiste à répartir des images complètes sur les différents GPUs. Dans sa forme la plus simple, un GPU calcule une image, et l'autre GPU calcule la suivante en parallèle. Les problèmes liés à la répartition des calculs entre cartes graphiques disparaissent alors. Cette technique est supportée par la majorité des cartes graphiques actuelles. Cette technique a été inventé par ATI, sur ses cartes graphiques Rage Fury, afin de faire concurrence à la Geforce 256. Évidemment, on retrouve un vieux problème présent dans certaines des techniques vues avant : chaque objet géométrique devra être présent dans la mémoire vidéo de chaque carte graphique, vu qu'elle devra l'afficher à l'écran. Il est donc impossible de répartir les différents objets dans les mémoires des cartes graphiques. Mais d'autres problèmes peuvent survenir.

Un des défauts de cette approche est le micro-stuttering. Dans des situations où le processeur est peu puissant, les temps entre deux images peuvent se mettre à varier très fortement, et d'une manière beaucoup moins imprévisible. Le nombre d'images par seconde se met à varier rapidement sur de petites périodes de temps. Alors certes, on ne parle que de quelques millisecondes, mais cela se voit à l’œil nu. Cela cause une impression de micro-saccades, que notre cerveau peut percevoir consciemment, même si le temps entre deux images est très faible. Suivant les joueurs, des différences de 10 à 20 millisecondes peuvent rendre une partie de jeu injouable. Pour diminuer l'ampleur de ce phénomène, les cartes graphiques récentes incorporent des circuits pour limiter la casse. Ceux-ci se basent sur un principe simple : pour égaliser le temps entre deux images, et éviter les variations, le mieux est d’empêcher des images de s'afficher trop tôt. Si une image a été calculée en très peu de temps, on retarde son affichage durant un moment. Le temps d'attente idéal est alors calculé en fonction de la moyenne du framerate mesuré précédemment.

Ensuite, il arrive que deux images soient dépendantes les unes des autres : les informations nées lors du calcul d'une image peuvent devoir être réutilisées dans le calcul des images suivantes. Cela arrive quand des données géométriques traitées par la carte graphique sont enregistrées dans des textures (dans les Streams Out Buffers pour être précis), dans l'utilisation de fonctionnalités de DirectX ou d'Open GL qu'on appelle le Render To Texture, ainsi que dans quelques autres situations. Évidemment, avec l'AFR, cela pose quelques problèmes : les deux cartes doivent synchroniser leurs calculs pour éviter que l'image suivante rate des informations utiles, et soit affichée n'importe comment. Sans compter qu'en plus, les données doivent être transférées dans la mémoire du GPU qui calcule l'image suivante.