Les cartes graphiques/Les systèmes à framebuffer
Les cartes graphiques récentes utilisent une portion de la mémoire vidéo pour stocker l'image à afficher à l'écran. La portion de VRAM en question est appelée le framebuffer, ou encore le tampon d'image. La mémoire vidéo peut aussi stocker d'autres informations importantes : les textures et les vertices de l'image à calculer, ainsi que divers résultats temporaires. Mais pour le moment, concentrons-nous sur le tampon d'image.
La taille du framebuffer limite la résolution maximale atteignable. En effet, prenons une image dont la résolution est de 640 par 480 : l'image est composée de 480 lignes, chacune contenant 640 pixels. En tout, cela fait 640 * 480 = 307 200 pixels. Si chaque pixel est codé sur 32 bits, l'image prend donc 307 200 * 32 = 9 830 400 bits, soit 1 228 800 octets, ce qui fait 1200 kilo-octets, plus d'un méga-octet. Si la carte d'affichage a moins d'un méga-octet de mémoire vidéo, elle ne pourra pas afficher cette résolution, sauf en trichant. De manière générale, la mémoire prise par une image se calcule comme : nombre de pixels * taille d'un pixel, où le nombre de pixels de l’image se calcule à partir de la résolution (on multiplie la hauteur par la largeur de l'écran, les deux exprimées en pixels).
Le codage des pixels dans le framebuffer
[modifier | modifier le wikicode]Tout pixel est codé sur un certain nombre de bits, qui dépend du standard d'affichage utilisé. Dans un fichier image, les données sont compressées avec des algorithmes compliqués, ce qui a pour conséquence qu'un pixel est codé sur un nombre variable de bits. Certains vont l'être sur 5 bits, d'autres sur 16, d'autres sur 4, etc. Mais dans une carte graphique, ce n'est pas le cas. Une carte graphique n’intègre pas d'algorithme de compression d'image dans le framebuffer, les images sont stockées décompressées. Tout pixel prend alors le même nombre de bit, ils ont tous une taille en binaire qui est fixe.
Le codage des images monochromes
[modifier | modifier le wikicode]À l'époque des toutes premières cartes graphiques, les écrans étaient monochromes et ne pouvait afficher que deux couleurs : blanc ou noir. De fait, il suffisait d'un seul bit pour coder la couleur d'un pixel : 0 codait blanc, 1 codait noir (ou l'inverse, peu importe). Par la suite, les niveaux de gris furent ajoutés, ce qui demanda d'ajouter des bits en plus.
| 1 bit | 2 bit | 4 bit | 8 bit |
|---|---|---|---|
|
|
|
|
Le cas le plus simple est celui des premiers modes CGA où 4 bits étaient utilisés pour indiquer la couleur : 1 bit pour chaque composante rouge, verte et bleue et 1 bit pour indiquer l'intensité (sombre / clair).
La technique de la palette indicée
[modifier | modifier le wikicode]
Avec l'apparition de la couleur, il fallut ruser pour coder les couleurs. Cela demandait d'utiliser plus de 1 bit par pixel : 2 bits permettaient de coder 4 couleurs, 3 bits codaient 8 couleurs, 4 bits codaient 16 couleurs, 8 bits codaient 256 couleurs, etc. Chaque combinaison de bit correspondait à une couleur et la carte d'affichage contenait une table de correspondance qui fait la correspondance entre un nombre et la couleur associée. Cette technique s'appelle la palette indicée, la table de correspondance s'appelant la palette.

