Les cartes graphiques/Les accélérateurs de scanline
Les chapitres précédents ont abordé des VDC couplés à un framebuffer, mais tous les VDC ne sont pas dans ce cas. La raison est assez simple : une image prend beaucoup de mémoire ! Par exemple, prenons le cas d'une image en niveaux de gris d'une résolution de 320 par 240 pixels, chaque pixel étant codé sur un octet. L'image prend alors 76800 octets, soit environ 76 kiloctets. Mine de rien, cela fait beaucoup de mémoire ! Les tout premiers ordinateurs n'avaient pas assez de mémoire vidéo, les cartes d'affichages devaient se débrouiller avec beaucoup de mémoire.
Pour optimiser l'utilisation de la mémoire, les cartes d'affichage n'avaient pas de framebuffer et fonctionnaient autrement. Il existe plusieurs optimisations pour économiser de la mémoire. La première optimisation est appelée le rendu en tiles, que nous verrons dans le prochain chapitre. Il s'agit techniquement d'une forme de compression d'image spécialisée, qui est assez reliée au mode texte des anciennes cartes graphiques. Nous verrons le rendu en tile et le rendu en mode texte dans un chapitre dédié.
Une autre optimisation rend l'image ligne par ligne. La mémoire vidéo ne contient que la ligne en cours de rendu, pas une image complète. D'ailleurs, la mémoire vidéo est souvent appelée le line buffer, tampon de ligne en français. Il n'y a pas de terminologie officielle pour désigner de tels VDC, mais nous pouvons les appeler des scanline accelerators, accélérateurs de scanline.
Quelques VDC anciens étaient de ce type. Par exemple, les premières consoles ATARI utilisaient des accélérateurs de scanline, de même que la carte graphique de la console néo-géo. Voyons-en quelques exemples, avec notamment les VDC des consoles ATARI. Nous verrons dans quelques sections qu'ils sont assez complexes, avec une gestion des sprites matériels, du défilement, et quelques autres techniques d'accélération 2D.
Généralités sur les accélérateurs de scanline
[modifier | modifier le wikicode]Avec un accélérateur de scanline, le processeur doit écrire la prochaine ligne à afficher dans le tampon de ligne, avec un timing assez serré. Il doit attendre que l'affichage de la ligne en cours soit terminé, mais doit finir son travail avant l'affichage de la prochaine ligne. Pour le dire autrement, le processeur met à jour la ligne à afficher pendant l'intervalle de horizontal blank. Il s'agit d'une technique appelée le racing the beam. Pour cela, le VDC émet une interruption matérielle à la fin de l’affichage de la ligne courante.
Les accélérateurs de scanline peuvent supporter les sprites matériels. Typiquement, ils supportent un certain nombre de sprite par ligne. Notez le "par ligne". En effet, le processeur peut en profiter pour changer les sprites entre l'affichage de deux lignes. L'intérêt est que cela permet d'afficher plus de sprites que ce que la console permet. Par exemple, si l’accélérateur de scanline supporte 8 sprites par ligne, il peut afficher plus de 8 sprite par image, en respectant la contrainte de 8 sprite par ligne. C'est juste qu'il y aura, par exemple, 8 sprites en haut de l'image, 8 au milieu, 8 en bas.
La display list
[modifier | modifier le wikicode]Les accélérateurs de scanline les plus complexes permettent de réduire grandement le nombre de raster interrupts en intégrant une sorte de contrôleur DMA programmable. Dans le détail, ce contrôleur DMA exécute un programme composé d'une suite d'instructions simples, chacune commande l'affichage d'une ligne à l'écran. Le programme exécuté est une série d'instructions appelée la display list, terme qui reviendra dans quelques chapitres.
La display list commence par un préambule de configuration, qui indique la résolution, le mode graphique, etc. Puis, elle afficher l'image à l'écran, ligne par ligne, sans intervention du processeur. La display list a une instruction par ligne à afficher, l'instruction copie la ligne à afficher de la mémoire RAM principale vers la mémoire vidéo (le tampon de ligne), pendant une période de blanking horizontal. La display list s'occupe de séquencer les lignes une par une, sans intervention du processeur. Le processeur n'a pas besoin de copier lui-même chaque ligne, la display list le fait automatiquement pour toutes les lignes.
Un avantage est que le défilement vertical est très rapide avec une display list. Il suffit de rajouter ou d'enlever des lignes dans la display list. Pour défiler d'une ligne vers le bas, on rajoute une ligne au début de la display list et on enlève la dernière. Et inversement pour défiler vers le haut. Le processeur peut le faire sans se synchroniser fortement avec l'accélérateur de scanline. Nous verrons dans quelques chapitres que ce mécanisme a été utilisé sur les ordinateurs Amstrad PCW, mais nous ne pouvons pas en parler pour le moment, vu que leur carte d'affichage est une carte en mode texte, qu'on n'a pas encore vu.

