# Les cartes graphiques/Le Video Display Controler : implémentation

Nous avions vu dans le chapitre précédent qu'il existe plusieurs types de VDC. Les VDC les plus simples, appelés des Video shifters, ne sont pas connectés à la mémoire vidéo. Ils fournissent des signaux de commande à l'écran, mais n’accèdent pas à la mémoire vidéo. D'autres VDC plus complexes sont capables de générer des adresses mémoire et accèdent directement à la mémoire vidéo, ils gèrent d'eux-mêmes le parcours du framebuffer. Parmi ces derniers, on distingue généralement plusieurs sous-types, suivant leur complexité et leurs fonctionnalités supportées.
La première catégorie est celle des Cathode Ray Tube Controler, ou CRTC. Leur nom vient du fait qu'ils servaient autrefois d'interface écran pour des écrans CRT. Ils gèrent des choses comme la résolution de l'écran, la fréquence d'affichage, le nombre de couleurs utilisés pour chaque pixel, etc.
Au-delà des Video shifters et des CRTC, les Video Interface Controler intègrent des fonctionnalités supplémentaires, comme de quoi gérer une palette indicée, ainsi que des circuits d'accélération 2D qu'on verra dans le prochain chapitre. En clair, ils regroupent tout ce qui est nécessaire pour faire une carte d'affichage, sauf la mémoire vidéo et éventuellement le RAMDAC. Ils peuvent gérer des RAMS séparées pour la gestion de la palette de couleur ou les caractères, voire peuvent l'intégrer dans leurs circuits. Là encore, ils peuvent souvent gérer à la fois mode texte et graphique, on peut les configurer pour choisir lequel utiliser.

## La génération des signaux de commande pour l'écran

Les VDC contiennent tous de quoi générer les signaux de commande à destination de l'écran. Et cela demande de générer pas mal de signaux, ainsi que des signaux d'horloge à la fréquence définie. Le premier signal à générer est le signal d'horloge transmission des pixels, à savoir le signal d'horloge dont la période est égale au temps mis pour envoyer un pixel à l'écran. Ce signal est souvent transmis à l'écran, via un fil dédié. Les VDC contiennent de quoi générer cette fréquence, grâce à un circuit oscillateur dédié. Mais il faut aussi générer les signaux de synchronisation verticale/horizontale.

### La génération des signaux de synchronisation verticale/horizontale

