< Les cartes graphiques

Avec l'arrivée des interfaces graphiques (celles des systèmes d'exploitation, notamment) et des jeux vidéo 2D, les cartes graphiques ont pu s'adapter. Les cartes graphiques 2D ont d'abord commencé par accélérer le tracé et coloriage de figures géométriques simples, tels les lignes, segments, cercles et ellipses, afin d’accélérer les premières interfaces graphiques. Par la suite, diverses techniques d'accélération de rendu 2D se sont fait jour.

Ces techniques ne sont pas utiles que pour les jeux vidéo, mais peuvent aussi servir à accélérer le rendu d'une interface graphique. Après tout, les lettres, les fenêtres d'une application ou le curseur de souris sont techniquement du rendu 2D. C'est ainsi que les cartes graphiques actuelles supportent des techniques d’accélération du rendu des polices d'écriture, une accélération du scrolling ou encore un support matériel du curseur de la souris, toutes dérivées des techniques d'accélération de rendu 2D.

La base d'un rendu en 2D est de superposer des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des sprites. Le rendu des sprites doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les sprites qui se superposent sur les autres) : on parle d'algorithme du peintre.

Exemple de rendu 2D utilisant l'algorithme du peintre.

Il existe plusieurs techniques d'accélération graphique pour le rendu en 2D :

  • L'accélération des copies dans la mémoire vidéo grâce à un circuit dédié. Elle aide à implémenter de manière rapide le scrolling ou les sprites, ou encore le tracé de certaines figures géométriques. Mais elle est moins performantes que les trois suivante, bien qu'elle lui soit complémentaire.
  • L'accélération matérielle des sprites, où les sprites sont stockés dans des registres dédiés et où la carte graphique les gère séparément de l'arrière-plan.
  • L'accélération matérielle du scrolling, une opération très couteuse.
  • L'accélération matérielle du tracé de lignes/segments/figures géométriques simples.

Les quatre techniques ne sont pas exclusives, mais complémentaires. Par exemple, les consoles 8 bits d'Atari disposaient de deux circuits séparés appelés respectivement ANTIC et CTIA (Color Television Interface Adaptor). L'ANTIC gérait les copies en mémoire et correspondait à la première forme d'accélération, alors que le CTIA gérait l'accélération matérielle des sprites et donc la seconde optimisation. Le CTIA gérait aussi la palette indicée.

Le circuit de Blitter : les copies en mémoire

Les cartes 2D ont introduit un circuit pour accélérer les copies d'images en mémoire, appelé le blitter. Les copies de données en mémoire vidéo sont nécessaires pour ajouter les sprites sur l'arrière-plan, mais aussi lorsqu'on doit scroller, ou qu'un objet 2D se déplace sur l'écran. Déplacer une fenêtre sur votre bureau est un bon exemple : le contenu de ce qui était présent sur l'écran doit être déplacé vers le haut ou vers le bas. Dans la mémoire vidéo, cela correspond à une copie des pixels correspondant de leur ancienne position vers la nouvelle.

Sans blitter, les copies étaient donc à la charge du processeur, qui devait déplacer lui-même les données en mémoire. Le blitter est conçu pour ce genre de tâches, sauf qu'il n'utilise pas le processeur.

Pour ceux qui ont quelques connaissances avancées en architecture des ordinateurs, on peut le voir comme une sorte de contrôleur DMA amélioré, avec une gestion d'opérations annexes autres que la copie. D'ailleurs, il est souvent combiné à un contrôleur DMA, voire fusionné avec lui.

La superposition des sprites sur l'arrière-plan

Les cartes 2D sans sprites matériels effectuent leur rendu en deux étapes : elles copient l'image d'arrière-plan dans le framebuffer, puis chaque sprite est copié à la bonne palce en mémoire pour le placer au bon endroit sur l'écran. Les copies de sprites sont généralement prises en charge par le blitter, qui est spécialement optimisé pour cela. L'optimisation principale est le fait que le blitter peut effectuer une opération bit à bit entre les données à copier et une donnée fournie par le programmeur appelée un masque.

Pour voir à quoi cela peut servir, rappelons que les sprites sont souvent des images rectangulaires sans transparence ! Le sans transparence est très important pour ce qui va suivre. Idéalement, les sprites devraient contenir des zones transparentes pour laisser la place à l'arrière-plan, mais le hardware ne gère pas forcément des pixels transparents à l'intérieur des sprites. Emuler la transparence peut s'émuler facilement en utilisant un masque, une donnée qui indique quelles partie de l'image sont censées être transparentes.