La display list fait que les raster interrupts sont inutiles, ou du moins grandement réduites. Les interruptions sont déclenchées pour certaines lignes seulement, celles où l'instruction qui affiche la ligne le précise. La gestion des interruptions est donc totalement programmable. La display list contient des instructions pour déclencher des raster interrupts, placées stratégiquement dans la display list par le programmeur. Par exemple, si le programmeur veut changer les sprites entre deux lignes, il peut déclencher une raster interrupts entre ces deux lignes, le processeur répondra à l'interruption en changeant les registres de sprites.
Le Television Interface Adaptor des consoles Atari 2600
[modifier | modifier le wikicode]Un exemple d'accélérateur de scanline est celui utilisé sur l'Atari Video Computer System et l'Atari 2600. La carte graphique en question s'appelait la Television Interface Adaptor, abrévié TIA. Il incorpore un tampon de ligne, qui est modifié entre l'envoi à l'écran de deux lignes. Par contre, il n'y a pas d'interruption pour prévenir le processeur qu'il doit envoyer la ligne suivante.
Les contraintes de conception
[modifier | modifier le wikicode]Le TIA a été conçu avec de nombreuses contraintes en tête, qui ne se comprennent qu'en remettant la console dans son contexte historique. L'époque est celle des consoles de première génération, des consoles qui étaient conçues pour un jeu bien précis. Pas de cartouche, de CD ou quoique ce soit d'autre. Le jeu était intégré directement dans les circuits de la console, qui n'était pas programmable. Il y avait une console pour le jeu Pong, une autre pour le jeu Tank, etc. Les jeux étaient alors très limités niveau gameplay.

Les premières consoles ATARI étaient les premières consoles de seconde génération, conçues pour faire fonctionner plusieurs jeux, conçues pour être programmables. Elles étaient conçues pour faire fonctionner Pong, Tank et des variantes de ces deux jeux. Pour rappel, s'il faut faire un rappel, Pong était un jeu de tennis à deux joueurs. Il se limitait à un arrière-plan fixe sur lequel chaque joueur contrôle une barre verticale, sur laquelle la balle peut rebondir.
Tank était une variante de PONG, où deux joueurs controlaient chaun un tank et se tiraient des missiles dessus avec l'intention de détruire le tank de l'adversaire. Son adaptation sur ATARI s'appelait Combat!, en voici un screenshot :