L'implémentation de la palette indicée demande d'ajouter à la carte graphique une table de correspondance pour traduire les couleurs au format RGB. Elle s'appelait la Color Look-up Table (CLT) et est placée après la mémoire vidéo. Tout pixel qui sort de la mémoire vidéo est envoyé à la CLT, sur son entrée d'adresse. En réponse, elle fournit en sortie le pixel coloré, la couleur RGB voulue.
Au tout début, la Color Look-up Table était une ROM qui mémorisait la couleur RGB pour chaque numéro envoyé en adresse. De ce fait, la table de correspondance était généralement fixée une bonne fois pour toute dans la carte d'affichage, dans un circuit dédié.
Mais par la suite, les cartes d'affichage permirent de modifier la table de correspondance dynamiquement. La CLT était alors une mémoire SRAM, ce qui permettait de changer la palette à la volée. Les programmeurs pouvaient modifier son contenu, et ainsi changer la correspondance nombre-couleur à la volée. La SRAM est soit mappée en mémoire, soit accessible de manière indirecte par des commandes spécialisées. La Color Look-up Table était parfois fusionnée avec le DAC qui convertissait les pixels numériques en données analogiques : le tout formait ce qui s'appelait le RAMDAC.

Des applications différentes pouvaient ainsi utiliser des couleurs différentes, on pouvait adapter la palette en fonction de l'image à afficher, c'était aussi utilisé pour faire des animations sans avoir à modifier la mémoire vidéo. Les applications étaient multiples. En changeant le contenu de la palette, on pouvait réaliser des gradients mobiles, ou des animations assez simples : c'est la technique du color cycling.
Le standard RGB et ses dérivés
[modifier | modifier le wikicode]
Au-delà de 8/12 bits, la technique de la palette n'est pas très intéressante car elle demande une table de correspondance assez grosse, donc beaucoup de mémoire. Ce qui fait que le codage des couleurs a dû prendre une autre direction quand la limite des 8 bits fût dépassée. L'idée pour contourner le problème est d'utiliser la synthèse additive des couleurs, que vous avez certainement vu au collège. Pour rappel, cela revient à synthétiser une couleur en mélangeant deux à trois couleurs primaires. La manière la plus simple de faire cela est de mélanger du Rouge, du Bleu, et du Vert. En appliquant cette méthode au codage des couleurs, on obtient le standard RGB (Red, Green, Blue). L'intensité du vert est codée par un nombre, idem pour le rouge et le bleu.
Autrefois, il était courant de coder un pixel sur 8 bits, soit un octet : 2 bits étaient utilisés pour coder le bleu, 3 pour le rouge et 3 pour le vert. Le fait qu'on ait choisi seulement 2 bits pour le bleu s'explique par le fait que l’œil humain est peu sensible au bleu, mais est très sensible au rouge et au vert. Nous avons du mal à voir les nuances fines de bleu, contrairement aux nuances de vert et de rouge. Donc, sacrifier un bit pour le bleu n'est pas un problème. De nos jours, l'intensité d'une couleur primaire est codée sur 8 bits, soit un octet. Il suffit donc de 3 octets, soit 24 bits, pour coder une couleur.
Une autre astuce pour économiser des bits est de se passer d'une des trois couleurs primaires, typiquement le bleu. En faisant cela, on code toutes les couleurs par un mélange de deux couleurs, le plus souvent du rouge et du vert. Vu que l’œil humain a du mal avec le bleu, c'est souvent la couleur bleu qui disparait, ce qui donne le standard RG. En faisant cela, on économise les bits qui codent le bleu : si chaque couleur primaire est codée sur un octet, deux octets suffisent au lieu de trois avec le RGB usuel.
| RGB 16 bits | RG 16 bits |
|---|---|
|
|
L'organisation du framebuffer
[modifier | modifier le wikicode]Le framebuffer peut être organisé plusieurs manières différentes, mais deux grandes méthodes se dégagent. Elles portent les noms de framebuffer compact et de framebuffer planaire. La première est bien plus intuitive que la seconde, c'est sans doute celle qui vous viendrait à l'esprit en premier si vous réfléchissiez à la manière de stocker une image dans une RAM vidéo. Mais elle a quelques défauts qui ne se manifestent que sur les anciennes cartes graphiques, qui devaient faire avec des limitations en RAM assez importantes. Voyons cela en détail.
Le framebuffer compact
[modifier | modifier le wikicode]La toute première est celle du packed framebuffer, ou encore du framebuffer compact. Elle est très intuitive : les pixels sont placés les uns à côté des autres en mémoire. L'image est découpée en plusieurs lignes de pixels, deux pixels consécutifs sur une ligne sont placés à des adresses consécutives, deux lignes consécutives se suivent dans la mémoire.
Cependant, un framebuffer compact a quelques problèmes sur les vielles cartes graphiques. Le framebuffer est placé dans une mémoire RAM, qui est adressable par octet. En clair, la mémoire est composée d'octets, qui ont chacun une adresse mémoire. Le processeur et la carte graphique peuvent lire ou écrire un octet à la fois, mais on ne peut pas modifier 1 bit isolé, ni des groupes de 2 ou 4 bits. La conséquence est qu'idéalement, un pixel doit faire au moins un octet. Avec un octet par pixel, on a alors 256 couleurs possibles par pixel, ce qui était beaucoup dans les années 80-90. Il est possible de coder un pixel sur 2, 3, 4 octets, au prix d'une consommation de mémoire 2, 3, ou 4 fois plus importante.
Sur les cartes graphiques modernes, ca ne pose pas de problème. La mémoire vidéo est assez grande pour qu'on puisse attribuer entre 1 et 4 octets par pixel, parfois plus. Mais sur les anciennes cartes graphiques, ce n'était pas le cas. Par exemple, les cartes graphiques VGA disposaient de 256 kilo-octets de mémoire vidéo. Or, une simple image en résolution 640 par 480 contient 307 200 pixels, ce qui prend déjà 300 kilo-octets ! Il fallait donc limiter la taille des images, sans pour autant toucher à leur résolution. Et la seule solution était de réduire le nombre de bits par pixels.
Typiquement, une solution était de stocker plusieurs pixels par octet, les seules solutions étant d'utiliser 1, 2 ou 4 bits par pixel. Utiliser 4 bits par pixel permettait de stocker deux pixels de 16 couleurs par octets. Il était aussi possible de stocker 2 bits par pixel, de stocker 4 pixels de 4 couleurs par octets. Le cas avec des pixels de 1 bit permettait de stocker 8 pixels monochromes (deux couleurs, typiquement blanc et noir).
Mais c'était tout sauf pratique quand il fallait lire ou écrire des pixels en mémoire vidéo. L'écriture devait se faire par groupe de deux pixels, ce qui n'était pas pratique pour le programmeur. Pour écrire un seul pixel, la solution était de copier un octet dans un registre du processeur, de modifier uniquement un pixel dans cet octet, puis d'écrire le résultat en mémoire vidéo. Mais c'était très lent. Aussi, une autre solution a vu le jour, qui permettait d'encoder des pixels sur 2, 3, 4, 5, 6, 7 bits. Mais elle demandait de modifier la manière dont la mémoire RAM était utilisée pour stocker le framebuffer.
Les framebuffers planaires
[modifier | modifier le wikicode]Une solution simple serait d'utiliser des mémoires qui ne sont pas composées d'octets, mais de groupes de 2/3/4/5/6/7 bits, chacun correspondant à un pixel. Mais il faudrait alors fabriquer ces mémoires sur-mesure pour une carte graphique bien précise. Une autre solution utilise des mémoires bit-adressables, à savoir qu'il y a non pas un octet, mais un bit par adresse mémoire. Une telle mémoire permet de lire/écrire bit par bit, et non octet par octet. L'idée est alors de prendre 2/3/4/5/6/7 mémoires bit-adressables, et de les combiner pour simuler une mémoire composée de groupes de 2/3/4/5/6/7 bits.
Prenons le cas où un pixel est codé sur 3 bits, qui sont appelés le bit de poids fort, le bit de poids intermédiaire et le bit de poids faible. Supposons qu'on veuille une mémoire qui soit capable de stocker 200 000 de pixels. Dans ce cas, on prend trois mémoires de 200 000 bits : la première mémoire stockera les bits de poids fort, une autre les bits de poids faible, et la dernière prendra les bits du milieu.
Le principe se généralise pour des pixels codés sur N bits, sauf qu'il faudra alors N mémoires RAM. Les N mémoires RAM de 1 bit sont appelées des bitplanes. Une telle organisation est ce qu'on appelle un planar framebuffer, aussi appelé un framebuffer planaire. Disons-le clairement, la méthode est compliquée et pas intuitive, mais elle permet de coder des pixels sur 2, 3, 4, 5, 6, 7, 9, 11 bits ou autre. Elle n'est plus utilisée depuis que les cartes d'affichage se sont standardisées avec une taille des pixels multiple d'un octet.