Par exemple, l'image correspondant à notre bon vieux pacman ressemblerait à celle ci-dessous, à gauche. Les parties noires de l'image du pacman sont censées être transparentes et ne pas être copiées au-dessus de l'arrière-plan, il ne faut le faire que pour les pixels jaunes. Le masque qui indique quels pixels sont à copier ou non, est celui situé à droite.

Image de Pacman.
Masque de Pacman.

Le blitter prend l'image du pacman, le morceau de l’arrière-plan auquel on superpose pacman, et le masque. Pour chaque pixel, il effectue l'opération suivante : ((arrière-plan) AND (masque)) OR (image de pacman). L'application d'un ET logique entre masque et arrière-plan met à zéro les pixels modifiés par le sprite et uniquement ceux-ci, ils sont mis à la couleur noire. Le OU copie le sprite dans le vide laissé. La première étape est nécessaire, car un OU avec un arrière-plan non-noir donnerait un mélange bâtard entre arrière-plan et sprite. Au final, l'image finale est bel et bien celle qu'on attend.

Sprite rendering by binary image mask

L'accélération du tracé de lignes/polygones

Une autre utilisation du blitter est l'accélération du tracé de ligne ou le remplissage de formes géométriques simples. Un blitter peut par exemple remplir un carré à l'écran avec une couleur précise, tant qu'il connait les coordonnées des segments du carré. Il peut faire la même chose avec un rectangle, parfois un triangle ou quelques formes géométriques polygonales simples. Pour cela, la couleur est placée dans un registre du blitter spécialement dédié à cette tâche.

Un exemple de blitter : le circuit Agnus des anciens micro-ordinateurs Amiga

Amiga Original Chipset diagram

Pour finir ce chapitre, nous allons voir le circuit de Blitter des anciens ordinateurs de marque Amiga, qui servaient aussi de console de jeu. Ces consoles disposaient d'un processeur Motorola MC 68000, couplé à une mémoire RAM principale, et d'un chipset qui gère tout ce qui a trait aux entrée-sorties (vidéo, sonore, interruptions, clavier et souris, lecteur de disquette). Il contient plusieurs circuits appelés respectivement Paula, Denise et Agnus.

  • Agnus gère la communication avec la mémoire vidéo et contient notamment un blitter et un co-processeur qui commande le blitter.
  • Denise est une carte graphique de rendu 2D qui gère 8 sprites matériels. Il s'occupe aussi de la souris et du joystick.
  • Paula est un circuit multifonction qui fait à la fois carte son, controleur du lecteur de disquette, gestion des interruptions matérielles, du joystick analogique, du ports série et de toutes les autres entrées-sorties.

Anus contient un blitter, un CRTC, un circuit de rafraichissement mémoire, et même un contrôleur mémoire de DRAM entier. Le fait qu'il intègre un CRTC fait qu'il gére aussi la génération des timings vidéos pour l'écran, le signal d'horloge du chipset, et quelques autres signaux temporles. Enfin, il contient un co-processeur spécialisé.

Il gère l'arbitrage de la mémoire, à savoir qu'il empêche le processeur et les circuits vidéo/audio de se marcher sur les pieds. Il privilégie les accès provenant du CRTC aux accès initié par le blitter, l'affichage à l'écran ayant la priorité sur tout le reste. Il se charge aussi de répartir les accès mémoire entre le processeur, le CRTC et le blitter intégrés. Agnus alloue un cycle sur deux au blitter, le reste est alloué suivant les besoins, soit au CPU, soit au blitter.

Le blitter gère nativement des blocs en forme de trapèzes ou de rectangles. Ils sont dessinés en fournissant plusieurs informations : une largeur qui est un multiple de 16 bits, une hauteur mesurée en nombre de lignes, un décalage qui indique de combien de pixels sont décalées deux lignes consécutives, la position de leur premier pixel. Le blitter prend en entrée 0, 1, 2 ou 3 blocs, et écrit le résultat d'une opération logique dans un quatrième bloc.

Il peut aussi dessiner/remplir des blocs d'une couleur uniforme. Pour cela, il parcourt le framebuffer ligne par ligne, pixel par pixel. Dès qu'croire un pixel appartenant au bloc à remplir/dessiner, il écrit une couleur dedans. Dès qu'il tombe sur un pixel qui n'est plus dans le bloc, il cesse de dessiner la couleur pour le reste de la ligne. Et il fait ainsi de suite pour chaque ligne. Il peut aussi tracer des lignes en utilisant une implémentation matérielle de l'algorithme de tracé de ligne de Bresenham.

Les sprites matériels