Le VDC gère les signaux de synchronisation verticale ou horizontale. Pour cela, ils intègrent deux compteurs (des circuits qui comptent de 0 à N). Le premier compteur compte les lignes transmises, l'autre les pixels dans une ligne, ce qui leur vaut les noms de compteur de colonne et de compteur de ligne. Les deux compteurs sont initialisés à 0 avant la transmission et sont incrémentés automatiquement quand on passe d'un pixel à l'autre, ou bien d'une ligne à l'autre. Quand le compteur atteint la valeur adéquate, il émet un signal de synchronisation verticale/horizontale. Au passage à la ligne suivante, le compteur de colonne est réinitialisé à 0, idem pour le compteur de ligne quand une image a été affichée totalement.
Ils sont configurés de manière à prendre en compte la résolution de l'écran, mais pas de la manière dont vous le pensez. Par exemple, pour une résolution de 640 par 480 : vous imaginez sans doute que le compteur de colonne est configuré pour compter de 0 à 639, alors que l'autre compte de 0 à 479. Par exemple, pour une résolution de 640 par 480, les deux compteurs sont initialisés à 0. Le compteur de colonne est incrémenté à chaque envoi de pixel, et il déclenche le signal de synchronisation horizontale une fois que le compteur atteint 640. Le compteur de colonne est alors réinitialisé après un certain temps, alors que le compteur de ligne est incrémenté. Le compteur de ligne est donc incrémenté à chaque nouvelle ligne. De plus, il émet un signal de synchronisation verticale quand il atteint 480, et est réinitialisé après cela.
Il est possible de faire ainsi, mais ce n'est pas la solution idéale. En réalité, il faut tenir compte du fait que les signaux de HSYNC et VSYNC, qui sont eux aussi générés par les deux compteurs. Imaginons que le signal HSYNC prenne 20 cycles d'horloge, et le signal VSYNC 150 cycles. Pour une résolution de 640 par 480, on utilise un compteur de colonne qui compte de 0 à 640 + 20, et un compteur de ligne qui compte de 0 à 480 + 150.
L'idée est d'utiliser des comparateurs pour générer les signaux HSYNC et VSYNC, un pour le signal HSYNC et un autre pour le signal VSYNC. En reprenant les valeurs mentionnées précédemment, on utilise un comparateur qui vérifie si le compteur de colonne est supérieur ou égal à 640, et un autre comparateur qui vérifie si le compteur de ligne est égal ou dépasse 480. La sortie des deux comparateurs fournit directement les signaux HSYNC et VSYNC.
Une autre solution remplace les comparateurs par une mémoire ROM. L'idée est d'envoyer les compteurs sur l'entrée d'adresse, la ROM fournit en sortie les signaux de commande destinés à l'écran. En remplissant la ROM avec les valeurs adéquates, la technique fonctionne à merveille et on peut se passer des circuits comparateurs. Pour les haute résolutions, il est possible d'utiliser deux ROMs : une pour le compteur de ligne, une pour le compteur de colonne.
Le VDC peut gérer plusieurs résolutions différentes, et les timings sont différents suivant les résolutions. Idéalement, il faut envoyer quelques bits de commande pour choisir la résolutions en entrée de la mémoire ROM pour choisir les bons timings. Avec des comparateurs, la technique demande d'utiliser les mêmes comparateurs, mais d'ajouter des circuits pour gérer les différentes résolutions.
Les mêmes compteurs ou la ROM sont souvent utilisés pour générer les raster interrupts et le bit de blanking, qui permettent de prévenir le processeur quand la carte d'affichage a terminé d'envoyer une ligne et/ou une image entière à l'écran. Notons qu'il est possible d'implémenter les interruptions à partir du bit de blanking, cela demande juste aux compteurs de générer ce bit de blanking et de l'utiliser pour générer les raster interrupt. Au passage, les compteurs de ligne et colonne ne servent pas qu'à générer des signaux : on verra dans la section sur le CRTC que quand on dispose de ces deux compteurs, ajouter de quoi parcourir le framebuffer est trivial !

### L'exemple des timings du standard VGA

Reprenons l'exemple du standard VGA. Avec ce standard, il existait un fil H-SYNC pour indiquer qu'on transmettait une nouvelle ligne et un fil V-SYNC pour indiquer qu'on envoie une nouvelle image. Une nouvelle ligne ou image est indiquée en mettant un 0 sur le fil adéquat. De plus, on devait attendre un certain temps entre la transmission de deux lignes, ce qui introduisait des vides dans le flux de pixels. Même chose entre deux images, sauf que le temps d'attente était plus long que le temps d'attente entre deux lignes. Le tout est détaillé dans le schéma ci-dessous, qui détaille le cas pour une résolution de 640 par 480.
Le compteur de colonne est cadencé à une fréquence bien précise, qui détermine le temps mis pour passer d'un pixel à l'autre. Le temps de transmission d'un pixel est de 25,6 µs / 640 = 0,04 µs, ce qui correspond à une fréquence de 25 MégaHertz. Et cela permet d'implémenter facilement les deux temps d'attente avant et après l'affichage d'une ligne. Les temps d'attente de 1,54 et 0,64 µs correspondent respectivement à 38 et 16 cycles du compteur, la durée de 3,8 µs du signal H-sync correspond à 95 cycles. En tout, cela fait 640 + 95 + 16 + 38 = 789. Il faut donc un compteur qui compte de 0 à 788.
La transmission des pixels commence quand le compteur commence à compter. Puis, le compteur continue de compter pendant 0,64 µs alors qu'aucun pixel n'est envoyé, afin de gérer le temps d'attente après le signal H-sync. Puis, au 640 + 16ème cycle, le signal H-sync est généré pendant 95 cycles. Enfin, le compteur continue de compter pendant 38 cycles pour le second temps d'attente, avant le prochain envoi de ligne. Le signal H-sync est donc généré quand le compteur a une valeur comprise entre 656 et 751 : il suffit d'ajouter un comparateur qui vérifie si le compteur est dans cet intervalle, et donc la sortie est à zéro si c'est le cas. L'adresse n'est pas calculée si le compteur n'a pas une valeur comprise entre 0 et la largeur indiquée par la résolution.
La même logique s'applique avec le signal V-sync, mais avec des timings différents, illustrés plus haut.
Pour implémenter tout cela, il suffit de combiner les deux compteurs avec des circuits comparateurs, qui vérifient si la valeur du compteur est dans tel ou tel intervalle. Il faut au minimum deux circuits comparateurs, un pour le signal HSYNC, un autre pour le signal VSYNC. D'autres compteurs peuvent être utilisés pour générer les bits de blanking ou pour réinitialiser le compteur à la valeur adéquate. Les comparateurs peuvent être remplacés par une mémoire ROM, comme dit plus haut.