Avec un framebuffer planaire, un pixel est répartit sur plusieurs mémoires, qu'il faut lire ou écrire simultanément. L’inconvénient que lire un pixel consomme plus d'énergie dans le cas général, car on accède à plusieurs mémoires simples au lieu d'une. Par contre, il est possible de modifier un bitplane indépendamment des autres, ce qui permet de faire certains effets graphiques simplement.
La technique a le bon gout d'être assez simple à implémenter au niveau du matériel. Le seul défaut de cette technique, au niveau matériel, est qu'elle demande d'utiliser des mémoires bit-adressables. Dans les faits, de telles mémoires ne sont pas rares, mais elles sont plus rares que les mémoires adressables par octet. Nous verrons plus bas, quand nous étudierons le standard VGA, qu'il est possible d'émuler des mémoires bit-adressable avec des mémoires adressables par octet. Mais laissons cela à plus tard.
Un exemple d'utilisation de planar framebuffer est l'ancien ordinateur/console de jeu Amiga Commodore. L'Amiga possédait 5 bits par pixel, donc disposait de 5 mémoires distinctes, et affichait 32 couleurs différentes. L'Amiga permettait de changer le nombre de bits nécessaires à la volée. Par exemple, si un jeu n'avait besoin que de quatre couleurs, seule deux plans/mémoires étaient utilisées. En conséquence, tout était plus rapide : les écritures dedans étaient alors accélérées, car on n'écrivait que 2 bits au lieu de 5. Et la RAM utilisée était limitée : au lieu de 5 bits par pixel, on n'en utilisait que 2, ce qui laissait trois plans de libre pour rendre des effets graphiques ou tout autre tache de calcul. Tout cela se généralise avec 3, 4, voire 1 seul bit d'utilisé.
Un sixième bit était utilisé pour le rendu dans certains modes d'affichage.
- Dans le mode Extra-Half Brite (EHB), le sixième bit indique s'il faut réduire la luminosité du pixel codé sur les 5 autres bits. S'il est mit à 1, la luminosité du pixel est divisée par deux, elle est inchangée s'il est à 0.
- En mode double terrain de jeu, les 6 bits sont séparés en deux framebuffer de 3 bits, qui sont modifiés indépendamment les uns des autres. Le calcul de l'image finale se fait en mélangeant les deux framebuffer d'une manière assez précise qui donne un rendu particulier. Les deux framebuffer sont scrollables séparément.
- Le mode Hold-And-Modify (HAM) interprète les 6 bits en tant que 4 bits de couleur et 2 bits de contrôle qui indiquent comment modifier la couleur du pixel final.
Le multibuffering et la synchronisation verticale
[modifier | modifier le wikicode]Sur les toutes premières cartes graphiques, le framebuffer ne pouvait contenir qu'une seule image. L'ordinateur écrivait donc une image dans le framebuffer et celle-ci était envoyée à l'écran dès que possible. Cependant, écran et ordinateur n'étaient pas forcément synchronisés. Rien n’empêchait à l’ordinateur d'écrire dans le framebuffer pendant que l'image était envoyée à l'écran. Et cela peut causer des artefacts qui se voient à l'écran.
Un exemple typique est celui des traitements de texte. Lorsque le texte affiché est modifié, le traitement de texte efface l'image dans le framebuffer et recalcule la nouvelle image à afficher. Ce faisant, une image blanche peut apparaitre durant quelques millisecondes à l'écran, entre le moment où l'image précédente est effacée et le moment où la nouvelle image est disponible. Ce phénomène de flickering; d'artefacts liés à une modification de l'image pendant qu'elle est affichée, est des plus désagréables.
Le double buffering
[modifier | modifier le wikicode]Pour éviter cela, on peut utiliser la technique du double buffering. L'idée derrière cette technique est de calculer une image en avance et de les mémoriser celle-ci dans le framebuffer. Mais cela demande que le framebuffer ait une taille suffisante, qu'il puisse mémoriser plusieurs images sans problèmes. Le framebuffer est alors divisé en deux portions, une par image, auxquelles nous donnerons le nom de tampons d'affichage. L'idée est de mémoriser l'image qui s'affiche à l'écran dans le premier tampon d'affichage et une image en cours de calcul dans le second. Le tampon pour l'image affichée s'appelle le tampon avant, ou encore le front buffer, alors que celui avec l'image en cours de calcul s'appelle le back buffer.