La console est donc conçue pour gérer un arrière-plan, deux sprites pour les joueurs, un pour la balle, deux pour les missiles. Chacun est mémorisé dans un registre dédié, qui mémorise la ligne du sprite/arrière-plan à afficher à l'écran. De plus, le TIA incorpore un système pour détecter les collisions, afin de détecter si la balle rebondit sur la raquette dans PONG, sur un missile touche un tank dans TANK. La conception du TIA est très simple, sans compter que diverses optimisations permettent d'encore plus simplifier le TIA. Par exemple, l'arrière-plan est censé être symétrique : la partie gauche est recopiée sur le côté droit de l'écran, avec un effet miroir. Optimisation possible car PONG et TANK sont censé avoir un terrain de jeu symétrique, identique pour chaque joueur.
Avec une architecture aussi simple, il n'est pas possible de faire de miracles. Les jeux sur ATARI devaient faire avec les limitations du TIA : arrière-plan symétrique, moins d'une dizaine de sprites simultanés à l'écran.
Quelques ruses de sioux ont cependant permis de dépasser ces limites techniques.
L'arrière-plan du TIA : un registre dédié de 20 bits
[modifier | modifier le wikicode]Vu que le rendu s'effectue ligne par ligne, l'arrière-plan et les sprites sont en réalité des lignes, pas des images complètes. Le registre d'arrière-plan est un registre de 20 bits. Un bit correspond à 4 pixels de l'écran, il indique la couleur de ces 4 pixels consécutifs. Peut-être avez-vous remarquez que 20 bits * 4 pixels = 80 pixels. Pourtant, le TIA gére une résolution horizontale par défaut de 160 pixels. Soit deux fois plus que ce qui est géré par le registre d'arrière-plan. La raison est que l'arrière-plan est symétrique. Le registre d'arrière-plan ne définit que la moitié gauche de l'écran, la moitié droite en est le reflet miroir.
Vous pourriez croire que la couleur codée dans ce registre est un simple noir et blanc, mais c'est plus compliqué. Le registre d'arrière-plan indique, pour chaque bloc de 4 pixels, si le pixel prend la couleur de l'arrière-plan ou la couleur de fond par défaut. Un registre séparé contient la couleur de l'arrière-plan, elle-même encodée grâce à un système de palette indicé. La couleur de fond est elle aussi configurable.
En théorie, l'arrière-plan est censé être symétrique, mais quelques astuces permettent d'afficher un arrière-plan asymétrique. L'idée est simplement de modifier le registre d'arrière-plan une fois que la moitié gauche a été affichée. Cela demande un timing très précis, mais c'est possible et plusieurs jeux Atari 2600 le faisaient. Certains arrivaient même à simuler un défilement horizontal !
Les sprites matériels du TIA
[modifier | modifier le wikicode]Le TIA gère 5 sprites matériels, grâce à 5 registres capables chacun de mémoriser une ligne de pixel. Ces registres sont spécialisés, avec deux registres pour les avatars des joueurs, deux missiles et une balle. Ne vous fiez pas aux noms, la différence entre les 5 est la taille des sprites stocké dedans. Les sprites des avatars sont de 8 pixels de large, peuvent être dupliquées ou étendus. La balle est un sprite de même couleur que l'arrière-plan, de 1, 2, 4 ou 8 pixels de large. Les deux missiles sont identiques à la balle, sauf qu'ils ont la même couleur que le sprite du joueur associé.
Un sprite matériel sur le TIA est codé sur un octet. Un sprite fait 8 pixels de large, chacun étant là-encore codé sur 1 bit. Il indique encore une fois si le pixel a la couleur de fond ou une couleur spécifique au sprite. Vu que cet octet écrit une ligne du sprite, il est changé à chaque ligne pour réellement afficher un sprite à deux dimensions. Vous remarquerez que le sprite est codé sur un octet, les informations de sa position X et Y à l'écran ne sont pas encodées. La raison est simple : un sprite est affiché quand le processeur écrit l'octet dedans.
L'affichage d'un sprite est donc commandé par le processeur et doit se faire au cycle d'horloge près. Le problème est que le processeur n'est pas précis au pixel près. Déjà, sa fréquence est égale à un tiers de la fréquence du TIA, ce qui fait qu'il est précis à 3 pixels près. De plus, le logiciel faisait l'écriture dans le registre de sprite est basé sur une boucle, dont les instructions prennent 5 cycles pour s'exécuter. Ce qui fait une imprécision de 15 cycles.
Pour compenser ce défaut, le TIA incorpore deux registres, un par avatar/player. Ils permettent de décaler le sprite de quelques pixels, la valeur de décalage étant précisée dans ce registre. La valeur est un entier allant de -7 à 8 cycles/pixels. L'affichage d'un sprite est donc en deux temps : on déclenche une écriture précise à 15 cycles près, puis on décale le sprite avec ces deux registres.
L'architecture du TIA
[modifier | modifier le wikicode]Mettre à jour les registres du TIA à chaque ligne n'est pas si différent de modifier une mémoire vidéo entre deux lignes. Mais un défaut des consoles ATARI est que le processeur n'utilisait pas d'interruptions pour gérer l'affichage des lignes. À la place, le bus indiquait au processeur quand le TIA acceptait qu'on modifie ses registres. Le bus avait un signal RDY (READY) qui indiquait si le TIA était occupé à afficher une ligne ou non.
Un autre défaut est que le TIA ne génère pas totalement les signaux de synchronisation verticale, qui servent à indiquer à l'écran qu'il a fini d'afficher une image. La raison est qu'il n'a pas de compteur de ligne ! Il y a bien un compteur pour les colonnes, pour générer les signaux de synchronisation horizontale, mais pas plus. C'est le processeur qui doit générer le signal de synchronisation verticale de lui-même ! Pour cela, le processeur doit écrire dans une adresse dédie associée au TIA, ce qui déclenche un signal de synchronisation verticale immédiat.
Si on devait faire un constat avec tout ce qui a été dit plus haut, il serait que le TIA est très simple, trop simple. Le processeur est très impliqué dans l'affichage à l'écran, notamment pour les sprites. La carte graphique est donc composé par l'association entre le TIA et le processeur proprement dit, bien que celui-ci soit le CPU. Ce qui explique l'absence de mécanismes de synchronisation entre TIA et CPU : c'est le CPU qui commande le TIA au cycle près.
Les VDC ANTIC et CTIA des consoles Atari post-2600
[modifier | modifier le wikicode]Les consoles Atari 8 bits, qui ont succédé à l'Atari 2600, n'utilisaient un VDC TIA amélioré appelé le CTIA pour sa première version (Color Television Interface Adaptor), le GTIA pour sa seconde version (Graphic Television Interface Adaptor). Les deux étaient un accélérateur de scanline qui intégraient l'accélération matérielle de sprites et une palette indicée. Comme pour le TIA, les sprites servaient pour les joueurs et des missiles, sauf que leur nombre est doublé pour passer à 4 chacun.
Mais la révolution était qu'ils étaient associé à un autre circuit appelé l'ANTIC, qui commandait le CTIA/GTIA sans intervention du processeur CPU. Là où l'Atari 2600 utilisait le processeur pour l'affichage des sprites et beaucoup d'autres fonctions, une bonne partie de ces traitements est déléguées à l'ANTIC sur les machines suivantes. L'ANTIC est un blitter amélioré, en charge des copies en mémoire. Mais surtout, il s'occupe du séquencement des lignes envoyées au CTIA. Ce n'est pas le processeur qui s'occupe d'envoyer les lignes une par une au CTIA, cette fonction est déportée sur l'ANTIC.
La display list
[modifier | modifier le wikicode]Le fonctionnement de l'ANTIC est assez complexe, mais on peut le voir comme une sorte de pseudo-processeur, qui exécute un programme de rendu 2D. Le programme en question est une suite d’opération de rendu 2d à exécuter dans l'ordre, qui permet de calculer l'image ligne par ligne. Il est appelé la Display List, et est stocké dans la mémoire de la console. L'ANTIC lit le programme instruction par instruction, grâce à un contrôleur DMA intégré dans l'ANTIC, qui contient un program counter interne à l'ANTIC. L'exécution d'une Display List sur l'ANTIC permet de décharger le processeur des tâches de rendu 2D, du moins dans une certaine mesure.
Une instruction de la display list permet de calculer une ligne de l'écran, en précisant comment combiner les sprites et l'arrière-plan, tous deux stockés dans la mémoire vidéo. La display list gére entre 0 et 240 instructions, ce qui limite la résolution verticale à 240 pixels. Les instructions peuvent se classer en quatre types principaux : des instructions pour écrire une ligne de pixels colorés, des instructions pour écrire une ligne de caractères, des instructions pour émettre les signaux de synchronisation de blanking, et enfin des branchements qui agissent sur le program counter de l'ANTIC. Les instructions pour afficher des pixels/caractères peuvent activer le défilement horizontal ou vertical suivant l'instruction.
Une instruction est codée sur 8 bits minimum.
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| DLI | LMS | VS | HS | Mode | |||
Les 4 bits de mode précisent quelle est l'instruction à utiliser, ce qui fait 16 instructions en tout, réparties dans les 4 classes mentionnées plus haut. Les bits HS et VS activent ou désactivent le défilement vertical et horizontal. Le bit LMS permet à l'instruction de préciser l'adresse où se trouvent les tableaux de données à utiliser, comme la place de l'arrière-plan en mémoire RAM, ce genre de choses. Pour cela, le bit est placé à 1 et l'adresse est dans les 2 octets qui suivent l'instruction. Le bit DLI gère les interruptions dites de Display List, ou interruptions DLI, que nous allons voir dans ce qui suit.
Les interruptions DLI sont l'équivalent sur l'ANTIC des interruptions d'horizontal blanking, déclenchées à la fin de l'affichage d'une ligne. Elles préviennent le processeur qu'une ligne entière a été affichée. Le processeur peut alors écrire en mémoire vidéo durant un court laps de temps s'il en a besoin. Il peut alors changer les sprites à la volée, ce qui permet d'afficher plus de sprites que ce que supporte le chip CTIA/GTIA. Les interruptions sont déclenchées pour certaines lignes seulement, celles où l'instruction qui affiche la ligne le précise. Si l'instruction a son bit DLI mis à 1, alors une interruption est déclenchée à la fin de la ligne. S'il est à 0, pas d'interruption. La gestion des interruptions DLI est donc totalement programmable.
Les registres de configuration de l'ANTIC
[modifier | modifier le wikicode]
Niveau registres, l'ANTIC contient un program counter et de quoi configurer le contrôleur DMA. Il a aussi des registres pour localiser les données importantes en mémoire, qui sont regroupées dans des tableaux séparés.
Pour ce qui est du rendu, il contient un registre pour l'adresse de l'arrière-plan, un autre pour l'adresse des sprites en mémoire RAM. Il contient aussi deux registres pour configurer le défilement horizontal et vertical.
Pour gérer la synchronisation avec le CPU, l'ANTIC incorpore un compteur de ligne, ainsi qu'un registre de synchronisation horizontale qui est à 1 quand une ligne est en train d'être affichée (mécanisme redondant avec les interruptions DLI). Il y a aussi plusieurs registres pour gérer les interruptions, avec un registre de statut que le CPU peut lire à loisir pour savoir quelle est la raison qui a déclenché l'interruption, ainsi qu'un registre de configuration des interruptions.
Le système graphique de la console néo-géo
[modifier | modifier le wikicode]La néo-géo est une console assez ancienne, de la période 16 bits, commercialisée en 1990. Elle avait pour caractéristique d'avoir le même hardware que les bornes d'arcade du même nom, les néo-géo MVS. Elle comprenait un processeur Motorola 68000, 64 kilooctets de mémoire RAM, un processeur sonore Zilog Z80, et un VDC avec 64 kilooctets de mémoire vidéo. Elle avait une palette de 65 536 couleurs, grâce à une palette indicée de 8 kilooctets.
L'avant-plan, l'arrière-plan et les sprites de la néo-géo
[modifier | modifier le wikicode]Un point important est que la mémoire vidéo n'est pas un framebuffer, le VDC utilise un tampon de ligne. La console a pour particularité de ne pas gérer d’arrière-plan, l'image affichée est intégralement composée de sprites. Les sprites sont tous combinés pour donner l'image finale.
Un autre détail est que les sprites matériels sur la néo-géo sont différents des sprites matériels des consoles précédentes. Ils sont composés de plusieurs motifs, de plusieurs tiles. Les motifs font tous des carrés de 16 pixels de côtés, contenant 15 couleurs (plus la couleur de transparence). Un sprite est un rectangle qui a un pixel de large, mais dont la hauteur va de 1 à 32 motifs, donc de 16 à 512 pixels. Chaque sprite indique quels sont ses motifs, quelle palette utiliser, s'il faut appliquer l'effet miroir, etc. Un sprite logiciel est composé en concaténant horizontalement plusieurs sprites de néo-géo.
A défaut d'un arrière-plan, la console gère un pseudo-arrière-plan composé d'une couleur unie. Pour faire la différence avec une image d’arrière-plan, on peut plutôt parler d'une couleur de fond. Les sprites sont empilés les uns sur les autres au-dessus de cet arrière-plan. Au-dessus des sprites, il y a un avant-plan spécialisé, qui est rendu au-dessus de tout le reste, qui est utilisé pour rendre le HUD. Cet avant-plan est appelé la fix layer dans la documentation de la console. Les motifs de l'avant-plan n'ont pas la même taille que pour les sprites : ce sont des carrés de 8 pixels de côtés, pas 16 !
Les motifs de l'avant-plan sont placés dans une mémoire ROM de celle pour les motifs des sprites. En effet, une cartouche de néo-géo contient plusieurs mémoires ROM :
- une pour les motifs des sprites ;
- une pour les motifs de l'avant-plan ;
- une pour le programme à exécuter sur le processeur M68000 principal ;
- une pour le programme à exécuter sur le processeur sonore Z80 ;
- une pour les échantillons audio, la musique et le son du jeu.
Le rendu d'une image
[modifier | modifier le wikicode]Lors du rendu d'une ligne, la console détermine quels sprites sont affichés sur cette ligne, et les sprites adéquats sont lus depuis la ROM de la cartouche. Les pixels de la ligne sont déterminés par le VDC et écrit dans le tampon de ligne. La console supporte au maximum 96 sprites par ligne, ce qui dépasse de très loin les capacités des autres consoles 8 ou 16 bits.
La console dispose de deux tampons de lignes, ce qui permet d'afficher une ligne pendant qu'on prépare la suivante. L'idée est la suivante. La première ligne est placée dans un tampon de ligne, elle est envoyée à l'écran pixel par pixel par le VDC. En même temps, le processeur écrit la prochaine dans le second tampon de ligne. Une fois la ligne totalement affichée, le VDC échange les deux tampons de ligne. La ligne suivante est déjà prête dans le second tampon de ligne, elle est envoyée à l'écran pixel par pixel, le premier tampon de ligne est utilisé pour précalculer la ligne suivante, et ainsi de suite.
La gestion de la profondeur des sprites est assez simple : les sprites sont placés dans la mémoire vidéo selon leur profondeur. En clair, les sprites situés au fond de l'image sont placés au début de la mémoire vidéo, ceux situés à l'avant-plan sont placés à la fin de la mémoire vidéo. Pas besoin po_ur le VDC de gérer les priorités et la profondeur des sprites, tout est déjà réglé une fois les sprites chargés dans la mémoire vidéo.
Le console gère une mise à l'échelle des sprites matériels, mais ceux-ci ne peuvent qu'être réduits, pas zoomés. La mise à l'échelle est précise au pixel près. La mise à l'échelle peut se faire à l'horizontale et à la verticale, les deux sont séparés. Par exemple, on peut réduire un sprite d'un facteur 5 sur la verticale, d'un facteur 2 sur l'horizontale.