## La génération de l'adresse à envoyer au framebuffer

Le CRTC est un VDC capable de générer les adresses mémoire à destination du framebuffer. Pour résumer ce qu'il fait, les pixels sont lus les uns après les autres, ligne par ligne, en balayant le framebuffer. Pour cela, il réutilise les deux compteurs précédents et utilise leur contenu pour forger l'adresse mémoire adéquate à chaque cycle.
Rappelons qu'un écran est considéré par la carte graphique comme un tableau de pixels, organisé en lignes et en colonnes. Chaque pixel a deux coordonnées : sa position en largeur et en hauteur. Par convention, on suppose que le pixel de coordonnées (0,0) est celui situé tout haut et tout à gauche de l'écran. Le pixel de coordonnées (X,Y) est situé sur la X-ème colonne et la Y-ème ligne. Le tout est illustré ci-contre.
Avec le balayage progressif, la carte graphique doit envoyer les pixels ligne par ligne, colonne par colonne : de haut en bas et de gauche à droite. La carte graphique envoie le pixel (0,0) en premier, puis celui situé à gauche et ainsi de suite. Quand il a fini d'envoyer la ligne de pixel, il descend et reprend à la ligne suivante, tout à gauche. L'ordre de transfert est donc assez simple : ligne par ligne, de gauche à droite.

### Le pointeur de framebuffer

Une méthode simple pour l'implémenter se base sur le fait que l'image à envoyer est stockée ligne par ligne dans la mémoire, avec les pixels d'une étant mémorisés dans l'ordre de balayage progressif. Les programmeurs appellent un tableau bi-dimensionnel. On peut récupérer un pixel en spécifiant les deux coordonnées X et Y, ce qui est l'idéal. Pour détailler un peu ce tableau bi-dimensionnel de pixels, c'est juste que les pixels consécutifs sur une ligne sont consécutifs en mémoire et les lignes consécutives sur l'écran le sont aussi dans la mémoire vidéo. En clair, il suffit de balayer la mémoire pixel par pixel en partant de l'adresse mémoire du premier pixel, jusqu’à atteindre la fin de l'image.
Pour cela, il suffit d'utiliser un simple compteur d'adresse. Le compteur contient l'adresse, à savoir la position du pixel en mémoire. Il est initialisé avec l'adresse du premier pixel, il est incrémenté à chaque envoi de pixel, et il s’arrête une fois que l'image est totalement envoyée. La méthode en question est appelée la méthode du framebuffer pointer, ou pointeur de framebuffer.
Le problème est qu'il faut gérer l'application des signaux de synchronisation verticale/horizontale. Le compteur d'adresse doit arrêter de compter pendant que ces signaux sont transmis. De plus, il faut tenir compte des timings, comme le temps pour remettre le canon à électrons d'un CRT au début de la ligne suivante. Rien d'insurmontable, mais il faut ajouter un circuit qui détermine si un signal de synchronisation HSYNC/VSYNC est à envoyer à l'écran, et stoppe le compteur si c'est le cas.