Quand l'image dans le back-buffer est complète, elle est copiée dans le front buffer pour être affichée. L'ancienne image dans le front buffer est donc éliminée au profit de la nouvelle image. Le remplacement peut se faire par une copie réelle, l'image étant copiée le premier tampon vers le second, ce qui est une opération très lente. C'est ce qui est fait quand le remplacement est réalisé par le logiciel, et non par la carte graphique elle-même. Par exemple, c'est ce qui se passait sur les très anciennes versions de Windows, pour afficher le bureau et l'interface graphique du système d'exploitation.
Mais une solution plus simple consiste à intervertir les deux tampons, le back buffer devenant le front buffer et réciproquement. Une telle interversion fait qu'on a pas besoin de copier les données de l'image. L'interversion des deux tampons peut se faire au niveau matériel.
La synchronisation verticale
[modifier | modifier le wikicode]Lors de l'interversion des deux tampons, le remplacement de la première image par la seconde est très rapide. Et il peut avoir lieu pendant que l'écran affiche la première image. L'image affichée à l'écran est alors composée d'un morceau de la première image en haut, et de la seconde image en dessous. Cela produit un défaut d'affichage appelé le tearing. Plus votre ordinateur calcule d'images par secondes, plus le phénomène est exacerbé.

Pour éviter ça, on peut utiliser la synchronisation verticale, aussi appelée vsync, dont vous en avez peut-être déjà entendu parler. C'est une option présente dans les options de nombreux jeux vidéo, ainsi que dans les réglages du pilote de la carte graphique. Elle consiste à attendre que l'image dans le front buffer soit entièrement affichée avant de faire le remplacement. La synchronisation verticale fait disparaitre le tearing, mais elle a de nombreux défauts, qui s'expliquent pour deux raisons que nous allons aboder.
Rappelons que l'écran affiche une nouvelle image à intervalles réguliers. L'écran affiche un certain nombre d'images par secondes, le nombre en question étant désigné sous le terme de "fréquence de rafraîchissement". La fréquence de rafraichissement est fixe, elle est gérée par un signal périodique dans l'écran. Par contre, sans Vsync, le nombre de FPS n'est pas limité, sauf si on a activé un limiteur de FPS dans les options d'un jeu vidéo ou dans les options du driver. Avec Vsync, le nombre de FPS est limité par la fréquence de l'écran. Par exemple, si vous avez un écran 60 Hz (sa fréquence de rafraichissement est de 60 Hertz), vous ne pourrez pas dépasser les 60 FPS. Vous pourrez avoir moins, cependant, si l'ordinateur ne peut pas sortir 60 images par secondes sans problème. Un autre défaut de la Vsync est donc qu'il faut un PC assez puissant pour calculer assez de FPS.
Par contre, même avec la vsync activée, l'écran n'est pas parfaitement synchronisé avec la carte graphique. Pour comprendre pourquoi, nous allons faire une analogie avec une situation de la vie courante. Imaginez deux horloges, qui sonnent toutes les deux à midi. Les deux ont la même fréquence, à savoir qu'elles sonnent une fois toutes les 24 heures. Maintenant, cela ne signifie pas qu'elles sont synchronisées. Imaginez qu'une horloge indique 1 heure du matin pendant que l'autre indique minuit : les deux horloges sont désynchronisées, alors qu'elles ont la même fréquence. Il y a un décalage entre les deux horloges, un déphasage.
Eh bien la même chose a lieu, avec la vsync. La vsync égalise deux fréquences : la fréquence de l'écran et les FPS (la fréquence de génération d'image par la carte graphique). Par contre, les deux fréquences sont généralement déphasées, il y a un délai entre le moment où la carte graphique a rendu une image, et le moment où l'écran affiche une image. Cela n'a l'air de rien, mais cela peut se ressentir. D'où l'impression qu'ont certains joueurs de jeux vidéo que leur souris est plus lente quand ils jouent avec la synchronisation verticale activée. Le temps d'attente lié à la synchronisation verticale dépend du nombre d'images par secondes. Pour un écran qui affiche maximum 60 images par seconde, le délai ajouté par la synchronisation verticale est au maximum de 1 seconde/60 = 16.666... millisecondes.
Un autre défaut est que la synchronisation verticale entraîne des différences de timings perceptibles. Le phénomène se manifeste avec les vidéos/films encodés à 24 images par secondes qui s'affichent sur un écran à 60 Hz : l'écran affiche une image tous les 16.6666... millisecondes, alors que la vidéo veut afficher une image toutes les 41,666... millisecondes. Or, 16.666... et 41.666... n'ont pas de diviseur entier commun : une image de film d'affiche tous les 2,5 images d'écran. Concrètement, écran et film sont désynchronisés. Si cela ne pose pas trop de problèmes sans la synchronisation verticale, cela en pose avec. Une image sur deux est décalée en termes de timings avec la synchronisation verticale, ce qui donne un effet bizarre, bien que léger, lors du visionnage sur un écran d'ordinateur. Le même problème survient dans les jeux vidéos, qui ont un nombre d'images par seconde très variable. Ces différences de timings entraînent des sauts d'images quand un jeu vidéo calcule moins d'images par seconde que ce que peut accepter l'écran, ce qui donne une impression désagréable appelée le stuttering.
Pour résumer, les problèmes de la vsync sont liés à deux choses : le nombre de FPS n'est pas nécessairement synchronisé avec le rafraichissement de l'écran, et le déphasage entre ordinateur et écran se ressent.
Le triple buffering et ses dérivés
[modifier | modifier le wikicode]Diverses solutions existent pour éliminer ces problèmes, et elles sont assez nombreuses. La première solution ajoute un troisième tampon d'affichage, ce qui donne la technique du triple buffering. L'utilité est de réduire le délai ajouté par la synchronisation verticale : utiliser le triple buffering sans synchronisation verticale n'a aucun sens. L'idée est que l'ordinateur peut calculer une seconde image d'avance. Ainsi, si l'écran affiche l'image n°1, une image n°2 est terminée mais en attente, et une image n°3 est en cours de calcul.

