(Translated by https://www.hiragana.jp/)
« Les cartes graphiques/Les cartes accélératrices 2D » : différence entre les versions — Wikilivres Aller au contenu

« Les cartes graphiques/Les cartes accélératrices 2D » : différence entre les versions

Un livre de Wikilivres.
Contenu supprimé Contenu ajouté
Ligne 117 : Ligne 117 :
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.
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.


==L'accélération matérielle du ''scrolling''==
==L'accélération matérielle du défilement==


[[File:Parallax-scroll-example.gif|thumb|Exemple de ''scrolling''.]]
[[File:Parallax-scroll-example.gif|thumb|Exemple de ''scrolling''.]]


Le ''scrolling'' permet de faire défiler l'environnement sur l'écran, spécialement quand le joueur se déplace. Les jeux de plateforme rétro utilisaient énormément le ''scrolling'', le joueur se déplaçait généralement de gauche à droite, ce qui fait que l'on parle de ''scrolling'' horizontal. Mais il y avait aussi le ''scrolling'' vertical, utilisé dans d'autres situations. Peu utilisé dans les jeux de plateforme, le ''scrolling'' vertical est absolument essentiel pour les ''Shoot 'em up'' !
Le défilement permet de faire défiler l'environnement sur l'écran, spécialement quand le joueur se déplace. Les jeux de plateforme rétro utilisaient énormément le défilement, le joueur se déplaçait généralement de gauche à droite, ce qui fait que l'on parle de défilement horizontal. Mais il y avait aussi le défilement vertical, utilisé dans d'autres situations. Peu utilisé dans les jeux de plateforme, le défilement vertical est absolument essentiel pour les ''Shoot 'em up'' !


Les cartes accélératrices intégraient souvent des techniques pour accélérer le ''scrolling''. La première optimisation est l'usage du ''blitter''. En effet, scroller ne demande pas de régénérer toute l'image à afficher à partir de zéro. L'idéal est de déplacer l'image de quelques pixels vers la gauche, puis de dessiner ce qui manque. Pour cela, on peut utiliser le ''blitter'' pour déplacer l'image dans le ''framebuffer''. L'optimisation est souvent très intéressante, mais elle est imparfaite et n'était pas suffisante sur les toutes premières consoles de jeu. Elle l'était sur les consoles plus récentes, ou disons plutôt : moins rétro. Les consoles moins rétros avaient des mémoires RAM plus rapides, ce qui rendait l'usage du ''blitter'' suffisante.
Les cartes accélératrices intégraient souvent des techniques pour accélérer le défilement. La première optimisation est l'usage du ''blitter''. En effet, défiler ne demande pas de régénérer toute l'image à afficher à partir de zéro. L'idéal est de déplacer l'image de quelques pixels vers la gauche, puis de dessiner ce qui manque. Pour cela, on peut utiliser le ''blitter'' pour déplacer l'image dans le ''framebuffer''. L'optimisation est souvent très intéressante, mais elle est imparfaite et n'était pas suffisante sur les toutes premières consoles de jeu. Elle l'était sur les consoles plus récentes, ou disons plutôt : moins rétro. Les consoles moins rétros avaient des mémoires RAM plus rapides, ce qui rendait l'usage du ''blitter'' suffisante.


Mais certains VDC implémentaient une forme de ''scrolling'' accéléré en matériel totalement différent. Les implémentations sont multiples, mais elles ajoutaient toutes des registres de ''scrolling'' dans le VDC, qui permettaient de scroller facilement l'écran. Il faut noter que l'implémentation du ''scrolling'' vertical est bien plus simple que pour le ''scrolling'' horizontal. En effet, les images sont stockées dans le ''framebuffer'' ligne par ligne ! Et le ''scrolling'' vertical demande de déplacer l'écran d'une ou plusieurs lignes. Les deux vont donc bien ensemble.
Mais certains VDC implémentaient une forme de défilement accéléré en matériel totalement différent. Les implémentations sont multiples, mais elles ajoutaient toutes des registres de défilement dans le VDC, qui permettaient de défiler facilement l'écran. Il faut noter que l'implémentation du défilement vertical est bien plus simple que pour le défilement horizontal. En effet, les images sont stockées dans le ''framebuffer'' ligne par ligne ! Et le défilement vertical demande de déplacer l'écran d'une ou plusieurs lignes. Les deux vont donc bien ensemble.


===Le ''scrolling'' vertical basé sur une modification de l'adresse de base===
===Le défilement vertical basé sur une modification de l'adresse de base===


Une technique utilise le fonctionnement d'un CRTC, couplé avec un ''framebuffer'' amélioré. Nous l’appellerons la technique du ''framebuffer'' étendu, terme de mon invention qui aide à comprendre en quoi consiste cette technique. Elle utilise cependant plus de mémoire vidéo qu'un ''framebuffer'' normal. Elle fonctionne très bien pour le ''scrolling'' vertical, elle demande quelques adaptations pour le ''scrolling'' horizontal.
Une technique utilise le fonctionnement d'un CRTC, couplé avec un ''framebuffer'' amélioré. Nous l’appellerons la technique du ''framebuffer'' étendu, terme de mon invention qui aide à comprendre en quoi consiste cette technique. Elle utilise cependant plus de mémoire vidéo qu'un ''framebuffer'' normal. Elle fonctionne très bien pour le défilement vertical, elle demande quelques adaptations pour le défilement horizontal.


La méthode demande d'utiliser un ''framebuffer'' beaucoup plus grand que l'image à afficher. Par exemple, imaginez qu'une console ait une résolution de 640*480, avec des couleurs sur 16 bits. L'image à afficher prend donc 640*480*2 = 600 Kilooctets. Maintenant, imaginez que la mémoire vidéo pour l'arrière-plan fasse 2 mégaoctets. On peut y stocker l’image à rendre, et ce qu'il y a hors de l'écran, en bas et en haut ou à gauche/droite. Si une scène est assez petite, l'arrière-plan tient entièrement dans la mémoire vidéo, changer l'adresse de base permet de scroller sur une distance assez longue, voire pour toute la scène. L'idée est de tout simplement dire au CRTC de demander à afficher l'image à partir de la ligne numéro X !
La méthode demande d'utiliser un ''framebuffer'' beaucoup plus grand que l'image à afficher. Par exemple, imaginez qu'une console ait une résolution de 640*480, avec des couleurs sur 16 bits. L'image à afficher prend donc 640*480*2 = 600 Kilooctets. Maintenant, imaginez que la mémoire vidéo pour l'arrière-plan fasse 2 mégaoctets. On peut y stocker l’image à rendre, et ce qu'il y a hors de l'écran, en bas et en haut ou à gauche/droite. Si une scène est assez petite, l'arrière-plan tient entièrement dans la mémoire vidéo, changer l'adresse de base permet de défiler l'écran sur une distance assez longue, voire pour toute la scène. L'idée est de tout simplement dire au CRTC de demander à afficher l'image à partir de la ligne numéro X !


Pour la comprendre, prenez le cas où on souhaite scroller d'un pixel, verticalement vers le bas. La première ligne de l'image disparait, une nouvelle apparait en bas de l'image. Cas simple, un peu irréaliste, mais qui permet de bien comprendre l'idée. Pour rappel, un CRTC incorpore deux compteurs de ligne et de colonne, ainsi qu'un registre pour l'adresse de départ de l'image dans le ''framebuffer''. L'idée est que l'adresse de départ de l'image est augmentée de manière à pointer sur la ligne suivante, en l'augmentant de la taille d'une ligne. Il ne reste plus qu'à remplir la ligne suivante, pas besoin de faire la moindre copie en mémoire vidéo ! Et scroller vers le haut demande au contraire de retrancher la taille d'une ligne de l'adresse. On peut généraliser le tout pour du ''scrolling'' vertical.
Pour la comprendre, prenez le cas où on souhaite défiler d'un pixel, verticalement vers le bas. La première ligne de l'image disparait, une nouvelle apparait en bas de l'image. Cas simple, un peu irréaliste, mais qui permet de bien comprendre l'idée. Pour rappel, un CRTC incorpore deux compteurs de ligne et de colonne, ainsi qu'un registre pour l'adresse de départ de l'image dans le ''framebuffer''. L'idée est que l'adresse de départ de l'image est augmentée de manière à pointer sur la ligne suivante, en l'augmentant de la taille d'une ligne. Il ne reste plus qu'à remplir la ligne suivante, pas besoin de faire la moindre copie en mémoire vidéo ! Et défiler vers le haut demande au contraire de retrancher la taille d'une ligne de l'adresse. On peut généraliser le tout pour du défilement vertical.


L'implémentation la plus complexe demande d'ajouter un registre de ''scrolling'' vertical, dans lequel on place le numéro de ligne à partir de laquelle il faut dessiner l'image. L'image en mémoire vidéo a plus de lignes que l'écran peut en afficher, le CRTC parcourt autant de ligne que ce que demande la résolution, le registre de ''scrolling'' vertical indique à partir de quelle ligne commencer l'affichage. Le calcul de l'adresse prend en compte le contenu de ce registre pour parcourir le ''framebuffer''.
L'implémentation la plus complexe demande d'ajouter un '''registre de défilement vertical''', dans lequel on place le numéro de ligne à partir de laquelle il faut dessiner l'image. L'image en mémoire vidéo a plus de lignes que l'écran peut en afficher, le CRTC parcourt autant de ligne que ce que demande la résolution, le registre de défilement vertical indique à partir de quelle ligne commencer l'affichage. Le calcul de l'adresse prend en compte le contenu de ce registre pour parcourir le ''framebuffer''.


Le ''scrolling'' en mode texte se base sur le même principe, sauf qu'on saute d'une ligne de caractère à une autre directement, le ''scrolling'' ne se fait pas par ligne de pixel. Et il est très utile, car scroller du texte vers le bas ou le haut est une opération très courante ! Aussi, même les premières cartes graphiques pour PC disposaient de techniques similaires pour accélérer le ''scrolling'' vertical. Les toutes premières cartes graphiques MBA (monochromes) et CGA n'incorporaient pas de ''scrolling'' vertical optimisé, mais les cartes suivantes, EGA et VGA, le faisaient.
Le défilement en mode texte se base sur le même principe, sauf qu'on saute d'une ligne de caractère à une autre directement, le défilement ne se fait pas par ligne de pixel. Et il est très utile, car défiler du texte vers le bas ou le haut est une opération très courante ! Aussi, même les premières cartes graphiques pour PC disposaient de techniques similaires pour accélérer le défilement vertical. Les toutes premières cartes graphiques MBA (monochromes) et CGA n'incorporaient pas de défilement vertical optimisé, mais les cartes suivantes, EGA et VGA, le faisaient.


===Le défilement vertical infini : le ''warp-around'' des adresses===
===Le défilement vertical infini : le ''warp-around'' des adresses===
Ligne 154 : Ligne 154 :
[[File:Parallax-scroll-example.gif|thumb|Parallax-scroll-example]]
[[File:Parallax-scroll-example.gif|thumb|Parallax-scroll-example]]


===Le ''scrolling'' horizontal et vertical implémenté par le CRTC===
===Le défilement horizontal et vertical implémenté par le CRTC===


Pour le ''scrolling vertical'', il faut procéder autrement. L'idée est d'utiliser des compteurs de ligne et de colonne séparés. L'idée est d'avoir des lignes plus longues dans le ''framebuffer'' que ce qui est indiqué dans le registre de résolution. On peut alors mémoriser une ligne plus longue que ce qui est affiché à l'écran, avec des portions non-affichées à l'écran.
Pour le défilement vertical, il faut procéder autrement. L'idée est d'utiliser des compteurs de ligne et de colonne séparés. L'idée est d'avoir des lignes plus longues dans le ''framebuffer'' que ce qui est indiqué dans le registre de résolution. On peut alors mémoriser une ligne plus longue que ce qui est affiché à l'écran, avec des portions non-affichées à l'écran.


L'idée est là encore d'initialiser le compteur de colonne avec une valeur qui est incrémentée d'un pixel à chaque fois qu'on scrolle vers la droite, décrémentée quand on va vers la gauche. Le registre pour la résolution horizontale, qui vérifie si la fin de la ligne/colonne est atteinte, est lui aussi incrémenté. La même méthode peut être utilisée pour le ''scrolling'' horizontal en faisant la même chose pour le compteur de ligne. L'implémentation demande d'ajouter un registre de ''scrolling'' horizontal, en plus du registre de ''scrolling'' vertical, le principe derrière est le même mais pour le numéro de colonne et non de ligne.
L'idée est là encore d'initialiser le compteur de colonne avec une valeur qui est incrémentée d'un pixel à chaque fois qu'on défile vers la droite, décrémentée quand on va vers la gauche. Le registre pour la résolution horizontale, qui vérifie si la fin de la ligne/colonne est atteinte, est lui aussi incrémenté. La même méthode peut être utilisée pour le défilement horizontal en faisant la même chose pour le compteur de ligne. L'implémentation demande d'ajouter un registre de défilement horizontal, en plus du registre de défilement vertical, le principe derrière est le même mais pour le numéro de colonne et non de ligne.


Notons que le comportement de ''warp-around'' peut aussi être implémenté pour les adresses et compteurs de colonne. Cela permet d'avoir du ''scrolling'' horizontal infini.
Notons que le comportement de ''warp-around'' peut aussi être implémenté pour les adresses et compteurs de colonne. Cela permet d'avoir du défilement horizontal infini.


La fonctionnalité était disponible sur les cartes EGA, les toutes premières cartes d'affichage datant d'avant même le standard VGA. Tout était en place sur ces cartes graphiques pour implémenter la technique : une mémoire vidéo assez grande, un ''framebuffer'' potentiellement plus grand que ce qui est affiché à l'écran, une adresse de départ qu'on peut incrémenter ligne par ligne, par incréments de 1 pixel. Le logiciel devait cependant utiliser cette faculté adéquatement pour implémenter le ''scrolling''.
La fonctionnalité était disponible sur les cartes EGA, les toutes premières cartes d'affichage datant d'avant même le standard VGA. Tout était en place sur ces cartes graphiques pour implémenter la technique : une mémoire vidéo assez grande, un ''framebuffer'' potentiellement plus grand que ce qui est affiché à l'écran, une adresse de départ qu'on peut incrémenter ligne par ligne, par incréments de 1 pixel. Le logiciel devait cependant utiliser cette faculté adéquatement pour implémenter le défilement.


Le défaut principal est que la technique ne marche pas vraiment avec un rendu à base de ''tiles''. La solution la plus simple permet de faire du scrolling tile par tile, mais le résultat à l'écran est tout simplement pas fluide du tout. La solution à ce problème a été trouvée par John Carmack, le programmeur derrière les moteurs des jeux IDSoftware comme DOOM, Quake, Wolfenstein 3D, etc. Il a inventé la technique de l'''Adaptive tile refresh'' qui permet justement d'avoir un ''scrolling'' fluide sur des cartes sans gestion hardware du ''scrolling''.
Le défaut principal est que la technique ne marche pas vraiment avec un rendu à base de ''tiles''. La solution la plus simple permet de faire du défilement tile par tile, mais le résultat à l'écran est tout simplement pas fluide du tout. La solution à ce problème a été trouvée par John Carmack, le programmeur derrière les moteurs des jeux IDSoftware comme DOOM, Quake, Wolfenstein 3D, etc. Il a inventé la technique de l'''Adaptive tile refresh'' qui permet justement d'avoir un défilement fluide sur des cartes sans gestion hardware du ''scrolling''.


{{NavChapitre | book=Les cartes graphiques
{{NavChapitre | book=Les cartes graphiques

Version du 17 juin 2024 à 16:50

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 performante que les trois suivantes, 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.

L'implémentation des optimisations du rendu 2D peuvent se faire soit directement dans le VDC, soit dans un circuit séparé. Le cas le plus fréquent est une intégration dans le VDC. Mais quelques consoles de jeu faisaient autrement. Elles utilisaient un circuit séparé pour les optimisations du rendu 2D. D'autres consoles avaient un VDC intégraient certaines optimisations, et un autre circuit à part pour les autres. 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 était un VDC qui intégrait l'accélération matérielle des sprites et qui 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'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é 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 parties 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

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 port 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és 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ée 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 !

Un exemple d'accélération des sprites : le Television Interface Adaptor des consoles Atari

Un exemple de carte 2D avec gestion de sprites matériels est celui des toutes premières consoles Atari, notamment celle de l'Atari Video Computer System. La carte graphique en question s'appelait la Television Interface Adaptor, abrévié TIA. Elle a une architecture foncièrement différente des autres consoles de l'époque : elle ne dispose pas du tout de mémoire vidéo et doit se débrouiller autrement ! À la place, le rendu d'une image est effectué ligne par ligne, et le TIA contient seulement de quoi mémoriser une ligne, dans quelques registres. Le processeur doit modifier ces registres entre l'affichage de deux lignes pour pouvoir afficher une image digne de ce nom. Il s'aggit d'une technique appelée le racing the beam.

Elle gère un arrière-plan très limité, stocké dans un registre. Le registre d'arrière-plan est un registre de 20 bits, qui est répliqué autant de fois que nécessaire sur tout la ligne. En clair, la ligne de l'écran est découpée en blocs de 20 pixels, chaque pixel étant codé sur un bit. Vous pourriez croire que la couleur codée dans ce registre est un simple noir et blanc, mais c'est plus compliqué. Un registre séparé contient la couleur affichée, elle-même encodée grâce à un système de palette indicé. Le registre d'arrière-plan indique, pour chaque pixel d'un bloc, si la couleur doit être affichée ou non.

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é.

Mettre à jour les registres du TIA à chaque ligne n'est pas si différent de modifier une mémoire vidéo entre deux lignes, ce qui fait que les solutions vues plus haut peuvent en théorie s'appliquer. 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.

L'accélération matérielle du défilement

Exemple de scrolling.

Le défilement permet de faire défiler l'environnement sur l'écran, spécialement quand le joueur se déplace. Les jeux de plateforme rétro utilisaient énormément le défilement, le joueur se déplaçait généralement de gauche à droite, ce qui fait que l'on parle de défilement horizontal. Mais il y avait aussi le défilement vertical, utilisé dans d'autres situations. Peu utilisé dans les jeux de plateforme, le défilement vertical est absolument essentiel pour les Shoot 'em up !

Les cartes accélératrices intégraient souvent des techniques pour accélérer le défilement. La première optimisation est l'usage du blitter. En effet, défiler ne demande pas de régénérer toute l'image à afficher à partir de zéro. L'idéal est de déplacer l'image de quelques pixels vers la gauche, puis de dessiner ce qui manque. Pour cela, on peut utiliser le blitter pour déplacer l'image dans le framebuffer. L'optimisation est souvent très intéressante, mais elle est imparfaite et n'était pas suffisante sur les toutes premières consoles de jeu. Elle l'était sur les consoles plus récentes, ou disons plutôt : moins rétro. Les consoles moins rétros avaient des mémoires RAM plus rapides, ce qui rendait l'usage du blitter suffisante.

Mais certains VDC implémentaient une forme de défilement accéléré en matériel totalement différent. Les implémentations sont multiples, mais elles ajoutaient toutes des registres de défilement dans le VDC, qui permettaient de défiler facilement l'écran. Il faut noter que l'implémentation du défilement vertical est bien plus simple que pour le défilement horizontal. En effet, les images sont stockées dans le framebuffer ligne par ligne ! Et le défilement vertical demande de déplacer l'écran d'une ou plusieurs lignes. Les deux vont donc bien ensemble.

Le défilement vertical basé sur une modification de l'adresse de base

Une technique utilise le fonctionnement d'un CRTC, couplé avec un framebuffer amélioré. Nous l’appellerons la technique du framebuffer étendu, terme de mon invention qui aide à comprendre en quoi consiste cette technique. Elle utilise cependant plus de mémoire vidéo qu'un framebuffer normal. Elle fonctionne très bien pour le défilement vertical, elle demande quelques adaptations pour le défilement horizontal.

La méthode demande d'utiliser un framebuffer beaucoup plus grand que l'image à afficher. Par exemple, imaginez qu'une console ait une résolution de 640*480, avec des couleurs sur 16 bits. L'image à afficher prend donc 640*480*2 = 600 Kilooctets. Maintenant, imaginez que la mémoire vidéo pour l'arrière-plan fasse 2 mégaoctets. On peut y stocker l’image à rendre, et ce qu'il y a hors de l'écran, en bas et en haut ou à gauche/droite. Si une scène est assez petite, l'arrière-plan tient entièrement dans la mémoire vidéo, changer l'adresse de base permet de défiler l'écran sur une distance assez longue, voire pour toute la scène. L'idée est de tout simplement dire au CRTC de demander à afficher l'image à partir de la ligne numéro X !

Pour la comprendre, prenez le cas où on souhaite défiler d'un pixel, verticalement vers le bas. La première ligne de l'image disparait, une nouvelle apparait en bas de l'image. Cas simple, un peu irréaliste, mais qui permet de bien comprendre l'idée. Pour rappel, un CRTC incorpore deux compteurs de ligne et de colonne, ainsi qu'un registre pour l'adresse de départ de l'image dans le framebuffer. L'idée est que l'adresse de départ de l'image est augmentée de manière à pointer sur la ligne suivante, en l'augmentant de la taille d'une ligne. Il ne reste plus qu'à remplir la ligne suivante, pas besoin de faire la moindre copie en mémoire vidéo ! Et défiler vers le haut demande au contraire de retrancher la taille d'une ligne de l'adresse. On peut généraliser le tout pour du défilement vertical.

L'implémentation la plus complexe demande d'ajouter un registre de défilement vertical, dans lequel on place le numéro de ligne à partir de laquelle il faut dessiner l'image. L'image en mémoire vidéo a plus de lignes que l'écran peut en afficher, le CRTC parcourt autant de ligne que ce que demande la résolution, le registre de défilement vertical indique à partir de quelle ligne commencer l'affichage. Le calcul de l'adresse prend en compte le contenu de ce registre pour parcourir le framebuffer.

Le défilement en mode texte se base sur le même principe, sauf qu'on saute d'une ligne de caractère à une autre directement, le défilement ne se fait pas par ligne de pixel. Et il est très utile, car défiler du texte vers le bas ou le haut est une opération très courante ! Aussi, même les premières cartes graphiques pour PC disposaient de techniques similaires pour accélérer le défilement vertical. Les toutes premières cartes graphiques MBA (monochromes) et CGA n'incorporaient pas de défilement vertical optimisé, mais les cartes suivantes, EGA et VGA, le faisaient.

Le défilement vertical infini : le warp-around des adresses

La technique précédente fonctionne bien, mais elle ne permet pas d'avoir du défilement infini, à savoir avec des maps dont la longueur n'est pas limitée par la mémoire vidéo. Mais on peut l'adapter pour obtenir du défilement vertical, en utilisant un comportement particulier du calcul des adresses. Le comportement en question est le warp-around.

Pour le comprendre, prenons l'exemple suivant. La mémoire vidéo peut contenir 1000 lignes, la résolution verticale est de 300 lignes. Si on démarre l'affichage à la 700ème ligne, tout va bien, on n'a pas besoin de warp-around. Mais maintenant, imaginons que le défilement vertical fasse démarrer l'affichage à partir de la 900ème ligne. L'affichage se fera normalement pour 100 lignes, mais la 101ème débordera hors du framebuffer, il n'y aura pas d'adresse associée. Le warp-around fait repartir les adresses à zéro lors d'un tel débordement. Ainsi, la 101ème adresse sera en fait l'adresse 0, la 102ème sera l'adresse 1, etc. En clair, si on commence à afficher l'image à la 900ème ligne, les 100 premières seront prises dans les 100 lignes à la fin du framebuffer, alors que les 200 suivantes seront les 200 lignes au début du framebuffer.

En faisant cela, on peut avoir un défilement vertical infini. Quand l'image affichée est démarrée assez loin, le début du framebuffer est libre, il contient des lignes qui ne seront sans doute pas affichées par la suite. Dans ce cas, on écrit dedans la suite du niveau, celle située après la ligne à la fin du framebuffer. Il suffit que le programmeur se charge de modifier le framebuffer de cette manière et on garantit que tout va bien se passer.

Avec cette technique, on peut avoir un défilement infini en utilisant seulement deux fois plus de mémoire vidéo que ce qui est nécessaire pour stocker une image à l'écran. Il est même possible de tricher pour utiliser moins de mémoire vidéo. Mais laissons cela de côté, tout cela est laissé à l’appréciation du programmeur. Maintenant, voyons les défauts des deux techniques précédentes. Le problème principal est que toute l'image bouge ! Or, le défilement demande généralement que certains éléments bougent, mais pas tous. Les sprites sont souvent gérés par un système de sprite matériel, ce qui fait qu'ils sont ou non concernés par le défilement et ce n'est pas tellement un problème.

Le vrai problème vient des techniques de défilement à parallaxe. Le défilement à parallaxe utilise plusieurs arrière-plans superposés qui défilent à des vitesses différentes. Il donne une illusion de profondeur dans l'environnement. La technique précédente fait bouger le framebuffer en bloc, tout d'un coup, et ne gère donc pas le défilement à parallaxe.

Parallax-scroll-example-2
Parallax-scroll-example

Le défilement horizontal et vertical implémenté par le CRTC

Pour le défilement vertical, il faut procéder autrement. L'idée est d'utiliser des compteurs de ligne et de colonne séparés. L'idée est d'avoir des lignes plus longues dans le framebuffer que ce qui est indiqué dans le registre de résolution. On peut alors mémoriser une ligne plus longue que ce qui est affiché à l'écran, avec des portions non-affichées à l'écran.

L'idée est là encore d'initialiser le compteur de colonne avec une valeur qui est incrémentée d'un pixel à chaque fois qu'on défile vers la droite, décrémentée quand on va vers la gauche. Le registre pour la résolution horizontale, qui vérifie si la fin de la ligne/colonne est atteinte, est lui aussi incrémenté. La même méthode peut être utilisée pour le défilement horizontal en faisant la même chose pour le compteur de ligne. L'implémentation demande d'ajouter un registre de défilement horizontal, en plus du registre de défilement vertical, le principe derrière est le même mais pour le numéro de colonne et non de ligne.

Notons que le comportement de warp-around peut aussi être implémenté pour les adresses et compteurs de colonne. Cela permet d'avoir du défilement horizontal infini.

La fonctionnalité était disponible sur les cartes EGA, les toutes premières cartes d'affichage datant d'avant même le standard VGA. Tout était en place sur ces cartes graphiques pour implémenter la technique : une mémoire vidéo assez grande, un framebuffer potentiellement plus grand que ce qui est affiché à l'écran, une adresse de départ qu'on peut incrémenter ligne par ligne, par incréments de 1 pixel. Le logiciel devait cependant utiliser cette faculté adéquatement pour implémenter le défilement.

Le défaut principal est que la technique ne marche pas vraiment avec un rendu à base de tiles. La solution la plus simple permet de faire du défilement tile par tile, mais le résultat à l'écran est tout simplement pas fluide du tout. La solution à ce problème a été trouvée par John Carmack, le programmeur derrière les moteurs des jeux IDSoftware comme DOOM, Quake, Wolfenstein 3D, etc. Il a inventé la technique de l'Adaptive tile refresh qui permet justement d'avoir un défilement fluide sur des cartes sans gestion hardware du scrolling.