### La réutilisation des compteurs de ligne/colonne

Une autre solution, qui se marie mieux avec les signaux de synchronisation, combine un pointeur de framebuffer avec les compteurs de ligne et de colonne vus dans la section précédente. Le contenu des compteurs de ligne et de colonne est envoyé à un circuit de calcul d'adresse, qui détermine la position du pixel à envoyer. L'adresse mémoire du pixel à afficher est calculée à partir de la valeur des deux compteurs, et de l'adresse du premier pixel. Le calcul de l'adresse prend en compte les timings, en n'accédant pas à la mémoire quand la valeur des compteurs dépasse celle de la résolution à rendre. Par exemple, pour une résolution de 640 par 480, le calcul d'adresse ne donne pas de résultat si le compteur de colonne dépasse 640 : c'est signe que le compteur envoie des signaux de synchronisation horizontale.
Le tout peut être amélioré pour implémenter le double buffering. Pour cela, il suffit d'utiliser deux registres pour l'adresse de base : un pour le front buffer et un autre pour le back buffer. La carte vidéo choisit le bon registre à utiliser, ce qui permet de passer de l'un à l'autre en quelques cycles d'horloge. En changeant l'adresse pour la faire pointer vers l'ancien back buffer, l’interversion se fait automatiquement.
L'entrelacement est géré par le VDC, qui lit l'image à afficher une ligne sur deux en mémoire vidéo. Gérer l'entrelacement est donc un sujet qui implique l'écran mais aussi la carte d'affichage. Notamment, la lecture des pixels dans la mémoire vidéo se fait différemment. Le compteur de ligne est modifié de manière à avoir une séquence de comptage différente. Déjà, il compte deux par deux, pour sauter une ligne sur deux. De plus, quand il est réinitialisé, il est réinitialisé à une valeur qui est soit paire, soit impaire, en alternant.

## Les fonctionnalités supplémentaires : les Video Interface Controlers

Les Video Shifters incorporent juste de quoi générer les signaux de commande. Les CRTC font de même, mais incorporent aussi de quoi générer l'adresse mémoire à destination du framebuffer. Les Video Interface Controlers sont des VDC très complexes, qui incorporent des fonctionnalités avancées. La plupart de ces fonctionnalités ne sont pas liées au rendu graphique lui-même. Il est courant qu'ils incorporent une carte son rudimentaire, de quoi gérer les entrées clavier-souris, etc.
Prenons par exemple le VDP 7360/8360 TExt Display (TED). Niveau affichage, il gérait mode texte et graphique, avec une résolution allant jusqu’au 320 × 200, avec une SRAM pour la palette de couleur intégrée. Il ressemble beaucoup au MOS Technology VIC-II de la Commorodre 64, mais avec des fonctionnalités de rendu 2D en moins. Mais contrairement à la VIC-II, il incorpore des circuits de génération sonore, avec de quoi générer un signal sonore dit carré ou du bruit blanc. Il incorporait aussi des circuits d'interface avec le clavier, ainsi que des timers (des circuits capables de compter une durée précise).
Une fonctionnalité très courante des VIC est la gestion d'un light pen, une sorte de stylet pour écrans CRT, aujourd'hui disparus. Beaucoup de VIC incorporaient cette fonctionnalité, très fréquente pour l'époque. Il faut dire que les light pen étaient très pratiques pour toute application gérant autre chose que tu texte, comme le dessin assisté par ordinateur. Mais même pour ls applications texte, le light pen était utile, pour sélectionner une lettre ou un mot, éventuellement pour le corriger. Les VIC géraient le light pen en interne et présentaient au processeurs deux registres, qui indiquaient la position du light pen à l'écran.