Le délai lié à la synchronisation verticale est réduit dans le cas où les FPS sont vraiment bas comparé à la fréquence d'affichage de l'écran, par exemple si on tourne à 40 images par secondes sur un écran à 60 Hz, du fait de l'image calculée en avance. Dans le cas où les FPS sont (temporairement) plus élevés que la fréquence d'affichage de l'écran, la troisième image finit son calcul avant que la seconde soit affichée. Dans ce cas, la seconde image est affichée avant la troisième. Il n'y a pas d'image supprimée ou abandonnée, peu importe la situation.
Les améliorations de la synchronisation verticale
[modifier | modifier le wikicode]La technologie Fast Sync sur les cartes graphiques NVIDIA est une amélioration du triple buffering, qui se préoccupe du cas où les FPS sont (temporairement) plus élevés que la fréquence d'affichage de l'écran. Dans ce cas, avec le triple buffering simple, aucune image n'est abandonnée : on a deux images en attente, dont l'une est plus récente que l'autre. La technologie fast sync élimine la première image en attente et de la remplacer par la seconde, plus récente. L'avantage est que le délai d'affichage d'une image est réduit, le temps d'attente lié à la synchronisation verticale étant réduit au strict minimum.
Une autre solution est la synchronisation verticale adaptative, qui consiste à désactiver la synchronisation verticale quand le nombre d'images par seconde descend sous la fréquence de rafraîchissement de l'écran. Le principe est simple, mais il s'agit pourtant d'une technologie assez récente, introduite en 2016 sur les cartes NVIDIA. Notons qu'on peut combiner cette technologie avec la technologie fast sync : cette dernière fonctionne quand les FPS dépassent la fréquence de rafraîchissement de l'écran, alors que la vsync adaptative fonctionne quand les FPS sont trop bas. C'est utile si les FPS sont très variables.
Une dernière possibilité est d'utiliser des technologies qui permettent à l'écran et la carte graphique d'utiliser une fréquence de rafraîchissement variable. La fréquence de rafraîchissement de l'écran s'adapte en temps réel à celle de la carte graphique. En clair, l'écran démarre l'affichage d'une nouvelle image quand la carte graphique le lui demande, pas à intervalle régulier. Évidemment, l'écran a une limite physique et ne peut pas toujours suivre la carte graphique. Dans ce cas, la carte graphique limite les FPS au maximum de ce que peut l'écran. Les premières technologies de ce type étaient le Gsync de NVIDIA et le Free Sync d'AMD, qui ont été suivies par les standards AdaptiveSync et MediaSync.
Les VDC des systèmes à framebuffer : les CRTC
[modifier | modifier le wikicode]Afficher une image à l'écran demande de prendre l'image dans le framebuffer et de l'envoyer à l'écran pixel par pixel. Pour cela, le VDC doit parcourir le framebuffer pour lire les pixels un par un, dans le bon ordre. Il existe des VDC qui sont capables de lire les pixels à envoyer à l'écran depuis la mémoire vidéo. Ils sont appelés 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.
Pour résumer ce que fait un CRTC, les pixels sont lus les uns après les autres, ligne par ligne, en balayant le framebuffer. Pour cela, ils génèrent l'adresse du pixel à lire, au rythme d'un pixel par cycle d'horloge. La génération d'adresse est assez simple si le framebuffer est coopératif. Il suffit de démarrer à une adresse bien précise, celle où commence le framebuffer, et parcourir la mémoire dans l'ordre, en passant à l'adresse suivante à chaque cycle. Un simple compteur fait l'affaire. Pour cela, il utilise les deux compteurs de ligne et colonne pour forger l'adresse mémoire adéquate à chaque cycle.