Il existe des cartes 2D sur lesquelles les sprites sont gérés directement en matériel, sans passer par un blitter, sans être copiés sur un arrière-plan préexistant. À la place, l'image est rendue pixel par pixel, à la volée. La carte graphique décide, à chaque envoi de pixel, s'il faut afficher les pixels de l’arrière-plan ou du sprite pendant l'envoi des pixels à l'écran, lors du balayage effectué par le CRTC.

De telles cartes graphiques peuvent s'implémenter avec ou sans framebuffer. Il est possible de se débrouiller sans framebuffer, comme l'on fait une partie des consoles de 2ème et 3ème génération. Mais certaines consoles comme l'Amiga disposaient à la fois d'un framebuffer et de sprites matériels. De telles architectures incorporent souvent un blitter amélioré, qui se voit ajouter des fonctions liées à la mémoire vidéo : contrôleur DMA, gestion du rafraichissement mémoire des DRAM, etc.

Les circuits d'accélération matérielle des sprites

Sur ces cartes 2D, les sprites et l'image d'arrière-plan sont stockés dans des registres ou des RAM. La RAM pour l'arrière-plan est généralement assez grosse, car l'arrière-plan a la même résolution que l'écran. Il arrive que l'arrière-plan ait une résolution supérieure à celle de l'écran, ce qui est utile pour le scrolling. Par exemple, si je prends la console de jeux NES, elle a une résolution de base de 256 par 240, alors que l'arrière-plan a uje résolution de 512 par 480. Pour les sprites, la mémoire est généralement très petite, ce qui fait que les sprites ont une taille limitée.

Pour chaque sprite, on trouve deux registres permettant de mémoriser la position du sprite à l'écran : un pour sa coordonnée X, un autre pour sa coordonnée Y. Lorsque le CRTC demande à afficher le pixel à la position (X , Y), chaque triplet de registres de position est comparé à la position X,Y fournie par le CRTC. Si aucun sprite ne correspond, les mémoires des sprites sont déconnectées du bus et le pixel affiché est celui de l'arrière-plan. Dans le cas contraire, la RAM du sprite est connectée sur le bus et son contenu est envoyé au RAMDAC.

Sprites matériels.

Les cartes 2D les plus simples ne géraient que deux niveaux : soit l'arrière-plan, soit un sprite devant l'arrière-plan. Il n'est donc pas possible que deux sprites se superposent, partiellement ou totalement. Dans ce cas, l'image n'a que deux niveaux de profondeur. C'était le cas sur les consoles de seconde et troisième génération, comme la NES ou la Sega Saturn. Par la suite, une gestion des sprite superposés est apparue. Pour cela, il fallait stocker la profondeur de chaque sprite, pour savoir celui qui est superposé au-dessus de tous les autres. Cela demandait d'ajouter un registre pour chaque sprite, qui mémorisait la profondeur. le circuit devait donc être modifié de manière à gérer la profondeur, en gérant la priorité des sprites.

D'autres consoles ont ajouté une gestion de la transparence. Dans le cas le plus simple, la transparence permet de ne pas afficher certaines portions d'un sprite. Certains pixels d'un sprite sont marqués comme transparents, et à ces endroits, c'est l'arrière-plan qui doit s'afficher. Cela permet d'afficher des personnages ou objets complexes alors que l'image du sprite est rectangulaire. Le choix du pixel à afficher dépend alors non seulement de la profondeur, mais aussi de la transparence des pixels des sprite. Le pixel d'un sprite a la priorité sur le reste s'il est opaque et que sa profondeur est la plus faible comparé aux autres. Cette gestion basique de la transparence ne permet pas de gérer des effets trop complexe, où on mélange la couleur du sprite avec celle de l'arrière-plan.

Le stockage des sprites et de l’arrière-plan : les tiles

Les sprites et l'arrière-plan sont donc stockés chacun dans une mémoire dédiée. En théorie, les sprites et l'arrière plan sont des images tout ce qu'il y a de plus normales, et elles devraient être codées telles quelles. Mais cela demanderait d'utiliser des mémoires assez grosses. Notamment, l'arrière-plan a la même taille que l'écran : un écran d'une résolution de 640 pixels par 480 demandera d'utiliser un arrière-plan de 640 pixels par 480. La mémoire pour stocker l'arrière-plan devrait donc être assez grosse pour stocker toute l'image affichée sur l'écran et pourrait servir de framebuffer. Le problème est moins critique pour les sprite, mais il se pose pour les plus gros sprite, qui demandent des mémoires assez importantes pour être stockés.