### Les fonctionnalités graphiques avancées des VIC

Il arrive que les VIC intègrent de quoi gérer une palette indicée, et incorporent donc une petite mémoire RAM spécialisée pour. En clair, ils ne font pas que CRTC, mais gèrent tout ce qui est au-delà du framebuffer et qui demande de modifier ou d'interpréter les pixels.
La plupart gèrent des fonctionnalités de rendu 2D qu'on verra dans le chapitre suivant, sur les cartes accélératrices 2D. La gestion matérielle des sprites était assez courante, et la gestion d'un curseur de souris matériel n'était pas rare. Des techniques de tracé de ligne sont aussi souvent présentes. Par exemple, certains intègrent un contrôleur DMA amélioré, pour gérer les copies de blocs de mémoire dans la mémoire vidéo.
De plus, beaucoup d'entre eu incorporent des circuits liés à la gestion de la mémoire vidéo. Pour rappel, l'accès à une mémoire DRAM est plus complexe que l'accès aux autres mémoires RAM, notamment, car les timings des accès mémoire sont complexes et que l'adresse doit être envoyée en deux fois. Pour cela, un circuit appelé le contrôleur mémoire est souvent intégré au VIC. Le contrôleur mémoire contient aussi de quoi gérer le rafraichissement mémoire ! En effet, la mémoire vidéo est souvent une mémoire de type DRAM, similaire aux mémoires SDRAM ou DDR des PC actuels. Elles tendent à perdre leur contenu au bout d'un certain temps, ce qui fait qu'elles doivent être rafraichies régulièrement pour éviter cet effacement. Les VIC peuvent incorporent des circuits pour effectuer ce rafraichissement automatiquement.
Les Video Display Co-Processor, aussi appelés VDC à co-processeur, sont des VIC intègrent un processeur, éventuellement connecté à sa propre mémoire RAM. Un VDC à co-processeur lit une série d'instructions dans la mémoire RAM. La liste d'instruction s'appelle la display list, terme qui reviendra dans quelques chapitres. En règle générale, une instruction commande l'affichage d'une ligne à l'écran. Elle dit ce qu'il faut afficher et comment, dans quelle résolution, dans quel mode graphique, etc. Du moins, c'est le cas sur les VDC à co-processeur les plus complexes, les plus simples ont des instructions très simples, qui ne gèrent pas autant de fonctionnalités. L

### Un exemple : le NEC μPD7220

Un bon exemple de VIC est le NEC μPD7220 de NEC. Le schéma ci-contre illustre ce qu'il y a à l'intérieur. On voit qu'il y a plusieurs circuits à 'intérieur, chacun spécialisé dans une tâche précise. Pour résumer, il contient : un CRTC, des circuits mémoire, et des circuits d'accélération 2D.
Le CRTC, qui contient lui-même plusieurs sous-circuits :
* Un circuit de génération des signaux de commande pour l'écran (HSYNC, VSYNC, autres).
* Des registres de configuration et de status.
* Une mémoire ROM et une mémoire RAM pour le circuit de contrôle.
Les circuits liés à la mémoire vidéo sont les suivants :
* Un circuit qui gère les timings pour les accès mémoire.
* Un contrôleur mémoire avec un circuit de rafraichissement intégré.
* Un contrôleur DMA pour la communication avec le bus ou les copies de blocs mémoire.
* Une mémoire FIFO pour gérer les accès simultanés à la mémoire du CPU et du VDP (voir chapitre sur le framebuffer).
Les circuits d'accélération 2D regroupent :
* Un circuit de tracé de lignes et de figures géométriques.
* Un circuit pour les zooms et certaines opérations graphiques similaires.
Enfin, il faut citer le circuit pour la gestion d'un light pen.