Une carte d'affichage se résume donc à un CRTC, une mémoire vidéo pour le framebuffer, une mémoire SRAM pour la palette indicée, et éventuellement un convertisseur digital-analogique (DAC) sur les anciennes cartes d’affichage. Il faut évidemment ajouter un circuit de communication avec le bus, ainsi qu'une interface écran, pour compléter le tout.

Le pointeur de framebuffer
[modifier | modifier le wikicode]
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.

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
[modifier | modifier le wikicode]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.
Le mode écran divisé (splitscreen)
[modifier | modifier le wikicode]L'affichage en splitscreen subdivise l'écran en rectangles qui affichent chacun une image différente. Les anciens jeux vidéo en faisaient usage pour le mode multijoueur local, où plusieurs manettes de jeu étaient reliées à une seule console de jeu, elle-même reliée à un seul écran. Pour un affrontement entre deux joueurs, l'écran était découpé en deux : la moitié haute de l'écran pour le premier joueur, la moitié basse pour le second joueur. Avec quatre joueurs, l'écran était découpé en quatre écrans. La console de jeu devait calculer 2 à 4 images, suivant le nombre de joueurs, et les combiner dans le framebuffer. Chaque image avait une résolution deux à quatre fois plus basse, ce qui fait que la console avait assez de puissance de calcul pour les calculer.