Pour résoudre ces problèmes, diverses techniques sont utilisées pour réduire la taille des sprites et de l'arrière-plan.

  • Premièrement, les cartes 2D utilisent la technique de la palette indicée, afin de réduire le nombre de bits nécessaires pour coder un pixel.
  • Deuxièmement, on force une certaine redondance à l'intérieur de l'arrière-plan et des sprites.
Tile set de Ultima VI.

La seconde idée est que les sprites et l'arrière-plan sont fabriqués à partir de tiles, des carrés de 8, 16, 32 pixels de côtés, qui sont assemblés pour fabriquer un sprite. L'ensemble des tiles est mémorisée dans un fichier unique, appelé le tile set, aussi appelé le tilemap. Ci-contre, vous voyez le tile set du jeu Ultima VI.

Le tile set est généralement placé dans la mémoire ROM de la cartouche de jeu. Les tiles sont numérotées et ce numéro indique sa place dans la mémoire de la cartouche. L'idée est l'image ne mémorise pas des pixels, mais des numéros de tiles. Le gain est assez appréciable : pour une tile de 8 pixels de côté, au lieu de stocker X * Y pixels, on stocke X/8 * Y/8 tiles. Ajoutons à cela que les numéros de tiles prennent moins de place que la couleur d'un pixel, et les gains sont encore meilleurs. La consommation mémoire est déportée de l'image vers la mémoire qui stocke les tiles, une mémoire ROM intégrée dans la cartouche de jeu.

Un autre avantage est que les tiles peuvent être utilisés en plusieurs endroits, ce qui garantit une certaine redondance. Par exemple, un sprite ou l'arrière-plan peuvent utiliser une même tile à plusieurs endroits, ce qui impose une certaine redondance. C'était très utile pour l'arrière-plan, qui est généralement l'image la plus redondante. On y trouve beaucoup de tiles bleues identiques pour le ciel, par exemple. Pareil si une tile est utilisée dans plusieurs sprites, elle n'est stockée qu'une seule fois. Une autre possibilité était de lire certaines tiles dans les deux sens à l'horizontale, de faire une sorte d'opération miroir. Ce faisant, on pouvait créer un objet symétrique en mémorisant seulement la moitié gauche de celui-ci, la moitié droite étant générée en faisant une opération miroir sur la partie gauche. Mais cette optimisation était assez rare, car elle demandait d'ajouter des circuits dans un environnement où le moindre transistor était cher. De plus, les objets symétriques sont généralement assez rares.

Le matériel des anciennes cartes 2D était optimisé pour gérer les tiles. Son rôle était de reconstituer l'image finale en plusieurs étapes. Le tile set était généralement mémorisé dans une mémoire à part, il était parfois dans la mémoire ROM de la cartouche, mais il était parfois recopié dans une mémoire RAM intégrée dans la carte graphique pour plus de rapidité. Les registres pour les sprites et la RAM de l'arrière-plan étaient remplis de numéros de tiles et non directement de pixels. Le matériel rendait les sprites en lisant la tile adéquate, automatiquement. D'abord les circuits d'adressage lisaient les mémoires pour l'arrière-plan et les sprites, et récupéraient un numéro de tile. La tile correspondant était lue depuis la tilemap, dans une autre mémoire. Enfin, divers circuits choisissaient le pixel adéquat dans la tile à chaque cycle d'horloge.

Carte 2D avec un rendu en tile

Pour les consoles de 2ème, 3ème et 4ème génération, l'usage de tiles était obligatoire et tous les jeux vidéo utilisaient cette technique. Les cartes graphiques des consoles de jeux de cette époque étaient conçues pour gérer les tiles.

L'accélération matérielle du curseur de souris

Le support des sprites est parfois utilisé dans un cadre particulièrement spécialisé : la prise en charge du curseur de la souris, ou le rendu de certaines polices d'écritures ! Le curseur de souris est alors traité comme un sprite spécialisé, surimposé au-dessus de tout le reste.

Les cartes graphiques modernes gèrent un ou plusieurs sprites, qui représentent chacun un curseur de souris, et deux registres, qui stockent les coordonnées x et y du curseur. Ainsi, pas besoin de redessiner l'image à envoyer à l'écran à chaque fois que l'on bouge la souris : il suffit de modifier le contenu des deux registres, et la carte graphique place le curseur sur l'écran automatiquement. Pour en avoir la preuve, testez une nouvelle machine sur laquelle les drivers ne sont pas installés, et bougez le curseur : lag garantit !

Cet article est issu de Wikibooks. Le texte est sous licence Creative Commons – Attribution – Partage à l’identique. Des conditions supplémentaires peuvent s’appliquer aux fichiers multimédias.