Implémenter un affichage splitscreen se fait généralement sans support matériel. La console de jeu n'a pas grand-chose à faire pour le supporter, ce sont les jeux vidéos qui sont programmés pour effectuer un rendu de ce type. L'idée est de rendre plusieurs images de résolution inférieure et de les combiner pour obtenir le framebuffer final. Mais quelques cartes d'affichage implémentent des optimisations pour le splitscreen . Les optimisations ne permettent pas vraiment d'avoir un rendu plus rapide, du moins ce n'est pas leur objectif. Elles visent à rendre l'affichage splitscreen plus simple pour, le programmeur.
Le splitscreen horizontal
[modifier | modifier le wikicode]L'optimisation la plus intuitive ne marche que pour un affichage divisé simple : l'écran est coupé en deux à l'horizontale, avec une moitié haute et une moitié basse. L'écran n'est pas forcément coupé en deux parties égales, la ligne de démarcation entre les deux écrans est configurable. Prenons l'exemple d'une résolution de 320 par 200 pixels, soit 200 pixels de haut. Le premier écran pouvait, par exemple, faire 56 pixels de hauteur, le second 144 pixels. Un tel splitscreen est appelé un splitscreen horizontal.
L'implémentation réutilise les registres du CRTC vus dans la section précédente : le compteur de ligne, le compteur de colonne, et les deux registres pour l'adresse de base. L'implémentation utilise deux framebuffers : un pour la moitié haute de l'écran, un pour la moitié basse. La ligne de démarcation est configurable grâce à un registre dédié, appelé le registre de splitscreen. Le programmeur y écrit à quelle ligne se trouve la ligne de démarcation, son numéro de ligne. Quand le compteur de ligne et le registre de splitscreen sont égaux, c'est signe qu'il faut changer d'écran. A ce moment là, le parcours du framebuffer reprend à l'adresse de base. Le changement demande de simplement changer de registre d'adresse de base, mais aussi de réinitialiser les compteurs de ligne et de colonne (pour repartir à zéro).
Sans registre de splitscreen, le splitscreen horizontal est implémenté avec des raster interrupts qui seront détaillés plus loin. Et il est possible de combiner raster interrupts et registre de splitscreen. Imaginez que vous vouliez découper un écran en plusieurs bandeaux horizontaux distincts. Un registre de splitscreen permet de couper l'écran en deux, mais pas en trois ou autre. Intuitivement, on se dit qu'il faudrait rajouter d'autres registres de splitscreen, mais cela aurait un cout en circuits pour une utilisation très rare. Une autre solution est de changer le contenu du registre de splitscreen lors de l'affichage, grâce à des raster interrupts.
L'idée part d'un écran initialement coupé en deux. Le passage à la seconde partie de l'écran déclenche une raster interrupt. Le processeur modifie alors les registres du CRTC, pour gérer l'affichage de la troisième portion de l'image. Le registre d'adresse de base, pointant initialement sur la première partie de l'image, pointe alors sur la troisième partie. Le registre de splitscreen est modifié de manière à pointer sur la seconde ligne de démarcation, celle entre la seconde partie de l'image et la troisième. Ainsi, lorsque la seconde ligne de démarcation sera atteinte, le hardware commutant d'image, et affichera automatiquement la troisième image. Supporter une quatrième image demande d'utiliser une autre raster interrupt lors du passage à la troisième partie de l'image, et ainsi de suite.





