Wikilivres
frwikibooks
https://fr.wikibooks.org/wiki/Accueil
MediaWiki 1.47.0-wmf.23
first-letter
Média
Spécial
Discussion
Utilisateur
Discussion utilisateur
Wikilivres
Discussion Wikilivres
Fichier
Discussion fichier
MediaWiki
Discussion MediaWiki
Modèle
Discussion modèle
Aide
Discussion aide
Catégorie
Discussion catégorie
Transwiki
Discussion Transwiki
Wikijunior
Discussion Wikijunior
TimedText
TimedText talk
Module
Discussion module
Event
Event talk
Formation musicale
0
3408
774467
749242
2026-10-08T15:05:41Z
Cdang
1202
/* Voir aussi */ +1
774467
wikitext
text/x-wiki
{{Page de garde|image=Colormusik Print Detail.jpg|description=
La formation musicale est l'étude des éléments permettant d'interpréter un morceau de musique, et inclut solfège, oreille, sens du rythme, travail collectif, mais aussi culture générale.
L'étude inclut la musique écrite. Elle n'est pas indispensable à la pratique de la musique ; elle permet toutefois de faciliter à la fois la lecture de partitions de musique et ainsi interpréter un morceau inconnu. C'est aussi un moyen de transmission de la musique aux interprètes, une forme de « communication technique ».
|avancement=Avancé
|cdu=
* {{CDU item|7|78}}
}}
Le '''[[w:Solfège|solfège]]''' est l'étude des éléments permettant d'interpréter un morceau de musique.
Le solfège n'est pas ''indispensable'' à la pratique de la musique : de nombreux musiciens, amateurs comme professionnel, jouent « à l'oreille » et apprennent la musique par mimétisme. La musique est d'abord une tradition orale. Toutefois, le solfège fournit un cadre qui facilite :
* l'interprétation d'un morceau que l'on ne connaît pas, en déchiffrant : un musicien peut ainsi interpréter un morceau qu'il ne connaît pas à partir de la seule partition ; la musique écrite est donc un moyen simple, économique et robuste de transmettre et de conserver la musique ;
* la préparation d'un concert : un orchestre de plusieurs dizaines de musiciens peut monter un concert d'une heure avec seulement quatre répétitions en s'appuyant sur les partitions ;
* la composition : si un auteur de livre peut se contenter d'enregistrer sa voix (principe du dictaphone), le passage par l'écriture permet de reprendre des passages et de mettre en cohérence les différentes parties du livre ; il en est de même pour la musique.
En France, le terme de « solfège » a été remplacé par celui de '''formation musicale (FM)'''. En effet, il s'agit également de former de manière globale le musicien — oreille, sens du rythme, travail collectif, mais aussi culture générale.
Nous nous intéressons essentiellement à la musique « occidentale » : musiques savantes et populaires du nord-ouest de l'Europe et des États-Unis d'Amérique, dont les musiques dites « classique », « jazz », « pop », « rock ». L'accent est mis sur la musique dite « classique » car c'est pour elle que le formalisme présenté a été développé.
== Table des matières ==
# [[/Qu'est-ce que la musique ?|Qu'est-ce que la musique ?]]
# [[/Transmettre la musique|Transmettre la musique]]
# [[/Caractéristiques et notation des sons musicaux|Caractéristiques et notation des sons musicaux]]
# [[/Gammes et intervalles|Gammes et intervalles]]
# [[/Mélodie|Mélodie]]
# [[/Harmonie|Harmonie]]
# [[/Représentation musicale|Représentation musicale]]
# [[/Forme d'une œuvre|Forme d'une œuvre]]
# [[/Analyse d'une œuvre|Analyse d'une œuvre]]
# [[/Culture générale musicale|Culture générale musicale]]
# Annexes
## [[/Bibliographie et liens|Bibliographie et liens]]
## [[/Index|Index]]
== Voir aussi ==
; Sur la wikiversité
* ''[[wikiversity:fr:Accord|Accord]]''
* ''[[wikiversity:fr:Gamme|Gamme]]''
* ''[[wikiversity:fr:Intervalle, accord|Intervalle, accord]]''
* ''[[wikiversity:fr:Introduction au solfège|Introduction au solfège]]''
* ''[[wikiversity:fr:Lecture de partitions|Lecture de partitions]]''
* ''[[wikiversity:fr:Solfège pour débutants|Solfège pour débutants]]''
; Sur Wikisource
* Adolphe Danhauser, ''[[wikisource:fr:Théorie_de_la_musique_(Danhauser,_1889)|Théorie de la musique]]'' (1889)
* Augustin d'Hippone dit « saint Augustin », ''[[wikisource:fr:Traité_de_la_musique|Traité de la musique]]'' (env. 389, 1864 pour la traduction)
* Marie Bobillier (sous le pseudonyme Michel Brenet), ''[[wikisource:fr:Dictionnaire_pratique_et_historique_de_la_musique|Dictionnaire pratique et historique de la musique ]]'' (1926)
; Autre
* {{lien web |url=https://www.youtube.com/watch?v=lZ9y5f5u21E |auteur=Karambolage en français - ARTE |titre=Le solfège, un mal pour un bien ? |site=YouTube |date=2026-04-27 |consulté le=202610-08}}
[[Catégorie:Musique]]
[[Catégorie:Livres en cours de rédaction]]
[[Catégorie:Formation musicale (livre)|*]]
1nflg32ur4t8xkce5giv3b1amucn0ic
Les cartes graphiques/Les cartes d'affichage
0
67388
774446
765161
2026-10-08T12:47:13Z
Mewtow
31375
/* Un PC avec plusieurs GPU : la commutation de GPU */
774446
wikitext
text/x-wiki
Les cartes graphiques sont des cartes qui communiquent avec l'écran, pour y afficher des images. Les cartes graphiques modernes incorporent aussi des circuits de calcul pour accélérer du rendu 2D ou 3D. Dans ce chapitre, nous allons faire une introduction et expliquer ce qu'est une carte graphique et surtout : nous allons voir ce qu'il y a à l'intérieur, du moins dans les grandes lignes.
==Les cartes graphiques dédiées, intégrées et soudées==
Vous avez sans doute déjà démonté votre PC pour en changer la carte graphique, vous savez sans doute à quoi elle ressemble. Sur les PC modernes, il s'agit d'un composant séparé, qu'on branche sur la carte mère, sur un port spécialisé. Du moins, c'est le cas si vous avez un PC fixe assez puissant. Mais il y a deux autres possibilités.
<noinclude>[[File:PX7800 GTX (557505323).jpg|centre|vignette|Carte graphique dédiée PX 7800 GTX I]]</noinclude>
La première est celle où la carte graphique est directement intégrée dans le processeur de la machine ! C'est quelque chose qui se fait depuis les années 2000-2010, avec l'amélioration de la technologie et la miniaturisation des transistors. Il est possible de mettre tellement de transistors sur une puce de silicium que les concepteurs de processeur en ont profité pour mettre une carte graphique peut puissante dans le processeur.
Une autre possibilité, surtout utilisée sur les consoles de jeu et les PC portables, est celle où la carte graphique est composée de circuits soudés à la carte mère.
Pour résumer, il faut distinguer trois types de cartes graphiques différentes :
* Les '''cartes graphiques dédiées''', séparées dans une carte d'extension qu'on doit connecter à la carte mère via un connecteur dédié.
* Les '''cartes graphiques intégrées''', qui font partie du processeur.
* Les '''cartes graphiques soudées''' à la carte mère.
Vous avez sans doute vu qu'il y a une grande différence de performance entre une carte graphique dédiée et une carte graphique intégrée. La raison est simplement que les cartes graphiques intégrées ont moins de transistors à leur disposition, ce qui fait qu'elles contiennent moins de circuits de calcul. Les cartes graphiques dédiées et soudées n'ont pas de différences de performances notables. Les cartes soudées des PC portables sont généralement moins performantes car il faut éviter que le PC chauffe trop, vu que la dissipation thermique est moins bonne avec un PC portable (moins de gros ventilos), ce qui demande d'utiliser une carte graphique moins puissante. Mais les cartes soudées des consoles de jeu n'ont pas ce problème : elles sont dans un boitier bien ventilés, on peut en utiliser une très puissante.
===Un PC avec plusieurs GPU : la commutation de GPU===
De nos jours, il y a de très fortes chances que votre ordinateur intègre plusieurs cartes graphiques, peu importe que ce soit un PC portable ou fixe. Le processeur de votre ordinateur intègre une carte graphique intégrée, de faible performance, qui consomme peu d'énergie/électricité. Sauf pour quelques rares modèles de processeurs (les modèles Intel avec un suffixe F à la fin de leur nom). Le marché du processeur grand public est ainsi, seuls quelques processeurs dédiés aux serveurs n'ont pas de carte graphique intégrée. Et en plus de la carte intégrée, une bonne partie des PC intègrent aussi soit une carte dédiée, soit une carte soudée. Soudée sur les PC portables, dédiée sur les PC fixe.
Dans le passé, il était possible de mettre plusieurs cartes graphiques dédiées dans un même PC, mais avec des conditions drastiques. ATI/AMD et NVIDIA avaient ajouté des fonctionnalités de multi-GPU, qui permettaient à deux GPU de travailler ensemble, afin de presque doubler les performances. Mais cela ne marchait qu'avec deux GPU NVIDIA ou deux GPU ATI/AMD, utiliser deux GPU de deux marques différentes ne marchait pas. Un chapitre entier sera dédié à ces techniques, mais nous n'en parlerons pas ici, car elles sont tombées en désuétude, aucun GPU grand public ne supporte ces technologies.
S'il y a deux cartes graphiques, cela ne signifie pas que les deux sont utilisées en même temps. En effet, selon les circonstances, le PC va privilégier l'une ou l'autre. Dans les années 2010, le choix se faisait dans le BIOS : une des deux carte graphique était désactivée pour de bon, typiquement la carte intégrée. Les PC avec une carte dédiée désactivaient la carte intégrée dans le processeur, pour éviter tout conflit entre les deux cartes.
De nos jours, les deux sont utilisables pour du rendu 3D, mais pas en même temps. Le système d'exploitation, Windows ou linux, utilise soit la carte intégrée, soit la carte dédiée, suivant les besoins. La carte dédiée a de bonnes performance, mais elle consomme beaucoup d'énergie/électricité et chauffe plus. La carte graphique intégrée fait l'inverse : ses performances sont basses, mais elle consomme très peu et chauffe moins. La carte dédiée est donc utilisée quand on a besoin de performance, l'intégrée est utilisée quand elle suffit, afin de faire des économies. Prenons l'exemple d'un jeu vidéo : un jeu ancien et peu gourmand sera exécuté sur la carte intégrée, alors qu'un jeu récent/gourmand sera exécuté sur la carte dédiée. Le rendu du bureau de Windows/linux est réalisé par la carte graphique intégrée, pour économiser de l'énergie.
===La connexion des cartes graphiques à l'écran===
Prenons un PC fixe avec deux cartes graphiques, une intégrée et une dédiée. En général, il y a deux connecteurs pour l'écran, un qui est relié à la carte graphique intégrée, un autre qui est sur la carte dédiée proprement dite. Suivant là où vous brancherez l'écran, vous n'utiliserez pas la même carte graphique. Le système d'exploitation se charge d'envoyer les images à afficher à la carte graphique adéquate.
Sur un PC portable ''gaming'', les choses sont différentes. Il n'y a qu'un seul connecteur pour l'écran, pas deux. Et dans ce cas, il y a deux possibilités.
La première est la plus simple. Les deux cartes graphiques sont reliées au connecteur écran, par l'intermédiaire d'un circuit multiplexeur. Le circuit multiplexeur reçoit les images à afficher de la part des deux cartes graphiques et choisit l'une d'entre elle. C'est la solution la plus performante, car la carte dédiée peut afficher directement ses images à l'écran, sans avoir à les envoyer à la carte intégrée. Mais elle complexifie le câblage et demande d'ajouter un circuit multiplexeur, ce qui n'est pas gratuit.
[[File:Commutation de GPU avec un MUX.png|centre|vignette|upright=2|Commutation de GPU avec un MUX]]
Avec la seconde solution, une seule carte graphique est connectée à l'écran, généralement la carte intégrée. Si la carte dédiée est utilisée, les images qu'elle calcule sont envoyées à la carte intégrée pour ensuite être affichées à l'écran. On passe par un intermédiaire, mais le câblage est plus simple.
[[File:Commutation de GPU sans MUX.png|centre|vignette|upright=2|Commutation de GPU sans MUX]]
==Les cartes d'affichage et leur architecture==
Vous vous demandez comment est-ce possible qu'une carte graphique soit soudée ou intégrée dans un processeur. La raison est que les trois types de cartes graphiques sont très similaires, elles sont composées des mêmes types de composants, ce qu'il y a à l'intérieur est globalement le même, comme on va le voir dans ce qui suit.
Au tout début de l'informatique, le rendu graphique était pris en charge par le processeur. Il calculait l'image à afficher et l'envoyait à l'écran, pixel par pixel. Le problème est que le processeur devait se synchroniser avec l'écran, pour envoyer les pixels au bon moment. Pour simplifier la vie des programmeurs, les fabricants de matériel ont inventé des cartes vidéo. Avec celles-ci, le processeur calcule l'image à envoyer à l'écran et la transmet à la carte d'affichage, sans avoir à se synchroniser avec l'écran. L'avantage est que le processeur n'a pas à se synchroniser avec l'écran, juste à envoyer l'image à une carte d'affichage.
Les cartes d'affichage ne géraient pas le rendu 3D. Le processeur calculait une image, la copiait dans la mémoire vidéo, puis la carte d'affichage l'envoyait à l'écran au bon moment. Il n'y avait pas de circuits de calcul graphique, ni de circuits de décodage vidéo. Juste de quoi afficher une image à l'écran. Et mine de rien, il est intéressant d'étudier de telles cartes graphiques anciennes. De telles cartes graphiques sont ce que j'ai décidé d'appeler des '''cartes d'affichage'''.
===L'intérieur d'une carte d'affichage===
Une carte d'affichage contient plusieurs sous-circuits, chacun dédié à une fonction précise.
* La '''mémoire vidéo''' est une mémoire RAM intégrée à la carte graphique, qui a des fonctions multiples.
* L''''interface écran''', ou ''Display interface'', regroupe les connecteurs et tous les circuits permettant d'envoyer l'image à l'écran.
* Le '''circuit d'interface avec le bus''' existe uniquement sur les cartes dédiées et éventuellement sur quelques cartes soudées. Il s'occupe des transmissions sur le bus PCI/AGP/PCI-Express, le connecteur qui relie la carte mère et la carte graphique.
* Un circuit de contrôle qui commande le tout, appelé le '''''Video Display Controler'''''.
[[File:Carte d'affichage - architecture.png|centre|vignette|upright=2|Carte d'affichage - architecture.]]
La mémoire vidéo mémorise l'image à afficher, les deux circuits d'interfaçage permettent à la carte d'affichage de communiquer respectivement avec l'écran et le reste de l'ordinateur, le ''Video Display Controler'' commande les autres circuits. Le ''Video Display Controler'' sert de chef d'orchestre pour un orchestre dont les autres circuits seraient les musiciens. Le circuit de contrôle était appelé autrefois le CRTC, car il commandait des écrans dit CRT, mais ce n'est plus d'actualité de nos jours.
La carte graphique communique via un bus, un vulgaire tas de fils qui connectent la carte graphique à la carte mère. Les premières cartes graphiques utilisaient un bus nommé ISA, qui fût rapidement remplacé par le bus PCI, plus rapide, lui-même remplacé par le bus AGP, puis le bus PCI-Express. Ce bus est géré par un contrôleur de bus, un circuit qui se charge d'envoyer ou de réceptionner les données sur le bus. Les circuits de communication avec le bus permettent à l'ordinateur de communiquer avec la carte graphique, via le bus PCI-Express, AGP, PCI ou autre. Il contient quelques registres dans lesquels le processeur pourra écrire ou lire, afin de lui envoyer des ordres du style : j'envoie une donnée, transmission terminée, je ne suis pas prêt à recevoir les données que tu veux m'envoyer, etc. Il y a peu à dire sur ce circuit, aussi nous allons nous concentrer sur les autres circuits.
Le circuit d'interfaçage écran est au minimum un circuit d’interfaçage électrique se contente de convertir les signaux de la carte graphique en signaux que l'on peut envoyer à l'écran. Il s'occupe notamment de convertir les tensions et courants : si l'écran demande des signaux de 5 Volts mais que la carte graphique fonctionne avec du 3,3 Volt, il y a une conversion à faire. De même, le circuit d'interfaçage électrique peut s'occuper de la conversion des signaux numériques vers de l'analogique. L'écran peut avoir une entrée analogique, surtout s'il est assez ancien.
Les anciens écrans CRT ne comprenaient que des données analogiques et pas le binaire, alors que c'est l'inverse pour la carte graphique, ce qui fait que le circuit d'interfaçage devait faire la conversion. La conversion était réalisée par un circuit qui traduit des données numériques (ici, du binaire) en données analogiques : le '''convertisseur numérique-analogique''' ou DAC (''Digital-to-Analogue Converter''). Au tout début, le circuit d’interfaçage était un DAC combiné avec des circuits annexes, ce qu'on appelle un RAMDAC (''Random Access Memory Digital-to-Analog Converter''). De nos jours, les écrans comprennent le binaire sous réserve qu'il soit codé suivant le standard adapté et les cartes graphiques n'ont plus besoin de RAMDAC.
Il y a peu à dire sur les circuits d'interfaçage. Leur conception et leur fonctionnement dépendent beaucoup du standard utilisé. Sans compter qu'expliquer leur fonctionnement demande de faire de l'électronique pure et dure, ce qui est rarement agréable pour le commun des mortels. Par contre, étudier le circuit de contrôle et la mémoire vidéo est beaucoup plus intéressant. Plusieurs chapitres seront dédiés à leur fonctionnement. Mais parlons maintenant des GPU modernes et passons à la section suivante.
===Un historique rapide des cartes d’affichage===
Les cartes d'affichages sont opposées aux cartes accélératrices 2D et 3D, qui permettent de décharger le processeur d'une partie du rendu 2D/3D. Pour cela, elles intègrent des circuits spécialisés. Vous imaginez peut-être que les cartes d'affichage sont apparues en premier, puis qu'elles ont gagné en puissance et en fonctionnalités pour devenir d'abord des cartes accélératrices 2D, puis des cartes 3D. C'est une suite assez logique, intuitive. Et ce n'est pas du tout ce qui s'est passé !
Les cartes d'affichage pures, sans rendu 2D, sont une invention des premiers PC. Elles sont arrivées alors que les consoles de jeu avaient déjà des cartes hybrides entre carte d'affichage et cartes de rendu 2D depuis une bonne décennie. Sur les consoles de jeu ou les microordinateurs anciens, il n'y avait pas de cartes d'affichage séparée. À la place, le système vidéo d'un ordinateur était un ensemble de circuits soudés sur la carte mère.
Les consoles de jeu, ainsi que les premiers micro-ordinateurs, avaient une configuration fixée une fois pour toute et n'étaient pas upgradables. Mais avec l'arrivée de l'IBM PC, les cartes d’affichages se sont séparées de la carte mère. Leurs composants étaient soudés sur une carte qu'on pouvait clipser et détacher de la carte mère si besoin. Et c'est ainsi que l'on peut actuellement changer la carte graphique d'un PC, alors que ce n'est pas le cas sur une console de jeu.
La différence entre les deux se limite cependant à cela. Les composant d'une carte d'affichage ou d'une console de jeu sont globalement les mêmes. Aussi, dans ce qui suit, nous parlerons de carte d'affichage pour désigner cet ensemble de circuits, peu importe qu'il soit soudé à la carte mère ou placé sur une carte d’affichage séparée. C'est un abus de langage qu'on ne retrouvera que dans ce cours.
===Les différents types de cartes d'affichage===
Dans la suite du cours, nous allons voir que toutes les cartes d'affichage ne fonctionnent pas de la même manière. Et ces différences font qu'on peut les classer en plusieurs types distincts. Leur classement s'explique par un fait assez simple : une image prend beaucoup de mémoire ! Par exemple, prenons le cas d'une image en niveaux de gris d'une résolution de 320 par 240 pixels, chaque pixel étant codé sur un octet. L'image prend alors 76800 octets, soit environ 76 kiloctets. Mine de rien, cela fait beaucoup de mémoire ! Et si on ajoute le support de la couleur, cela triple, voire quadruple la taille de l'image.
Les cartes graphiques récentes ont assez de mémoire pour stocker l'image à afficher. Une partie de la mémoire vidéo est utilisée pour mémoriser l'image à afficher. La portion en question s'appelle le ''framebuffer'', '''tampon d'image''' en français. Il s'agit là d'une solution très simple, mais qui demande une mémoire vidéo de grande taille. Les systèmes récents peuvent se le permettre, mais les tout premiers ordinateurs n'avaient pas assez de mémoire vidéo. Les cartes d'affichages devaient se débrouiller avec peu de mémoire, impossible de mémoriser l'image à afficher entièrement.
Pour compenser cela, les cartes d'affichage anciennes utilisaient diverses optimisations assez intéressantes. La première d'entre elle utilise pour cela le fonctionnement des anciens écrans CRT, qui affichaient l'image ligne par ligne. Pour rappel, l'image a afficher à l'écran a une certaine résolution : 320 pixels pour 240 pixels, par exemple. Pour l'écran CRT, l'image est composée de plusieurs lignes. Par exemple, pour une résolution de 640 par 480, l'image est découpée en 480 lignes, chacune faisant 640 pixels de long. L'écran est conçu pour qu'on lui envoie les lignes les unes après les autres, avec une petite pause entre l'affichage/envoi de deux lignes. Précisons que les écrans LCD ont abandonné ce mode de fonctionnement.
L'idée est alors la suivante : la mémoire vidéo ne mémorise que la ligne en cours d'affichage par l'écran. Le processeur met à jour la mémoire vidéo entre l'affichage de deux lignes. La mémoire vidéo n'est alors pas un tampon d'image, mais un '''tampon de ligne'''. Le défaut de cette technique est qu'elle demande que le processeur et la carte d'affichage soient synchronisés, de manière à ce que les lignes soient mises à jour au bon moment. L'avantage est que la quantité de mémoire vidéo nécessaire est divisée par un facteur 100, voire plus, égal à la résolution verticale (le nombre de lignes).
Une autre méthode, appelée le '''rendu en tiles''', est formellement une technique de compression d'image particulière. L'image à afficher est stockée sous un format compressé en mémoire vidéo, mais est décompressée pixel par pixel lors de l'affichage. Il nous est difficile de décrire cette technique maintenant, mais un chapitre entier sera dédié à cette technique. Le chapitre en question abordera une technique similaire, appelée le rendu en mode texte, qui servira d'introduction propédeutique.
Le rendu en tiles et l'usage d'un tampon ligne sont deux optimisations complémentaires. Il est ainsi possible d'utiliser soit l'une, soit l'autre, soit les deux. En clair, cela donne quatre types de cartes d'affichage distincts :
* les cartes d'affichage à tampon d'image ;
* les cartes à tampon d'images en rendu à tiles ;
* les cartes d'affichage à tampon de ligne ;
* les cartes d'affichage à tampon de ligne en rendu à tiles.
{|class="wikitable"
|-
!
! Rendu normal
! Rendu à tile
|-
! Tampon d'image
| Cartes graphiques post-années 90, standard VGA sur PC
| Consoles de jeu 8 bits et 16 bits, standards CGA, MGA
|-
! Tampon de ligne
| Consoles de jeu ATARI 2600 et postérieures
| Console de jeu néo-géo
|}
Lez prochains chapitres porteront surtout sur les cartes d'affichages à tampon d'image, plus simples à, expliquer. Deux chapitres seront dédiés respectivement aux cartes à rendu en tile, et aux cartes à tampon de ligne.
==Les cartes graphiques actuelles sont très complexes==
Les cartes graphiques actuelles sont des cartes d'affichage améliorées auxquelles on a ajouté des circuits annexes, afin de leur donner des capacités de calcul pour le rendu 2D et/ou 3D, mais elles n'en restent pas moins des cartes d'affichages. La seule différence est que le processeur n’envoie pas une image à la mémoire vidéo, mais que l'image à afficher est calculée par la carte graphique 2D/3D. Les calculs autrefois effectués sur le CPU sont donc déportés sur la carte graphique.
Si vous analysez une carte graphique récente, vous verrez que les circuits des cartes d'affichage sont toujours là, bien que noyés dans des circuits de rendu 2D/3D. On retrouve une mémoire vidéo, une interface écran, un circuit d'interface avec le bus, un ''Video Display Controler''. L'interface écran est par contre fusionnée avec le ''Video Display Controler''. Mais à ces circuits d'affichage, sont ajoutés un '''GPU''' (''Graphic Processing Unit''), qui s'occupe du rendu 3D, du rendu 2D, mais aussi d'autres fonctions comme du décodage de fichiers vidéos.
[[File:Architecture d'une carte graphique avec un GPU.png|centre|vignette|upright=2|Architecture d'une carte graphique avec un GPU]]
===L'intérieur d'un GPU===
Le GPU est un composant assez complexe, surtout sur les cartes graphiques modernes. Il regroupe plusieurs circuits aux fonctions distinctes.
* Un '''''Video Display controler''''' est toujours présent, mais ne sera pas représenté dans ce qui suit.
* Des '''circuit de rendu 2D''' séparés peuvent être présents, mais sont la plupart du temps intégrés au VDC.
* Les '''circuits de rendu 3D''' s'occupent du rendu 3D.
* Les '''circuits de décodage vidéo''', pour améliorer la performance du visionnage de vidéos.
Les trois circuits précédents sont gouvernés par un '''processeur de commande''', un circuit assez difficile à décrire. Pour le moment, disons qu'il s'occupe de répartir le travail entre les différents circuits précédents. Le processeur de commande reçoit du travail à faire, envoyé par le CPU via le bus, et le redistribue dans les trois circuits précédents. Par exemple, si on lui envoie une vidéo encodée en H264, il envoie le tout au circuit vidéo. De plus, il le configure pour qu'il la décode correctement : il dit au circuit que c'est une vidéo encodée en H264, afin que le circuit sache comment la décoder. Ce genre de configuration est aussi présente sur les circuits de rendu 2D et 3D, bien qu'elle soit plus compliquée.
[[File:Microarchitecture simplifiée d'un GPU.png|centre|vignette|upright=2|Microarchitecture simplifiée d'un GPU]]
Un GPU contient aussi un contrôleur mémoire, qui est une interface entre le GPU et la mémoire vidéo. Interface électrique, car GPU et mémoire n'utilisent pas les mêmes tensions et qu'en plus, leur interconnexion demande de gérer pas mal de détails purement électriques. Mais aussi interface de communication, car communiquer entre mémoire vidéo et GPU demande de faire pas mal de manipulations, comme gérer l'adressage et bien d'autres choses qu'on ne peut pas encore détailler ici.
Un GPU contient aussi d'autres circuits annexes, comme des circuits pour gérer la consommation électrique, un BIOS vidéo, etc. L'interface écran et l'interface avec le bus sont parfois intégrées au GPU.
===Un historique simplifié des pipelines 2D/3D/vidéo===
Avant les années 90, les cartes graphiques des ordinateurs personnels et des consoles de jeux se résumaient à des circuits d'accélération 2D, elles n'avaient pas de décodeurs vidéos ou de circuits de rendu 3D. C'est dans les années 90 qu'elles ont commencé à incorporer des circuits d'accélération 3D. Puis, après les années 2000-2010, elles ont commencé à intégrer des circuits de décodage vidéo, aux alentours des années 2010. En parallèle, les circuits de rendu 2D ont progressivement été réduits puis abandonnés.
Les circuits de rendu 2D sont des circuits qui ne sont pas programmables, mais qu'on peut configurer. On dit aussi que ce sont des '''circuits fixes'''. Ils sont opposés aux circuits programmables, basés sur des processeurs. Les circuits de rendu 3D étaient initialement des circuits fixes eux aussi, mais sont devenus de plus en plus programmables avec le temps. Les GPU d'après les années 2000 utilisent un mélange de circuits programmables et fixes assez variable. Nous reviendrons là-dessus dans un chapitre dédié.
[[File:Microarchitecture globale d'un GPU.png|centre|vignette|upright=2|Microarchitecture globale d'un GPU]]
Pour les circuits de décodage vidéo, les choses sont assez complexes. Initialement, ils s'agissait de circuits fixes, qu'on pouvait programmer. Un GPU de ce type est illustré ci-dessus. Mais de nos jours, les calculs de décodage vidéo sont réalisés sur les processeurs de shaders. Les circuits de rendu 3D et de décodage vidéo ont chacun des circuits fixes dédiés, mais partagent des processeurs.
[[File:GPU avec processeurs de shaders partagés entre rendu 3D et décodage vidéo.png|centre|vignette|upright=2|GPU avec processeurs de shaders partagés entre rendu 3D et décodage vidéo]]
Les cinq prochains chapitres vont parler des cartes d'affichage et des cartes accélératrices 2D, les deux étant fortement liées. C'est seulement dans les chapitres qui suivront que nous parlerons des cartes 3D. Les cartes 3D sont composées d'une carte d'affichage à laquelle on a rajouté des circuits de calcul, ce qui fait qu'il est préférable de faire ainsi : on voit ce qui est commun entre les deux d'abord, avant de voir le rendu 3D ensuite. De plus, le rendu 3D est plus complexe que l'affichage d'une image à l'écran, ce qui fait qu'il vaut mieux voir cela après.
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| next=Le Video Display Controler
| nextText=Le Video Display Controler
}}{{autocat}}
</noinclude>
dxl1l14qei76idj1temhd6stne5i118
774447
774446
2026-10-08T12:49:40Z
Mewtow
31375
/* Un historique simplifié des pipelines 2D/3D/vidéo */
774447
wikitext
text/x-wiki
Les cartes graphiques sont des cartes qui communiquent avec l'écran, pour y afficher des images. Les cartes graphiques modernes incorporent aussi des circuits de calcul pour accélérer du rendu 2D ou 3D. Dans ce chapitre, nous allons faire une introduction et expliquer ce qu'est une carte graphique et surtout : nous allons voir ce qu'il y a à l'intérieur, du moins dans les grandes lignes.
==Les cartes graphiques dédiées, intégrées et soudées==
Vous avez sans doute déjà démonté votre PC pour en changer la carte graphique, vous savez sans doute à quoi elle ressemble. Sur les PC modernes, il s'agit d'un composant séparé, qu'on branche sur la carte mère, sur un port spécialisé. Du moins, c'est le cas si vous avez un PC fixe assez puissant. Mais il y a deux autres possibilités.
<noinclude>[[File:PX7800 GTX (557505323).jpg|centre|vignette|Carte graphique dédiée PX 7800 GTX I]]</noinclude>
La première est celle où la carte graphique est directement intégrée dans le processeur de la machine ! C'est quelque chose qui se fait depuis les années 2000-2010, avec l'amélioration de la technologie et la miniaturisation des transistors. Il est possible de mettre tellement de transistors sur une puce de silicium que les concepteurs de processeur en ont profité pour mettre une carte graphique peut puissante dans le processeur.
Une autre possibilité, surtout utilisée sur les consoles de jeu et les PC portables, est celle où la carte graphique est composée de circuits soudés à la carte mère.
Pour résumer, il faut distinguer trois types de cartes graphiques différentes :
* Les '''cartes graphiques dédiées''', séparées dans une carte d'extension qu'on doit connecter à la carte mère via un connecteur dédié.
* Les '''cartes graphiques intégrées''', qui font partie du processeur.
* Les '''cartes graphiques soudées''' à la carte mère.
Vous avez sans doute vu qu'il y a une grande différence de performance entre une carte graphique dédiée et une carte graphique intégrée. La raison est simplement que les cartes graphiques intégrées ont moins de transistors à leur disposition, ce qui fait qu'elles contiennent moins de circuits de calcul. Les cartes graphiques dédiées et soudées n'ont pas de différences de performances notables. Les cartes soudées des PC portables sont généralement moins performantes car il faut éviter que le PC chauffe trop, vu que la dissipation thermique est moins bonne avec un PC portable (moins de gros ventilos), ce qui demande d'utiliser une carte graphique moins puissante. Mais les cartes soudées des consoles de jeu n'ont pas ce problème : elles sont dans un boitier bien ventilés, on peut en utiliser une très puissante.
===Un PC avec plusieurs GPU : la commutation de GPU===
De nos jours, il y a de très fortes chances que votre ordinateur intègre plusieurs cartes graphiques, peu importe que ce soit un PC portable ou fixe. Le processeur de votre ordinateur intègre une carte graphique intégrée, de faible performance, qui consomme peu d'énergie/électricité. Sauf pour quelques rares modèles de processeurs (les modèles Intel avec un suffixe F à la fin de leur nom). Le marché du processeur grand public est ainsi, seuls quelques processeurs dédiés aux serveurs n'ont pas de carte graphique intégrée. Et en plus de la carte intégrée, une bonne partie des PC intègrent aussi soit une carte dédiée, soit une carte soudée. Soudée sur les PC portables, dédiée sur les PC fixe.
Dans le passé, il était possible de mettre plusieurs cartes graphiques dédiées dans un même PC, mais avec des conditions drastiques. ATI/AMD et NVIDIA avaient ajouté des fonctionnalités de multi-GPU, qui permettaient à deux GPU de travailler ensemble, afin de presque doubler les performances. Mais cela ne marchait qu'avec deux GPU NVIDIA ou deux GPU ATI/AMD, utiliser deux GPU de deux marques différentes ne marchait pas. Un chapitre entier sera dédié à ces techniques, mais nous n'en parlerons pas ici, car elles sont tombées en désuétude, aucun GPU grand public ne supporte ces technologies.
S'il y a deux cartes graphiques, cela ne signifie pas que les deux sont utilisées en même temps. En effet, selon les circonstances, le PC va privilégier l'une ou l'autre. Dans les années 2010, le choix se faisait dans le BIOS : une des deux carte graphique était désactivée pour de bon, typiquement la carte intégrée. Les PC avec une carte dédiée désactivaient la carte intégrée dans le processeur, pour éviter tout conflit entre les deux cartes.
De nos jours, les deux sont utilisables pour du rendu 3D, mais pas en même temps. Le système d'exploitation, Windows ou linux, utilise soit la carte intégrée, soit la carte dédiée, suivant les besoins. La carte dédiée a de bonnes performance, mais elle consomme beaucoup d'énergie/électricité et chauffe plus. La carte graphique intégrée fait l'inverse : ses performances sont basses, mais elle consomme très peu et chauffe moins. La carte dédiée est donc utilisée quand on a besoin de performance, l'intégrée est utilisée quand elle suffit, afin de faire des économies. Prenons l'exemple d'un jeu vidéo : un jeu ancien et peu gourmand sera exécuté sur la carte intégrée, alors qu'un jeu récent/gourmand sera exécuté sur la carte dédiée. Le rendu du bureau de Windows/linux est réalisé par la carte graphique intégrée, pour économiser de l'énergie.
===La connexion des cartes graphiques à l'écran===
Prenons un PC fixe avec deux cartes graphiques, une intégrée et une dédiée. En général, il y a deux connecteurs pour l'écran, un qui est relié à la carte graphique intégrée, un autre qui est sur la carte dédiée proprement dite. Suivant là où vous brancherez l'écran, vous n'utiliserez pas la même carte graphique. Le système d'exploitation se charge d'envoyer les images à afficher à la carte graphique adéquate.
Sur un PC portable ''gaming'', les choses sont différentes. Il n'y a qu'un seul connecteur pour l'écran, pas deux. Et dans ce cas, il y a deux possibilités.
La première est la plus simple. Les deux cartes graphiques sont reliées au connecteur écran, par l'intermédiaire d'un circuit multiplexeur. Le circuit multiplexeur reçoit les images à afficher de la part des deux cartes graphiques et choisit l'une d'entre elle. C'est la solution la plus performante, car la carte dédiée peut afficher directement ses images à l'écran, sans avoir à les envoyer à la carte intégrée. Mais elle complexifie le câblage et demande d'ajouter un circuit multiplexeur, ce qui n'est pas gratuit.
[[File:Commutation de GPU avec un MUX.png|centre|vignette|upright=2|Commutation de GPU avec un MUX]]
Avec la seconde solution, une seule carte graphique est connectée à l'écran, généralement la carte intégrée. Si la carte dédiée est utilisée, les images qu'elle calcule sont envoyées à la carte intégrée pour ensuite être affichées à l'écran. On passe par un intermédiaire, mais le câblage est plus simple.
[[File:Commutation de GPU sans MUX.png|centre|vignette|upright=2|Commutation de GPU sans MUX]]
==Les cartes d'affichage et leur architecture==
Vous vous demandez comment est-ce possible qu'une carte graphique soit soudée ou intégrée dans un processeur. La raison est que les trois types de cartes graphiques sont très similaires, elles sont composées des mêmes types de composants, ce qu'il y a à l'intérieur est globalement le même, comme on va le voir dans ce qui suit.
Au tout début de l'informatique, le rendu graphique était pris en charge par le processeur. Il calculait l'image à afficher et l'envoyait à l'écran, pixel par pixel. Le problème est que le processeur devait se synchroniser avec l'écran, pour envoyer les pixels au bon moment. Pour simplifier la vie des programmeurs, les fabricants de matériel ont inventé des cartes vidéo. Avec celles-ci, le processeur calcule l'image à envoyer à l'écran et la transmet à la carte d'affichage, sans avoir à se synchroniser avec l'écran. L'avantage est que le processeur n'a pas à se synchroniser avec l'écran, juste à envoyer l'image à une carte d'affichage.
Les cartes d'affichage ne géraient pas le rendu 3D. Le processeur calculait une image, la copiait dans la mémoire vidéo, puis la carte d'affichage l'envoyait à l'écran au bon moment. Il n'y avait pas de circuits de calcul graphique, ni de circuits de décodage vidéo. Juste de quoi afficher une image à l'écran. Et mine de rien, il est intéressant d'étudier de telles cartes graphiques anciennes. De telles cartes graphiques sont ce que j'ai décidé d'appeler des '''cartes d'affichage'''.
===L'intérieur d'une carte d'affichage===
Une carte d'affichage contient plusieurs sous-circuits, chacun dédié à une fonction précise.
* La '''mémoire vidéo''' est une mémoire RAM intégrée à la carte graphique, qui a des fonctions multiples.
* L''''interface écran''', ou ''Display interface'', regroupe les connecteurs et tous les circuits permettant d'envoyer l'image à l'écran.
* Le '''circuit d'interface avec le bus''' existe uniquement sur les cartes dédiées et éventuellement sur quelques cartes soudées. Il s'occupe des transmissions sur le bus PCI/AGP/PCI-Express, le connecteur qui relie la carte mère et la carte graphique.
* Un circuit de contrôle qui commande le tout, appelé le '''''Video Display Controler'''''.
[[File:Carte d'affichage - architecture.png|centre|vignette|upright=2|Carte d'affichage - architecture.]]
La mémoire vidéo mémorise l'image à afficher, les deux circuits d'interfaçage permettent à la carte d'affichage de communiquer respectivement avec l'écran et le reste de l'ordinateur, le ''Video Display Controler'' commande les autres circuits. Le ''Video Display Controler'' sert de chef d'orchestre pour un orchestre dont les autres circuits seraient les musiciens. Le circuit de contrôle était appelé autrefois le CRTC, car il commandait des écrans dit CRT, mais ce n'est plus d'actualité de nos jours.
La carte graphique communique via un bus, un vulgaire tas de fils qui connectent la carte graphique à la carte mère. Les premières cartes graphiques utilisaient un bus nommé ISA, qui fût rapidement remplacé par le bus PCI, plus rapide, lui-même remplacé par le bus AGP, puis le bus PCI-Express. Ce bus est géré par un contrôleur de bus, un circuit qui se charge d'envoyer ou de réceptionner les données sur le bus. Les circuits de communication avec le bus permettent à l'ordinateur de communiquer avec la carte graphique, via le bus PCI-Express, AGP, PCI ou autre. Il contient quelques registres dans lesquels le processeur pourra écrire ou lire, afin de lui envoyer des ordres du style : j'envoie une donnée, transmission terminée, je ne suis pas prêt à recevoir les données que tu veux m'envoyer, etc. Il y a peu à dire sur ce circuit, aussi nous allons nous concentrer sur les autres circuits.
Le circuit d'interfaçage écran est au minimum un circuit d’interfaçage électrique se contente de convertir les signaux de la carte graphique en signaux que l'on peut envoyer à l'écran. Il s'occupe notamment de convertir les tensions et courants : si l'écran demande des signaux de 5 Volts mais que la carte graphique fonctionne avec du 3,3 Volt, il y a une conversion à faire. De même, le circuit d'interfaçage électrique peut s'occuper de la conversion des signaux numériques vers de l'analogique. L'écran peut avoir une entrée analogique, surtout s'il est assez ancien.
Les anciens écrans CRT ne comprenaient que des données analogiques et pas le binaire, alors que c'est l'inverse pour la carte graphique, ce qui fait que le circuit d'interfaçage devait faire la conversion. La conversion était réalisée par un circuit qui traduit des données numériques (ici, du binaire) en données analogiques : le '''convertisseur numérique-analogique''' ou DAC (''Digital-to-Analogue Converter''). Au tout début, le circuit d’interfaçage était un DAC combiné avec des circuits annexes, ce qu'on appelle un RAMDAC (''Random Access Memory Digital-to-Analog Converter''). De nos jours, les écrans comprennent le binaire sous réserve qu'il soit codé suivant le standard adapté et les cartes graphiques n'ont plus besoin de RAMDAC.
Il y a peu à dire sur les circuits d'interfaçage. Leur conception et leur fonctionnement dépendent beaucoup du standard utilisé. Sans compter qu'expliquer leur fonctionnement demande de faire de l'électronique pure et dure, ce qui est rarement agréable pour le commun des mortels. Par contre, étudier le circuit de contrôle et la mémoire vidéo est beaucoup plus intéressant. Plusieurs chapitres seront dédiés à leur fonctionnement. Mais parlons maintenant des GPU modernes et passons à la section suivante.
===Un historique rapide des cartes d’affichage===
Les cartes d'affichages sont opposées aux cartes accélératrices 2D et 3D, qui permettent de décharger le processeur d'une partie du rendu 2D/3D. Pour cela, elles intègrent des circuits spécialisés. Vous imaginez peut-être que les cartes d'affichage sont apparues en premier, puis qu'elles ont gagné en puissance et en fonctionnalités pour devenir d'abord des cartes accélératrices 2D, puis des cartes 3D. C'est une suite assez logique, intuitive. Et ce n'est pas du tout ce qui s'est passé !
Les cartes d'affichage pures, sans rendu 2D, sont une invention des premiers PC. Elles sont arrivées alors que les consoles de jeu avaient déjà des cartes hybrides entre carte d'affichage et cartes de rendu 2D depuis une bonne décennie. Sur les consoles de jeu ou les microordinateurs anciens, il n'y avait pas de cartes d'affichage séparée. À la place, le système vidéo d'un ordinateur était un ensemble de circuits soudés sur la carte mère.
Les consoles de jeu, ainsi que les premiers micro-ordinateurs, avaient une configuration fixée une fois pour toute et n'étaient pas upgradables. Mais avec l'arrivée de l'IBM PC, les cartes d’affichages se sont séparées de la carte mère. Leurs composants étaient soudés sur une carte qu'on pouvait clipser et détacher de la carte mère si besoin. Et c'est ainsi que l'on peut actuellement changer la carte graphique d'un PC, alors que ce n'est pas le cas sur une console de jeu.
La différence entre les deux se limite cependant à cela. Les composant d'une carte d'affichage ou d'une console de jeu sont globalement les mêmes. Aussi, dans ce qui suit, nous parlerons de carte d'affichage pour désigner cet ensemble de circuits, peu importe qu'il soit soudé à la carte mère ou placé sur une carte d’affichage séparée. C'est un abus de langage qu'on ne retrouvera que dans ce cours.
===Les différents types de cartes d'affichage===
Dans la suite du cours, nous allons voir que toutes les cartes d'affichage ne fonctionnent pas de la même manière. Et ces différences font qu'on peut les classer en plusieurs types distincts. Leur classement s'explique par un fait assez simple : une image prend beaucoup de mémoire ! Par exemple, prenons le cas d'une image en niveaux de gris d'une résolution de 320 par 240 pixels, chaque pixel étant codé sur un octet. L'image prend alors 76800 octets, soit environ 76 kiloctets. Mine de rien, cela fait beaucoup de mémoire ! Et si on ajoute le support de la couleur, cela triple, voire quadruple la taille de l'image.
Les cartes graphiques récentes ont assez de mémoire pour stocker l'image à afficher. Une partie de la mémoire vidéo est utilisée pour mémoriser l'image à afficher. La portion en question s'appelle le ''framebuffer'', '''tampon d'image''' en français. Il s'agit là d'une solution très simple, mais qui demande une mémoire vidéo de grande taille. Les systèmes récents peuvent se le permettre, mais les tout premiers ordinateurs n'avaient pas assez de mémoire vidéo. Les cartes d'affichages devaient se débrouiller avec peu de mémoire, impossible de mémoriser l'image à afficher entièrement.
Pour compenser cela, les cartes d'affichage anciennes utilisaient diverses optimisations assez intéressantes. La première d'entre elle utilise pour cela le fonctionnement des anciens écrans CRT, qui affichaient l'image ligne par ligne. Pour rappel, l'image a afficher à l'écran a une certaine résolution : 320 pixels pour 240 pixels, par exemple. Pour l'écran CRT, l'image est composée de plusieurs lignes. Par exemple, pour une résolution de 640 par 480, l'image est découpée en 480 lignes, chacune faisant 640 pixels de long. L'écran est conçu pour qu'on lui envoie les lignes les unes après les autres, avec une petite pause entre l'affichage/envoi de deux lignes. Précisons que les écrans LCD ont abandonné ce mode de fonctionnement.
L'idée est alors la suivante : la mémoire vidéo ne mémorise que la ligne en cours d'affichage par l'écran. Le processeur met à jour la mémoire vidéo entre l'affichage de deux lignes. La mémoire vidéo n'est alors pas un tampon d'image, mais un '''tampon de ligne'''. Le défaut de cette technique est qu'elle demande que le processeur et la carte d'affichage soient synchronisés, de manière à ce que les lignes soient mises à jour au bon moment. L'avantage est que la quantité de mémoire vidéo nécessaire est divisée par un facteur 100, voire plus, égal à la résolution verticale (le nombre de lignes).
Une autre méthode, appelée le '''rendu en tiles''', est formellement une technique de compression d'image particulière. L'image à afficher est stockée sous un format compressé en mémoire vidéo, mais est décompressée pixel par pixel lors de l'affichage. Il nous est difficile de décrire cette technique maintenant, mais un chapitre entier sera dédié à cette technique. Le chapitre en question abordera une technique similaire, appelée le rendu en mode texte, qui servira d'introduction propédeutique.
Le rendu en tiles et l'usage d'un tampon ligne sont deux optimisations complémentaires. Il est ainsi possible d'utiliser soit l'une, soit l'autre, soit les deux. En clair, cela donne quatre types de cartes d'affichage distincts :
* les cartes d'affichage à tampon d'image ;
* les cartes à tampon d'images en rendu à tiles ;
* les cartes d'affichage à tampon de ligne ;
* les cartes d'affichage à tampon de ligne en rendu à tiles.
{|class="wikitable"
|-
!
! Rendu normal
! Rendu à tile
|-
! Tampon d'image
| Cartes graphiques post-années 90, standard VGA sur PC
| Consoles de jeu 8 bits et 16 bits, standards CGA, MGA
|-
! Tampon de ligne
| Consoles de jeu ATARI 2600 et postérieures
| Console de jeu néo-géo
|}
Lez prochains chapitres porteront surtout sur les cartes d'affichages à tampon d'image, plus simples à, expliquer. Deux chapitres seront dédiés respectivement aux cartes à rendu en tile, et aux cartes à tampon de ligne.
==Les cartes graphiques actuelles sont très complexes==
Les cartes graphiques actuelles sont des cartes d'affichage améliorées auxquelles on a ajouté des circuits annexes, afin de leur donner des capacités de calcul pour le rendu 2D et/ou 3D, mais elles n'en restent pas moins des cartes d'affichages. La seule différence est que le processeur n’envoie pas une image à la mémoire vidéo, mais que l'image à afficher est calculée par la carte graphique 2D/3D. Les calculs autrefois effectués sur le CPU sont donc déportés sur la carte graphique.
Si vous analysez une carte graphique récente, vous verrez que les circuits des cartes d'affichage sont toujours là, bien que noyés dans des circuits de rendu 2D/3D. On retrouve une mémoire vidéo, une interface écran, un circuit d'interface avec le bus, un ''Video Display Controler''. L'interface écran est par contre fusionnée avec le ''Video Display Controler''. Mais à ces circuits d'affichage, sont ajoutés un '''GPU''' (''Graphic Processing Unit''), qui s'occupe du rendu 3D, du rendu 2D, mais aussi d'autres fonctions comme du décodage de fichiers vidéos.
[[File:Architecture d'une carte graphique avec un GPU.png|centre|vignette|upright=2|Architecture d'une carte graphique avec un GPU]]
===L'intérieur d'un GPU===
Le GPU est un composant assez complexe, surtout sur les cartes graphiques modernes. Il regroupe plusieurs circuits aux fonctions distinctes.
* Un '''''Video Display controler''''' est toujours présent, mais ne sera pas représenté dans ce qui suit.
* Des '''circuit de rendu 2D''' séparés peuvent être présents, mais sont la plupart du temps intégrés au VDC.
* Les '''circuits de rendu 3D''' s'occupent du rendu 3D.
* Les '''circuits de décodage vidéo''', pour améliorer la performance du visionnage de vidéos.
Les trois circuits précédents sont gouvernés par un '''processeur de commande''', un circuit assez difficile à décrire. Pour le moment, disons qu'il s'occupe de répartir le travail entre les différents circuits précédents. Le processeur de commande reçoit du travail à faire, envoyé par le CPU via le bus, et le redistribue dans les trois circuits précédents. Par exemple, si on lui envoie une vidéo encodée en H264, il envoie le tout au circuit vidéo. De plus, il le configure pour qu'il la décode correctement : il dit au circuit que c'est une vidéo encodée en H264, afin que le circuit sache comment la décoder. Ce genre de configuration est aussi présente sur les circuits de rendu 2D et 3D, bien qu'elle soit plus compliquée.
[[File:Microarchitecture simplifiée d'un GPU.png|centre|vignette|upright=2|Microarchitecture simplifiée d'un GPU]]
Un GPU contient aussi un contrôleur mémoire, qui est une interface entre le GPU et la mémoire vidéo. Interface électrique, car GPU et mémoire n'utilisent pas les mêmes tensions et qu'en plus, leur interconnexion demande de gérer pas mal de détails purement électriques. Mais aussi interface de communication, car communiquer entre mémoire vidéo et GPU demande de faire pas mal de manipulations, comme gérer l'adressage et bien d'autres choses qu'on ne peut pas encore détailler ici.
Un GPU contient aussi d'autres circuits annexes, comme des circuits pour gérer la consommation électrique, un BIOS vidéo, etc. L'interface écran et l'interface avec le bus sont parfois intégrées au GPU.
===Un historique simplifié des pipelines 2D/3D/vidéo===
Avant les années 90, les cartes graphiques des ordinateurs personnels et des consoles de jeux se résumaient à des circuits d'accélération 2D, elles n'avaient pas de décodeurs vidéos ou de circuits de rendu 3D. C'est dans les années 90 qu'elles ont commencé à incorporer des circuits d'accélération 3D. Puis, après les années 2000-2010, elles ont commencé à intégrer des circuits de décodage vidéo, aux alentours des années 2010. En parallèle, les circuits de rendu 2D ont progressivement été réduits puis abandonnés.
Les circuits de rendu 2D sont des circuits qui ne sont pas programmables, mais qu'on peut configurer. On dit aussi que ce sont des '''circuits fixes'''. Ils sont opposés aux circuits programmables, basés sur des processeurs. Les circuits de rendu 3D étaient initialement des circuits fixes eux aussi, mais sont devenus de plus en plus programmables avec le temps. Les GPU d'après les années 2000 utilisent un mélange de circuits programmables et fixes assez variable. Nous reviendrons là-dessus dans un chapitre dédié.
[[File:Microarchitecture globale d'un GPU.png|centre|vignette|upright=2|Microarchitecture globale d'un GPU]]
Les circuits de décodage vidéo sont des circuits fixes, le décodage n'est pas réalisé sur les processeurs de shaders.
[[File:GPU avec processeurs de shaders partagés entre rendu 3D et décodage vidéo.png|centre|vignette|upright=2|GPU avec processeurs de shaders partagés entre rendu 3D et décodage vidéo]]
Les cinq prochains chapitres vont parler des cartes d'affichage et des cartes accélératrices 2D, les deux étant fortement liées. C'est seulement dans les chapitres qui suivront que nous parlerons des cartes 3D. Les cartes 3D sont composées d'une carte d'affichage à laquelle on a rajouté des circuits de calcul, ce qui fait qu'il est préférable de faire ainsi : on voit ce qui est commun entre les deux d'abord, avant de voir le rendu 3D ensuite. De plus, le rendu 3D est plus complexe que l'affichage d'une image à l'écran, ce qui fait qu'il vaut mieux voir cela après.
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| next=Le Video Display Controler
| nextText=Le Video Display Controler
}}{{autocat}}
</noinclude>
hflk85cqjdkd41oi24mwqkffjzo5t7b
774448
774447
2026-10-08T13:00:12Z
Mewtow
31375
/* L'intérieur d'un GPU */
774448
wikitext
text/x-wiki
Les cartes graphiques sont des cartes qui communiquent avec l'écran, pour y afficher des images. Les cartes graphiques modernes incorporent aussi des circuits de calcul pour accélérer du rendu 2D ou 3D. Dans ce chapitre, nous allons faire une introduction et expliquer ce qu'est une carte graphique et surtout : nous allons voir ce qu'il y a à l'intérieur, du moins dans les grandes lignes.
==Les cartes graphiques dédiées, intégrées et soudées==
Vous avez sans doute déjà démonté votre PC pour en changer la carte graphique, vous savez sans doute à quoi elle ressemble. Sur les PC modernes, il s'agit d'un composant séparé, qu'on branche sur la carte mère, sur un port spécialisé. Du moins, c'est le cas si vous avez un PC fixe assez puissant. Mais il y a deux autres possibilités.
<noinclude>[[File:PX7800 GTX (557505323).jpg|centre|vignette|Carte graphique dédiée PX 7800 GTX I]]</noinclude>
La première est celle où la carte graphique est directement intégrée dans le processeur de la machine ! C'est quelque chose qui se fait depuis les années 2000-2010, avec l'amélioration de la technologie et la miniaturisation des transistors. Il est possible de mettre tellement de transistors sur une puce de silicium que les concepteurs de processeur en ont profité pour mettre une carte graphique peut puissante dans le processeur.
Une autre possibilité, surtout utilisée sur les consoles de jeu et les PC portables, est celle où la carte graphique est composée de circuits soudés à la carte mère.
Pour résumer, il faut distinguer trois types de cartes graphiques différentes :
* Les '''cartes graphiques dédiées''', séparées dans une carte d'extension qu'on doit connecter à la carte mère via un connecteur dédié.
* Les '''cartes graphiques intégrées''', qui font partie du processeur.
* Les '''cartes graphiques soudées''' à la carte mère.
Vous avez sans doute vu qu'il y a une grande différence de performance entre une carte graphique dédiée et une carte graphique intégrée. La raison est simplement que les cartes graphiques intégrées ont moins de transistors à leur disposition, ce qui fait qu'elles contiennent moins de circuits de calcul. Les cartes graphiques dédiées et soudées n'ont pas de différences de performances notables. Les cartes soudées des PC portables sont généralement moins performantes car il faut éviter que le PC chauffe trop, vu que la dissipation thermique est moins bonne avec un PC portable (moins de gros ventilos), ce qui demande d'utiliser une carte graphique moins puissante. Mais les cartes soudées des consoles de jeu n'ont pas ce problème : elles sont dans un boitier bien ventilés, on peut en utiliser une très puissante.
===Un PC avec plusieurs GPU : la commutation de GPU===
De nos jours, il y a de très fortes chances que votre ordinateur intègre plusieurs cartes graphiques, peu importe que ce soit un PC portable ou fixe. Le processeur de votre ordinateur intègre une carte graphique intégrée, de faible performance, qui consomme peu d'énergie/électricité. Sauf pour quelques rares modèles de processeurs (les modèles Intel avec un suffixe F à la fin de leur nom). Le marché du processeur grand public est ainsi, seuls quelques processeurs dédiés aux serveurs n'ont pas de carte graphique intégrée. Et en plus de la carte intégrée, une bonne partie des PC intègrent aussi soit une carte dédiée, soit une carte soudée. Soudée sur les PC portables, dédiée sur les PC fixe.
Dans le passé, il était possible de mettre plusieurs cartes graphiques dédiées dans un même PC, mais avec des conditions drastiques. ATI/AMD et NVIDIA avaient ajouté des fonctionnalités de multi-GPU, qui permettaient à deux GPU de travailler ensemble, afin de presque doubler les performances. Mais cela ne marchait qu'avec deux GPU NVIDIA ou deux GPU ATI/AMD, utiliser deux GPU de deux marques différentes ne marchait pas. Un chapitre entier sera dédié à ces techniques, mais nous n'en parlerons pas ici, car elles sont tombées en désuétude, aucun GPU grand public ne supporte ces technologies.
S'il y a deux cartes graphiques, cela ne signifie pas que les deux sont utilisées en même temps. En effet, selon les circonstances, le PC va privilégier l'une ou l'autre. Dans les années 2010, le choix se faisait dans le BIOS : une des deux carte graphique était désactivée pour de bon, typiquement la carte intégrée. Les PC avec une carte dédiée désactivaient la carte intégrée dans le processeur, pour éviter tout conflit entre les deux cartes.
De nos jours, les deux sont utilisables pour du rendu 3D, mais pas en même temps. Le système d'exploitation, Windows ou linux, utilise soit la carte intégrée, soit la carte dédiée, suivant les besoins. La carte dédiée a de bonnes performance, mais elle consomme beaucoup d'énergie/électricité et chauffe plus. La carte graphique intégrée fait l'inverse : ses performances sont basses, mais elle consomme très peu et chauffe moins. La carte dédiée est donc utilisée quand on a besoin de performance, l'intégrée est utilisée quand elle suffit, afin de faire des économies. Prenons l'exemple d'un jeu vidéo : un jeu ancien et peu gourmand sera exécuté sur la carte intégrée, alors qu'un jeu récent/gourmand sera exécuté sur la carte dédiée. Le rendu du bureau de Windows/linux est réalisé par la carte graphique intégrée, pour économiser de l'énergie.
===La connexion des cartes graphiques à l'écran===
Prenons un PC fixe avec deux cartes graphiques, une intégrée et une dédiée. En général, il y a deux connecteurs pour l'écran, un qui est relié à la carte graphique intégrée, un autre qui est sur la carte dédiée proprement dite. Suivant là où vous brancherez l'écran, vous n'utiliserez pas la même carte graphique. Le système d'exploitation se charge d'envoyer les images à afficher à la carte graphique adéquate.
Sur un PC portable ''gaming'', les choses sont différentes. Il n'y a qu'un seul connecteur pour l'écran, pas deux. Et dans ce cas, il y a deux possibilités.
La première est la plus simple. Les deux cartes graphiques sont reliées au connecteur écran, par l'intermédiaire d'un circuit multiplexeur. Le circuit multiplexeur reçoit les images à afficher de la part des deux cartes graphiques et choisit l'une d'entre elle. C'est la solution la plus performante, car la carte dédiée peut afficher directement ses images à l'écran, sans avoir à les envoyer à la carte intégrée. Mais elle complexifie le câblage et demande d'ajouter un circuit multiplexeur, ce qui n'est pas gratuit.
[[File:Commutation de GPU avec un MUX.png|centre|vignette|upright=2|Commutation de GPU avec un MUX]]
Avec la seconde solution, une seule carte graphique est connectée à l'écran, généralement la carte intégrée. Si la carte dédiée est utilisée, les images qu'elle calcule sont envoyées à la carte intégrée pour ensuite être affichées à l'écran. On passe par un intermédiaire, mais le câblage est plus simple.
[[File:Commutation de GPU sans MUX.png|centre|vignette|upright=2|Commutation de GPU sans MUX]]
==Les cartes d'affichage et leur architecture==
Vous vous demandez comment est-ce possible qu'une carte graphique soit soudée ou intégrée dans un processeur. La raison est que les trois types de cartes graphiques sont très similaires, elles sont composées des mêmes types de composants, ce qu'il y a à l'intérieur est globalement le même, comme on va le voir dans ce qui suit.
Au tout début de l'informatique, le rendu graphique était pris en charge par le processeur. Il calculait l'image à afficher et l'envoyait à l'écran, pixel par pixel. Le problème est que le processeur devait se synchroniser avec l'écran, pour envoyer les pixels au bon moment. Pour simplifier la vie des programmeurs, les fabricants de matériel ont inventé des cartes vidéo. Avec celles-ci, le processeur calcule l'image à envoyer à l'écran et la transmet à la carte d'affichage, sans avoir à se synchroniser avec l'écran. L'avantage est que le processeur n'a pas à se synchroniser avec l'écran, juste à envoyer l'image à une carte d'affichage.
Les cartes d'affichage ne géraient pas le rendu 3D. Le processeur calculait une image, la copiait dans la mémoire vidéo, puis la carte d'affichage l'envoyait à l'écran au bon moment. Il n'y avait pas de circuits de calcul graphique, ni de circuits de décodage vidéo. Juste de quoi afficher une image à l'écran. Et mine de rien, il est intéressant d'étudier de telles cartes graphiques anciennes. De telles cartes graphiques sont ce que j'ai décidé d'appeler des '''cartes d'affichage'''.
===L'intérieur d'une carte d'affichage===
Une carte d'affichage contient plusieurs sous-circuits, chacun dédié à une fonction précise.
* La '''mémoire vidéo''' est une mémoire RAM intégrée à la carte graphique, qui a des fonctions multiples.
* L''''interface écran''', ou ''Display interface'', regroupe les connecteurs et tous les circuits permettant d'envoyer l'image à l'écran.
* Le '''circuit d'interface avec le bus''' existe uniquement sur les cartes dédiées et éventuellement sur quelques cartes soudées. Il s'occupe des transmissions sur le bus PCI/AGP/PCI-Express, le connecteur qui relie la carte mère et la carte graphique.
* Un circuit de contrôle qui commande le tout, appelé le '''''Video Display Controler'''''.
[[File:Carte d'affichage - architecture.png|centre|vignette|upright=2|Carte d'affichage - architecture.]]
La mémoire vidéo mémorise l'image à afficher, les deux circuits d'interfaçage permettent à la carte d'affichage de communiquer respectivement avec l'écran et le reste de l'ordinateur, le ''Video Display Controler'' commande les autres circuits. Le ''Video Display Controler'' sert de chef d'orchestre pour un orchestre dont les autres circuits seraient les musiciens. Le circuit de contrôle était appelé autrefois le CRTC, car il commandait des écrans dit CRT, mais ce n'est plus d'actualité de nos jours.
La carte graphique communique via un bus, un vulgaire tas de fils qui connectent la carte graphique à la carte mère. Les premières cartes graphiques utilisaient un bus nommé ISA, qui fût rapidement remplacé par le bus PCI, plus rapide, lui-même remplacé par le bus AGP, puis le bus PCI-Express. Ce bus est géré par un contrôleur de bus, un circuit qui se charge d'envoyer ou de réceptionner les données sur le bus. Les circuits de communication avec le bus permettent à l'ordinateur de communiquer avec la carte graphique, via le bus PCI-Express, AGP, PCI ou autre. Il contient quelques registres dans lesquels le processeur pourra écrire ou lire, afin de lui envoyer des ordres du style : j'envoie une donnée, transmission terminée, je ne suis pas prêt à recevoir les données que tu veux m'envoyer, etc. Il y a peu à dire sur ce circuit, aussi nous allons nous concentrer sur les autres circuits.
Le circuit d'interfaçage écran est au minimum un circuit d’interfaçage électrique se contente de convertir les signaux de la carte graphique en signaux que l'on peut envoyer à l'écran. Il s'occupe notamment de convertir les tensions et courants : si l'écran demande des signaux de 5 Volts mais que la carte graphique fonctionne avec du 3,3 Volt, il y a une conversion à faire. De même, le circuit d'interfaçage électrique peut s'occuper de la conversion des signaux numériques vers de l'analogique. L'écran peut avoir une entrée analogique, surtout s'il est assez ancien.
Les anciens écrans CRT ne comprenaient que des données analogiques et pas le binaire, alors que c'est l'inverse pour la carte graphique, ce qui fait que le circuit d'interfaçage devait faire la conversion. La conversion était réalisée par un circuit qui traduit des données numériques (ici, du binaire) en données analogiques : le '''convertisseur numérique-analogique''' ou DAC (''Digital-to-Analogue Converter''). Au tout début, le circuit d’interfaçage était un DAC combiné avec des circuits annexes, ce qu'on appelle un RAMDAC (''Random Access Memory Digital-to-Analog Converter''). De nos jours, les écrans comprennent le binaire sous réserve qu'il soit codé suivant le standard adapté et les cartes graphiques n'ont plus besoin de RAMDAC.
Il y a peu à dire sur les circuits d'interfaçage. Leur conception et leur fonctionnement dépendent beaucoup du standard utilisé. Sans compter qu'expliquer leur fonctionnement demande de faire de l'électronique pure et dure, ce qui est rarement agréable pour le commun des mortels. Par contre, étudier le circuit de contrôle et la mémoire vidéo est beaucoup plus intéressant. Plusieurs chapitres seront dédiés à leur fonctionnement. Mais parlons maintenant des GPU modernes et passons à la section suivante.
===Un historique rapide des cartes d’affichage===
Les cartes d'affichages sont opposées aux cartes accélératrices 2D et 3D, qui permettent de décharger le processeur d'une partie du rendu 2D/3D. Pour cela, elles intègrent des circuits spécialisés. Vous imaginez peut-être que les cartes d'affichage sont apparues en premier, puis qu'elles ont gagné en puissance et en fonctionnalités pour devenir d'abord des cartes accélératrices 2D, puis des cartes 3D. C'est une suite assez logique, intuitive. Et ce n'est pas du tout ce qui s'est passé !
Les cartes d'affichage pures, sans rendu 2D, sont une invention des premiers PC. Elles sont arrivées alors que les consoles de jeu avaient déjà des cartes hybrides entre carte d'affichage et cartes de rendu 2D depuis une bonne décennie. Sur les consoles de jeu ou les microordinateurs anciens, il n'y avait pas de cartes d'affichage séparée. À la place, le système vidéo d'un ordinateur était un ensemble de circuits soudés sur la carte mère.
Les consoles de jeu, ainsi que les premiers micro-ordinateurs, avaient une configuration fixée une fois pour toute et n'étaient pas upgradables. Mais avec l'arrivée de l'IBM PC, les cartes d’affichages se sont séparées de la carte mère. Leurs composants étaient soudés sur une carte qu'on pouvait clipser et détacher de la carte mère si besoin. Et c'est ainsi que l'on peut actuellement changer la carte graphique d'un PC, alors que ce n'est pas le cas sur une console de jeu.
La différence entre les deux se limite cependant à cela. Les composant d'une carte d'affichage ou d'une console de jeu sont globalement les mêmes. Aussi, dans ce qui suit, nous parlerons de carte d'affichage pour désigner cet ensemble de circuits, peu importe qu'il soit soudé à la carte mère ou placé sur une carte d’affichage séparée. C'est un abus de langage qu'on ne retrouvera que dans ce cours.
===Les différents types de cartes d'affichage===
Dans la suite du cours, nous allons voir que toutes les cartes d'affichage ne fonctionnent pas de la même manière. Et ces différences font qu'on peut les classer en plusieurs types distincts. Leur classement s'explique par un fait assez simple : une image prend beaucoup de mémoire ! Par exemple, prenons le cas d'une image en niveaux de gris d'une résolution de 320 par 240 pixels, chaque pixel étant codé sur un octet. L'image prend alors 76800 octets, soit environ 76 kiloctets. Mine de rien, cela fait beaucoup de mémoire ! Et si on ajoute le support de la couleur, cela triple, voire quadruple la taille de l'image.
Les cartes graphiques récentes ont assez de mémoire pour stocker l'image à afficher. Une partie de la mémoire vidéo est utilisée pour mémoriser l'image à afficher. La portion en question s'appelle le ''framebuffer'', '''tampon d'image''' en français. Il s'agit là d'une solution très simple, mais qui demande une mémoire vidéo de grande taille. Les systèmes récents peuvent se le permettre, mais les tout premiers ordinateurs n'avaient pas assez de mémoire vidéo. Les cartes d'affichages devaient se débrouiller avec peu de mémoire, impossible de mémoriser l'image à afficher entièrement.
Pour compenser cela, les cartes d'affichage anciennes utilisaient diverses optimisations assez intéressantes. La première d'entre elle utilise pour cela le fonctionnement des anciens écrans CRT, qui affichaient l'image ligne par ligne. Pour rappel, l'image a afficher à l'écran a une certaine résolution : 320 pixels pour 240 pixels, par exemple. Pour l'écran CRT, l'image est composée de plusieurs lignes. Par exemple, pour une résolution de 640 par 480, l'image est découpée en 480 lignes, chacune faisant 640 pixels de long. L'écran est conçu pour qu'on lui envoie les lignes les unes après les autres, avec une petite pause entre l'affichage/envoi de deux lignes. Précisons que les écrans LCD ont abandonné ce mode de fonctionnement.
L'idée est alors la suivante : la mémoire vidéo ne mémorise que la ligne en cours d'affichage par l'écran. Le processeur met à jour la mémoire vidéo entre l'affichage de deux lignes. La mémoire vidéo n'est alors pas un tampon d'image, mais un '''tampon de ligne'''. Le défaut de cette technique est qu'elle demande que le processeur et la carte d'affichage soient synchronisés, de manière à ce que les lignes soient mises à jour au bon moment. L'avantage est que la quantité de mémoire vidéo nécessaire est divisée par un facteur 100, voire plus, égal à la résolution verticale (le nombre de lignes).
Une autre méthode, appelée le '''rendu en tiles''', est formellement une technique de compression d'image particulière. L'image à afficher est stockée sous un format compressé en mémoire vidéo, mais est décompressée pixel par pixel lors de l'affichage. Il nous est difficile de décrire cette technique maintenant, mais un chapitre entier sera dédié à cette technique. Le chapitre en question abordera une technique similaire, appelée le rendu en mode texte, qui servira d'introduction propédeutique.
Le rendu en tiles et l'usage d'un tampon ligne sont deux optimisations complémentaires. Il est ainsi possible d'utiliser soit l'une, soit l'autre, soit les deux. En clair, cela donne quatre types de cartes d'affichage distincts :
* les cartes d'affichage à tampon d'image ;
* les cartes à tampon d'images en rendu à tiles ;
* les cartes d'affichage à tampon de ligne ;
* les cartes d'affichage à tampon de ligne en rendu à tiles.
{|class="wikitable"
|-
!
! Rendu normal
! Rendu à tile
|-
! Tampon d'image
| Cartes graphiques post-années 90, standard VGA sur PC
| Consoles de jeu 8 bits et 16 bits, standards CGA, MGA
|-
! Tampon de ligne
| Consoles de jeu ATARI 2600 et postérieures
| Console de jeu néo-géo
|}
Lez prochains chapitres porteront surtout sur les cartes d'affichages à tampon d'image, plus simples à, expliquer. Deux chapitres seront dédiés respectivement aux cartes à rendu en tile, et aux cartes à tampon de ligne.
==Les cartes graphiques actuelles sont très complexes==
Les cartes graphiques actuelles sont des cartes d'affichage améliorées auxquelles on a ajouté des circuits annexes, afin de leur donner des capacités de calcul pour le rendu 2D et/ou 3D, mais elles n'en restent pas moins des cartes d'affichages. La seule différence est que le processeur n’envoie pas une image à la mémoire vidéo, mais que l'image à afficher est calculée par la carte graphique 2D/3D. Les calculs autrefois effectués sur le CPU sont donc déportés sur la carte graphique.
Si vous analysez une carte graphique récente, vous verrez que les circuits des cartes d'affichage sont toujours là, bien que noyés dans des circuits de rendu 2D/3D. On retrouve une mémoire vidéo, une interface écran, un circuit d'interface avec le bus, un ''Video Display Controler''. L'interface écran est par contre fusionnée avec le ''Video Display Controler''. Mais à ces circuits d'affichage, sont ajoutés un '''GPU''' (''Graphic Processing Unit''), qui s'occupe du rendu 3D, du rendu 2D, mais aussi d'autres fonctions comme du décodage de fichiers vidéos.
[[File:Architecture d'une carte graphique avec un GPU.png|centre|vignette|upright=2|Architecture d'une carte graphique avec un GPU]]
===L'intérieur d'un GPU===
Le GPU est un composant assez complexe, surtout sur les cartes graphiques modernes. Il regroupe plusieurs circuits aux fonctions distinctes.
* Un '''contrôleur d'affichage vidéo''' (''Video Display controler'') est toujours présent, mais ne sera pas représenté dans ce qui suit.
* Des '''circuit de rendu 2D''' séparés peuvent être présents, mais sont la plupart du temps intégrés au VDC.
* Les '''circuits de rendu 3D''' s'occupent du rendu 3D.
* Les '''circuits de décodage vidéo''', pour améliorer la performance du visionnage de vidéos.
Les trois circuits précédents sont gouvernés par un '''processeur de commande''', un circuit assez difficile à décrire. Pour le moment, disons qu'il s'occupe de répartir le travail entre les différents circuits précédents. Le processeur de commande reçoit du travail à faire, envoyé par le CPU via le bus, et le redistribue dans les trois circuits précédents. Par exemple, si on lui envoie une vidéo encodée en H264, il envoie le tout au circuit vidéo. De plus, il le configure pour qu'il la décode correctement : il dit au circuit que c'est une vidéo encodée en H264, afin que le circuit sache comment la décoder. Ce genre de configuration est aussi présente sur les circuits de rendu 2D et 3D, bien qu'elle soit plus compliquée.
[[File:Microarchitecture simplifiée d'un GPU.png|centre|vignette|upright=2|Microarchitecture simplifiée d'un GPU]]
Un GPU contient aussi un contrôleur mémoire, qui est une interface entre le GPU et la mémoire vidéo. Interface électrique, car GPU et mémoire n'utilisent pas les mêmes tensions et qu'en plus, leur interconnexion demande de gérer pas mal de détails purement électriques. Mais aussi interface de communication, car communiquer entre mémoire vidéo et GPU demande de faire pas mal de manipulations, comme gérer l'adressage et bien d'autres choses qu'on ne peut pas encore détailler ici.
Un GPU contient aussi d'autres circuits annexes, comme des circuits pour gérer la consommation électrique, un BIOS vidéo, etc. L'interface écran et l'interface avec le bus sont parfois intégrées au GPU.
===Un historique simplifié des pipelines 2D/3D/vidéo===
Avant les années 90, les cartes graphiques des ordinateurs personnels et des consoles de jeux se résumaient à des circuits d'accélération 2D, elles n'avaient pas de décodeurs vidéos ou de circuits de rendu 3D. C'est dans les années 90 qu'elles ont commencé à incorporer des circuits d'accélération 3D. Puis, après les années 2000-2010, elles ont commencé à intégrer des circuits de décodage vidéo, aux alentours des années 2010. En parallèle, les circuits de rendu 2D ont progressivement été réduits puis abandonnés.
Les circuits de rendu 2D sont des circuits qui ne sont pas programmables, mais qu'on peut configurer. On dit aussi que ce sont des '''circuits fixes'''. Ils sont opposés aux circuits programmables, basés sur des processeurs. Les circuits de rendu 3D étaient initialement des circuits fixes eux aussi, mais sont devenus de plus en plus programmables avec le temps. Les GPU d'après les années 2000 utilisent un mélange de circuits programmables et fixes assez variable. Nous reviendrons là-dessus dans un chapitre dédié.
[[File:Microarchitecture globale d'un GPU.png|centre|vignette|upright=2|Microarchitecture globale d'un GPU]]
Les circuits de décodage vidéo sont des circuits fixes, le décodage n'est pas réalisé sur les processeurs de shaders.
[[File:GPU avec processeurs de shaders partagés entre rendu 3D et décodage vidéo.png|centre|vignette|upright=2|GPU avec processeurs de shaders partagés entre rendu 3D et décodage vidéo]]
Les cinq prochains chapitres vont parler des cartes d'affichage et des cartes accélératrices 2D, les deux étant fortement liées. C'est seulement dans les chapitres qui suivront que nous parlerons des cartes 3D. Les cartes 3D sont composées d'une carte d'affichage à laquelle on a rajouté des circuits de calcul, ce qui fait qu'il est préférable de faire ainsi : on voit ce qui est commun entre les deux d'abord, avant de voir le rendu 3D ensuite. De plus, le rendu 3D est plus complexe que l'affichage d'une image à l'écran, ce qui fait qu'il vaut mieux voir cela après.
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| next=Le Video Display Controler
| nextText=Le Video Display Controler
}}{{autocat}}
</noinclude>
a03s310t0gaadkjj9azzrcc9o71640b
Les cartes graphiques/Les cartes accélératrices 2D
0
67390
774450
765345
2026-10-08T13:20:15Z
Mewtow
31375
/* Les circuits d'accélération matérielle des sprites */
774450
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu les CRTC. Il est maintenant temps de voir les '''''Video Interface Controlers''''', des VDC qui gèrent des fonctionnalités de rendu 2D avancées, voire plus. 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.
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'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|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 défilement 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 défilement, 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. Elles 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 défilement ou encore un support matériel du curseur de la souris, toutes dérivées des techniques d'accélération de rendu 2D.
==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é. D'ailleurs, il est souvent combiné à un contrôleur DMA, voire fusionné avec lui. Il pouvait non seulement faire des copies, mais aussi appliquer des opérations bit à bit sur les données copiées.
===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 place 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''.
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, prenons l'image ci-dessous à gauche et son masque à droite. Les parties blanches du masque 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 noirs. Le masque indique quels pixels sont à copier ou non, ce qui demande simplement 1 bit par pixel. Le pixel est associé dans le masque à une couleur noire ou blanche, pas de niveaux de gris permis.
{|
|[[File:Pacman--------2.gif|vignette|Image de Pacman.]]
|[[File:Pacman mask.gif|vignette|Masque de Pacman.]]
|}
Le ''blitter'' combine le ''sprite'', l'arrière-plan et le masque. Pour chaque pixel, il effectue l'opération suivante : ((arrière-plan) AND (masque)) OR (image de pacman).
[[File:Sprite rendering by binary image mask.png|centre|vignette|upright=2|Sprite rendering by binary image mask]]
Imaginons qu'on veut placer l'image ci-dessus (le point vert), en plusieurs endroits de l'arrière-plan.
[[File:XBlit dot.png|centre|vignette|upright=3|Image et masque.]]
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é, les parties noires sont ignorées. Au final, l'image finale est bel et bien celle qu'on attend.
{|
|[[File:Blit back.png|vignette|upright=3|Arrière-plan de l'exemple.]]
|[[File:XBlit and.png|vignette|upright=3|Application du masque - ET.]]
|[[File:XBlit final.png|vignette|upright=3|Application des ''sprites'' - OU.]]
|}
==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'accès au ''framebuffer'' par le CRTC.
Il faut noter que les ''sprites'' matériel ont une taille assez petite. Ils sont typiquement des carrés de 8 pixels de côté, rarement plus. Par contre, les ''sprites'' utilisés dans les jeux vidéos sont souvent plus grands, certains font 16 pixels de côtés, parfois plus. Aussi, les ''sprites'' d'un jeu vidéo sont parfois composés de plusieurs ''sprites'' matériels placés les uns à côté des autres. Par exemple, un ''sprite'' de 16 pixels de hauteur et de 8 pixels de largeur est composé de deux ''sprites'' 8 par 8, placés l'un au-dessus de l'autre.
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 pour respectivement 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, 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 !
===Les circuits d'accélération matérielle des ''sprites''===
Avec une implémentation très simple, les ''sprites'' sont stockés dans des registres, alors que l'image d'arrière-plan est stockée dans un ''framebuffer'', dans une mémoire 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. 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.
[[File:Hardware sprites.png|centre|vignette|upright=2.0|Sprites matériels.]]
Les jeux vidéos des consoles 8 ou 16 bits ne mémorisaient pas les ''sprites'' directement dans des registres. Les ''sprites'' étaient dans la cartouche de jeu, donc dans une mémoire ROM. Les registres de ''sprites'' mémorisaient l'adresse du ''sprite'', donc sa position dans la mémoire ROM. Les ''sprites'' étaient lus depuis la cartouche en cas de ''match'' avec les registres X et Y.
Les ''sprites'' allaient presque toujours de pair avec 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. 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.
Les VDC avaient des limitations en termes de ''sprites'' matériels supportés. Notamment, ils géraient un nombre maximal de ''sprites'' par image. Cette limitation par image était secondée par une limitation du nombre de ''sprites'' par ligne. Par exemple, la NES et la Master Sytem géraient 64 ''sprites'' par image, avec maximum 8 ''sprites'' par ligne. Cependant, ces limitations pouvaient être contournées par l'usage de ''raster interrupts'', et précisément l'''horizontal blank interrupt'' qui précise quand l'affichage d'une ligne précise est terminée. L'idée est de changer les ''sprites'' d'une image et de les repositionner, pendant le tracé de l'image, entre l'affichage de deux lignes. Les ''sprites'' qui ont déjà été affichés en haut de l'écran sont remplacés par des ''sprites'' à afficher plus bas. Les programmeurs utilisaient ce genre de ruses pour afficher plus de ''sprites'' à l'écran que ne peut en supporter la console.
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 ''sprites'' 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''.
Il faut noter que certaines consoles géraient des ''sprites'' situés sous l'arrière-plan. Quelques effets graphiques le requéraient. Par exemple, imaginez un personnage qui passe derrière un arbre. L'arbre est dessiné dans l'arrière-plan, afin d'économiser des ''sprites'', alors que le personage est composé de plusieurs ''sprites''. Avec des ''sprites'' pouvant passer derrière l'arrière-plan, on peut simuler cet effet de personnage qui passe derrière l'arbre, donc sous l'arrière-plan.
===La palette indicée des ''sprites''===
Certaines consoles de jeux utilisaient à la fois des ''sprites'' et une palette indicée. En soi, rien d'incompatible, les deux techniques sont totalement orthogonales. Mais il y a des consoles qui utilisaient deux palettes séparées : une pour les ''sprites'', l'autre pour l'arrière-plan. En général, les deux palettes ont une couleur commune : la couleur transparente. Elle est codée deux fois, une fois dans chaque palette. Mais les autres couleurs des palettes sont potentiellement différentes dans les deux palettes. L'usage de deux palettes indicées séparées est assez commun sur les consoles 8 bits.
L'utilité de ce système est de gérer deux fois plus de couleurs, tout en réduisant les besoins en mémoire. En théorie, on pourrait gérer deux fois plus de couleurs en utilisant une palette unique deux fois plus grande, ce qui serait plus flexible. Mais en utilisant une palette deux fois plus grande, on doit ajouter un bit en plus par pixel, pour encoder deux fois plus de couleurs. Avec deux palettes séparées pour les ''sprites'' et l'arrière-plan, pas besoin : le bit est implicite, il vaut 0 pour les ''sprites'' et 1 pour l'arrière-plan.
La Master System a deux palettes : une réservée à l'arrière-plan, l'autre servant à la fois pour les ''sprites'' et le décor. Les ''sprites'' ont à disposition 16 couleurs qu'ils peuvent configurer comme ils le souhaitent. La couleur de transparence n'est pas obligatoire. À l'inverse, la palette pour l'arrière-plan dispose de 15 couleurs configurables, et une couleur de transparence fixe. L'arrière-plan a donc à sa disposition 31 couleurs configurables : 16 provenant de la palette des ''sprites'', 15 provenant de l'autre palette, la 32ème couleur fixe étant la couleur de transparence.
La NES utilise un système de palette indicé assez complexe pour réduire encore plus le nombre de bits utilisé pour encoder un ''sprite''. Pour simplifier, elle dispose de 4 palettes pour les ''sprites''. Chaque palette gère 4 couleurs chacune, dont l'une est la couleur transparente, ce qui fait 3 couleurs configurables. Chaque ''sprite'' précise quelle palette utiliser avec deux bits de configuration précisés dans le registre associé au ''sprite''. L'avantage est que cela réduit grandement la quantité de mémoire utilisée pour stocker un ''sprite'' : deux bits par pixel du motif, deux pixels pour préciser la palette utilisée pour le ''sprite''. Au total, cela fait maximum 13 couleurs différentes : 3 couleurs fois 4 palettes plus la couleur de transparence. Même principe pour la palette de l'arrière-plan, qui est elle aussi découpée en 4 sous-palettes avec chacune ayant sa propre copie de la couleur de transparence.
De rares consoles 8 bits disposent de plus de deux palettes. Par exemple, la PC-Engine a 32 palettes indicées de 16 couleurs chacune. La première palette est toujours consacrée à l'arrière-plan, la dernière est toujours réservée aux ''sprites'', les 30 palettes restantes sont utilisables à volonté par le programmeur. L'avantage est que l'on peut utiliser une palette différente par ''sprite'', au lieu d'avoir une palette indicée partagée par tous les ''sprites''. Mine de rien, trouver un ensemble de couleur partagé par plus d'une dizaine de ''sprite'' est assez compliqué. Les graphismes sont donc plus colorés, sans que cela demande d'utiliser une palette trop large. Et quand on veut modifier la palette à la volée pour un seul ''sprite'', cette implémentation avec des palettes séparées est bien plus simple.
Les consoles comme la NES n'avaient que 4 couleurs par ''sprite''. Mais il y avait moyen de contourner ces limitations en superposant plusieurs ''sprites'' de couleurs différentes. Par exemple, on peut simuler un ''sprite'' de 8 couleurs avec deux ''sprites'' de 4 couleurs chacun. Quelques consoles avaient même des fonctionnalités matérielles pour faciliter cette superposition. Par exemple, la MSX 2 fait automatiquement un OU entre deux ''sprites'' superposés.
===Le zoom et la rotation des ''sprites''===
Il a existé des VDC qui pouvaient zoomer sur des ''sprites'', voire les faire tourner. Le ZOOM des sprites permet de faire grossir ou de réduire la taille d'un sprite dynamiquement. L'effet de zoom pouvait servir pour générer des effets de profondeur. Par exemple, quand on veut simuler un personnage qui se rapproche de l'écran en marchant/courant, il suffit de prendre son sprite et de grossir le zoom progressivement pour donner l'illusion d'un personnage qui se rapproche. Idem pour n'importe quel sprite : plus le sprite est loin, plus on en réduit la taille. L'idée est de gérer la taille des sprites en fonction de la profondeur du sprite. L'effet donnait un rendu en pseudo-3D. On parle alors de '''''Sprite Scaling'''''.
Les implémentations les plus simples permettaient de faire multiplier les dimensions d'un sprite par 2, 4, 8 fois. En clair, la largeur et la longueur du sprite étaient multipliés par 2, 4, 8, etc. Il y avait possibilité de séparer zoom vertical et horizontal. Quelques VDC ne supportaient que le zoom horizontal, d'autre que en vertical, mais la plupart de ceux qui supportaient le zoom supportaient les deux.
[[File:IllustrationOfAPixelReplicationProcess.svg|centre|vignette|upright=2|ZOOM Entier sur un "sprite".]]
Les implémentations plus complexes supportaient un zoom plus fluide, avec tous les intermédiaires possibles entre *1 et *X. Pour cela, le sprite devait subir des opérations d'interpolation pour avoir un rendu agréable à l'écran. Pour comprendre ce que cela veut dire : imaginez que vous souhaitez multiplier la taille d'un sprite par X. Pour multiplier par 2, 3, 4 ou tout autre entier, cela demande juste de dupliquer chaque pixel. Mais pour les valeurs fractionnaires, les choses sont plus compliquées. Vous devrez appliquer un filtre pour faire le zoom, filtre qui prend les valeurs des pixels et les mélange pour calculer les pixels finaux. Nous reviendrons sur les filtres possibles dans le chapitre sur le filtrage de texture. Vous avez bien lu : le zoom des sprites n'est pas si différent du filtrage de texture, les algorithmes utilisés pour zoomer sur les sprites et filtrer des textures sont d'ailleurs très similaires, voire identiques.
===L'effet miroir ou ''flipping''===
Une autre possibilité est de faire tourner les sprites. La technique la plus simple est un '''effet de miroir''', à savoir que le sprite est inversé horizontalement, il est tourné dans l'autre sens. Pour vous donner une idée de l'utilité de cette technique, imaginez que vous jouez à Mario : si vous allez à droite, Mario sera tourné vers la droite. Mais si vous allez à gauche, le sprite sera tourné à gauche. Mais il s'agit d'un seul sprite, qui est tourné dans un sens ou l'autre avec un effet de miroir.
Il est aussi possible d'inverser le sprite verticalement, pour le retourner. Mais cette possibilité est plus rarement utilisée. La raison est que les rares jeux où cela pourrait être utile sont des jeux en vue de dessus, par exemple les Zelda, les Pokemon, et d'autres jeux dans le genre. Mais ils utilisent souvent un effet de perspective qui fait que la caméra n'est pas placée exactement à la verticale, ce qui fait qu'inverser un sprite verticalement ne respecte pas la perspective.
Toutes les consoles 8 bits supportaient l'effet de miroir directement en matériel, celui-ci étant très utile. Une des rares exceptions était la Master System de Sega. Sur cette dernière, le GPU était un VDC TMS9918 de Texas Instrument qui ne gérait pas l'effet miroir. Deux autres consoles utilisaient ce VDC : la colevision et la Sega SG-1000. Elles avaient le même problème. Cependant, il était possible de gérer l'effet miroir en utilisant le CPU.
L'avantage de la technique est qu'elle réduit le nombre de ''sprites'' nécessaires, ce qui entraine un gain en mémoire ROM. Les concepteurs peuvent ainsi faire rentrer un jeu sur une cartouche plus facilement, voire peuvent en profiter pour rajouter des ''sprites''. Mais un défaut de cette méthode est que les personnages donnent l'impression d'être ambidextres ! Au passage, sur la Master System, les concepteurs de jeu n'avaient pas d'autre choix que d'utiliser des ''sprites'' séparés pour les personnages/ennemis vus de gauche et vu de droite. Et on peut le remarquer au fait que les ennemis/personnages ne sont pas ambidextres, les artistes ayant souvent pris en compte de détail.
<noinclude>Au passage, j'en profite pour vous renvoyer vers ce lien, qui détaille l'utilisation de l'effet de miroir dans plusieurs jeux :
* [https://upsilandre.over-blog.com/2019/09/asymetrie-ou-ambidextrie-il-faut-choisir.html ASYMÉTRIE OU AMBIDEXTRIE, IL FAUT CHOISIR], du blog "Upsilandre Retrogaming".</noinclude>
===La rotation des ''sprites''===
La '''rotation des ''sprites''''' regroupe plusieurs techniques diverses, qui vont de simples effets de miroir à des techniques de rotation complexes. L'effet miroir est techniquement une rotation de ''sprite'' particulière, mais ce n'est pas la seule. Les techniques plus évoluées permettent de faire tourner un sprite, pour faire comme si on voyait un objet de biais. Un sprite rectangulaire représente un objet vu soit de profil, soit de face. Mais si on veut donner l'illusion d'un objet faisant un angle différent, il faut modifier la forme du sprite. Le sprite rectangulaire est transformé en trapèze, qui est d'autant moins proche d'un rectangle que l'angle est important. Déformer un sprite rectangulaire en trapèze est un effet de rotation général. Le tout donne un '''effet de perspective'''.
[[File:Mode 7 Test-0000.png|centre|vignette|Mode 7, test.]]
[[File:Mode 7 process.svg|vignette|upright=1|Mode 7.]]
L'implémentation de la perspective est assez simple. Le sprite est une image formée de plusieurs lignes de pixels. L'idée est que la taille des lignes est d'autant plus réduite que la ligne est censée être loin. Les portions proches du sprite seront de taille normale, les autres sont réduites. Mais la technique demande cependant de multiplier la taille de chaque ligne par un coefficient, qui n'est pas forcément entier. Là encore, des algorithmes permettent de lisser les images des sprites pour les mettre à l'échelle. L'algorithme peut faire à la fois la gestion de la perspective et le zoom des sprites, et utiliser des algorithmes proches de ceux du filtrage de textures. Une implémentation complexe est celle du ''mode 7'' de la Super Nintendo.
L'implémentation matérielle est paradoxalement assez simple. Mais la comprendre demande de faire la distinction entre les pixels du ''framebuffer'' et les pixels du ''sprite''. Le ''sprite'' est une image qui est composée de pixels. Pour faire la distinction avec les pixels du ''framebuffer'', nous allons appeler les pixels du ''sprite'' des '''texels'''. Terme qui est normalement utilisé pour les textures, mais il y a un lien qui sera fait dans quelques chapitres.
Placer le ''sprite'' sur l'arrière-plan demande de prendre les coordonnées de chaque ''texel'' et de faire un calcul qui donne les coordonnées finales x,Y dans le ''framebuffer''. Sans rotation et sans zoom, le calcul est simple : on additionne la position x,y du sprite et la coordonnée du texel dans le sprite. Pour faire tourner les sprites, il faut faire des calculs impliquant des matrices (des objets mathématiques en forme de tableaux de nombre), que je ne détaillerais pas du tout. Pour le dire très rapidement, il faut juste multiplier les coordonnées du texel par une matrice qui encode à la fois la rotation et le zoom, mais aussi le placement au bon endroit sur l'écran (la translation). La matrice est calculée par le processeur, le VDC ne fait que faire la multiplication matricielle.
==L'accélération matérielle du défilement==
[[File:Parallax scroll.gif|vignette|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 implémenté dans le CRTC===
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.
[[File:Implémentation du défilement vertical accéléré en hardware via un pointeur de framebuffer.png|vignette|Implémentation du défilement vertical accéléré en hardware via un pointeur de framebuffer]]
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. 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 !
Avec cette technique, il faut faire la différence entre le ''framebuffer'' et le '''''viewport'''''. Le ''viewport'' est la portion du ''framebuffer'' qui mémorise l'image à afficher à l'écran. Le ''framebuffer'', lui, mémorise plus que ce qu'il faut afficher à l'écran, il mémorise quelques lignes en plus, voire un niveau entier ! Le pointeur de ''framebuffer'' et la résolution indiquent la position du ''viewport'' dans le ''framebuffer'', qui monte ou descend en fonction du sens de défilement.
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 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.
===Le défilement horizontal et vertical implémenté par le CRTC===
Pour le défilement horizontal, il faut procéder de la même manière, mais en trichant un peu. Là encore, il y a une différence entre ''viewport'' et ''framebuffer'', et les deux n'ont pas la même résolution. Mais cette fois-ci, outre la résolution verticale qui est plus grande, la résolution horizontale l'est aussi. Par exemple, si je prends la console de jeux NES, elle a une résolution pour l'écran de 256 par 240, alors que l'arrière-plan a une résolution de 512 par 480.
L'implémentation utilise 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.
[[File:Implémentation du défilement horizontal avec in viewport.png|centre|vignette|upright=2|Implémentation du défilement horizontal avec in viewport]]
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. Malheureusement, le BIOS des cartes VGA n'implémentait pas les optimisations du défilement, et les programmeurs devaient tout coder à la main dans leurs applications pour les utiliser.
==Les ''raster effects'' : défilement partiel et ''sprites'' supplémentaires==
Les '''''raster effects''''' sont des effets graphiques qui sont implémentés en modifiant le VDC pendant l'affichage de l'image. Par exemple, il est possible de changer la couleur de l'arrière-plan à partir d'une certaine ligne, afin de séparer le ciel du sol. Un exemple classique est celui qui permet de contourner le nombre maximal de ''sprites'' à l'écran.
===La limite de ''sprites'' par écran===
Un VDC normal ne peut gérer qu'un nombre maximal de ''sprites'' par image/écran. Mais grâce aux ''raster effects'', il est possible de dépasser cette limite, en changeant les ''sprites'' à la volée, pendant que l'image s'affiche. Par exemple, les ''sprites'' déjà affichés sont recyclés et remplacés par d'autres ''sprites''. Leur coordonnée passe d'une portion affichée de l'image (typiquement en haut de l'écran) à une portion pas encore affichée (typiquement plus bas).
En faisant cela, on peut afficher plus de ''sprites'' que ce que gére le VDC. Par contre, on ne peut pas dépasser la limite de ''sprite'' par ''scanline''. Par exemple, si un VDC gère maximum 64 ''sprites'' par imaeg et 8 ''sprites'' par ligne, la première limite est facilement contournée via les ''raster effects'', mais pas la seconde. Autant changer des ''sprites'' entre l'affichage de deux lignes est possible, autant le faire pendant l'affichage d'une ligne ne l'est pas. En effet, le VDC n'est souvent pas reconfigurable par le processeur pendant l'affichage d'une ligne. Il l'est entre l'affichage de deux lignes, pas pendant.
===Le défilement partiel===
De nombreux effets graphiques demandent de ne faire défiler qu'une partie de l'écran, le reste de l'écran reste en place. Dans ce cas, nous parlerons de '''défilement partiel''', le terme partiel indiquant qu'il ne touche qu'une portion de l'image. De tels effets sont généralement implémentés avec des ''raster effect'', mais il faut que le matériel le supporte pleinement.
Pour donner un exemple, prenez les phases d’ascenseur dans les jeux de type ''beat'them up'', où votre personnage est sur une plateforme qui monte/descend, les ennemis arrivant dessus par vagues successives. Dans ce cas, la plateforme est immobile et le reste de l'image défile verticalement vers le haut (ascenseur descend) ou vers le bas (ascenseur monte).
Un cas particulier de défilement partiel est celui utilisé pour le HUD : les compteurs de vie, score, et autres. Il arrive que le HUD soit dessiné avec des ''sprites'', mais ce n'est pas la majorité des cas. En réalité, le HUD est placé dans l'arrière-plan directement. Et il ne doit pas être défilé, pour rester en place. par exemple, si le HUD est placé au sommet de l'écran, il doit y rester et ne pas subir de défilement vertical ou horizontal.
D'autres effets demandent de changer le défilement ligne par ligne : des effets de vagues, d'ondulation, ou autre. Par exemple, c'est ce qui permet de créer des virages sur les jeux de course ! Il n'y a qu'une seule image de route en ligne droite, qu'on décale à chaque ligne pour simuler un virage !
D'autres effets demandent un défilement partiel à la verticale. Il a été utilisé pour le HUD, les Boss de grande taille de certains jeux, des effets d'eau qui monte et bien d'autres encore. Voici un article qui explique le tout en détail :
* [https://upsilandre.over-blog.com/2019/07/les-arcanes-du-scrolling-vertical.html LES ARCANES DU SCROLLING VERTICAL SUR NES]
Un problème est que le défilement doit être reconfigurable lors de l'affichage de l'image. La quasi-totalité des consoles permettait cela, avec cependant quelques exceptions. Par exemple, la Master System permettait de changer les ''sprites'' à la volée, idem pour le défilement horizontal, mais ne permettait pas de changer le défilement vertical pendant l'affichage d'une image. Sur Master System, le registre de défilement vertical était pris en compte uniquement au début de l'affichage, le modifier en cours d'affichage n'avait pas le moindre effet sur l'affichage. On parle alors de '''''Vlock'''''. Il était possible de contourner le problème avec des solutions logicielles, avec un cout en performance et quelques limitations quant au résultat.
===L'implémentation des ''raster effects''===
Les VDC étaient souvent conçus avec les ''raster effects'' en tête. Ils supportent souvent des fonctionnalités qui facilitent l'implémentation des ''raster effects'' : compteurs de ligne, interruptions horizontales, et autres que nous allons voir dans ce qui suit. En théorie, les ''raster effects'' peuvent se faire même si le VDC ne supporte pas ces fonctionnalités. Mais cela demande de synchroniser le processeur et le VDC au cycle près, ou presque. Les programmeurs de l'Atari 2600 faisaient ainsi, mais il faut avouer que ce n'était pas pratique.
La première fonctionnalité est le '''compteur de ligne'''. Le nom est assez transparent : il s'agit d'un registre qui contient le numéro de la dernière ligne affichée. Par exemple, si le VDC a affiché la 20ème ligne et se prépare à afficher la 21ème, alors ce compteur contient le numéro 20. Le compteur de ligne permet au processeur/logiciel de savoir où il en est lors de l'affichage de l'écran. Il peut regarder régulièrement ce compteur et changer le défilement et/ou les ''sprites'' quand un certain numéro apparait dans ce compteur de ligne.
Le compteur de ligne en question existe déjà dans la plupart des VDC, vu qu'il s'agit de celui vu dans les chapitres précédents, qui sert pour l'adressage de la mémoire vidéo et quelques autres fonctionnalités. Mais il n'est pas forcément accesible par le processeur. Les VDC anciens ont un compteur de ligne interne, qui n'est accesible que par les circuits du VDC, mais le processeur n'y a pas accès. La fonctionnalité dont on parle ici rend ce registre visible par le processeur, un peu comme l'est le registre d'état.
Une seconde fonctionnalité, très liée au compteur de ligne, est l''''''horizontal blank interrupt''''', une cousine de l'interruption de ''vertical blank interrupt'' qui indique qu'une image complète a été affichée à l'écran. L'''horizontal blank interrupt'' fait la même chose, mais au niveau des lignes et d'une manière configurable. L'idée est que l'on peut demander au VDC de déclencher une interruption quand il atteint la ligne numéro X, par exemple quand il a fini d'afficher la 20ème ligne, la 50ème, etc. Le programmeur peut précise à quelle ligne se déclenche l'interruption. Pour le dire autrement, cette interruption se déclenche quand le compteur de ligne atteint une valeur bien précise, configurable par le programmeur.
Prenons l'exemple où programmeur veut afficher un HUD de 30 pixels de hauteur au-dessus d'un arrière-plan qui défile horizontalement. L'image ne défile pas initialement et le jeu affiche d'abord le HUD. Mais le programmeur a configuré l'interruption pour se déclencher à la 30ème ligne. L'interruption réactive le défilement de l'arrière-plan et change l'affichage pour basculer du HUD vers l'arrière-plan.
Une autre fonctionnalité, spécifique à la SNES, est le '''HDMA''', une amélioration de la fonctionnalité DMA (''Direct Memory Access''). Le DMA normal permet de faire des copies de la RAM vers la RAM, parfois de la RAM vers la mémoire vidéo (et inversement). Le '''HDMA''' de la SNES étend le comportement du DMA. Elle permet de faire copies RAM -> VDC, pour reconfigurer le VDC. De plus, les copies sont programmées à l'avance, elles ne se font pas en réaction à une interruption de ''blanking'' horizontal. Précisément, le VDC est reconfiguré à chaque période de ''blanking'' horizontal, entre l'affichage de deux lignes.
Pour cela, le processeur prépare une table HDMA qui précise comment configurer le VDC pour chaque ligne à l'écran. Pour simplifier, la table HDMA contient des entrées, chacune correspondant à une ligne de l'écran, qui indique quoi envoyer dans les registres de configuration du VDC. Le VDC lit les entrées de la première à la dernière, ligne par ligne. Le fonctionnement réel est un peu plus complexe. La table permet de conserver la même configuration pour N lignes consécutives, N allant de 1 à 128. Quand le VDC voit une de ces entrées, les données de configuration sont recopiées à l'identique pour N lignes consécutives.
==L'usage de plusieurs arrière-plans et d'avant-plans==
Une carte graphique 2D basique gére un arrière-plan et un avant-plan contenant plusieurs ''sprites'', avec éventuellement un avant-plan pour le HUD ou une interface sur laquelle afficher les vies, et d'autres informations. En tout, cela fait trois plans aux usages bien précis. Mais il est possible d'ajouter plusieurs arrière-plans, voir plusieurs avant-plans. L'utilité n'est pas évidente, mais cela sert pour de nombreux effets graphiques. Voyons lesquels.
===Les décors à l'avant-plan===
Il faut noter que les cartes graphiques peuvent aussi gérer des avant-plans, à savoir des décors qui passent devant les ''sprites''. Une forme d'avant-plan très utile est celui pour le HUD, les compteurs de vie, mana, munitions et autres. Implémenter un HUD avec du défilement est compliqué, car le HUD ne doit pas défiler : il doit rester à la même place sur l'écran, alors que le reste de l'écran défile.
Il faut noter que les avant-plans ne sont pas forcément séparés des arrière-plans. La seule différence entre les deux est la profondeur ! Et précisément la profondeur comparée à la couche pour les ''sprites''. Plus profond que les ''sprites'' : c'est un arrière-plan. Moins profond, c'est un avant-plan. Les cartes graphiques gèrent souvent des "couches" (''layers'') qui se superposent, avec une couche spécialisée pour les ''sprites'', et d'autres couches qu'on peut configurer à volonté comme arrière- ou avant-plan.
Les avant-plans sont aussi utilisés dans quelques jeux pour un effet purement esthétique, pour mettre un décor ou des objets devant les ''sprites''. De plus, ils sont occasionnellement nécessaires pour rendre certains effets graphiques, ou pour ajouter de la profondeur à une scène. Les avant-plans défilent à une vitesse différente des arrière-plans, sans quoi le résultat est absolument affreux. Aussi, l'usage d'avant-plans est très fortement corrélé à l'usage du défilement à parallaxe qu'on va voir dans ce qui suit.
===Le défilement horizontal à parallaxe===
[[File:Parallax-scroll-example-2.png|vignette|Défilement avec parallaxe.]]
[[File:Parallax-scroll-example.gif|vignette|Défilement avec parallaxe : illustration des plans.]]
Le '''défilement à parallaxe''' donne une illusion de profondeur dans l'environnement? Pour cela, il utilise plusieurs arrière-plans superposés qui défilent à des vitesses différentes. Le défilement à parallaxe demande que les éléments à l'arrière-plan bougent à des vitesses différentes, mais pas les sprites à l'avant-plan. Le défilement sans parallaxe fait bouger le ''framebuffer'' en bloc, ce qui fait bouger tout l'arrière-plan tout d'un coup.
Pour implémenter la technique, il est possible d'utiliser un défilement partiel basé sur des ''raster effects''. L'idée est de changer le défilement horizontal ou vertical pendant l'affichage. En général, l'écran est découpé en bandes qui défilent à des vitesses différentes, pour donner des effets parallaxe, parfois des effets de rouleau (le sol forme un tore) ou d'autres effets graphiques.
Une autre solution utilise des arrière-plans partiellement transparents qui se superposent. Les différents arrière-plans ont chacun une profondeur qui dit quel arrière-plan passe devant les autres. Il s'agit de l'implémentation la plus simple, mais elle demande que la carte graphique gère la transparence. Ce n'est cependant pas la seule option et quelques consoles de jeu se débrouillaient pour mélanger plusieurs arrière-plans sans pour autant gérer directement la transparence. Mais nous verrons cela plus tard.
===L'émulation de la transparence, des ombres et lumières===
L'usage de plusieurs arrière-plans marche bien si la carte graphique gère la transparence. C’est-à-dire que chaque pixel indique s'il est totalement opaque, totalement transparent, ou partiellement opaque. En faisant cela, déterminer quel pixel afficher est simple : on prend en compte la profondeur de chaque arrière-plan, et on détermine quel est le premier pixel visible. Par un exemple, si un pixel transparent est situé devant un pixel opaque, c'est ce dernier qui s'affiche. Pour les pixels partiellement transparents, il faut mélanger les couleurs des pixels en faisant une simple moyenne.
Mais cela implique que la carte graphique gère la transparence, que chaque pixel contienne une couleur alpha qui indique son niveau de transparence, couleur ajoutée aux couleurs RGB existantes. Des consoles comme la SNES utilisaient un système totalement différent. Elles utilisaient des arrière-plans dont les pixels étaient codés en RGB, sans composante de transparence alpha. Mais elles arrivaient à combiner plusieurs arrière-plans entre eux sans problèmes.
Les arrière-plans étaient combinés entre eux, ce qui donnait l'image finale à afficher. La combinaison pouvait utiliser plusieurs opérations, qui étaient appliquées pixel par pixel. Les opérations de combinaison sont : l'addition, la soustraction, la moyenne additive, et une soustraction suivie d'une division par deux. Pour résumer, on peut additionner/soustraire des pixels à la même place dans les différents arrière-plans, puis éventuellement diviser par deux le résultat. Précisons que les additions/soustractions sont dites saturées : elles n'ont pas de débordements d'entiers.
Les différentes opérations permettaient surtout d'implémenter des effets d'éclairage, d'ombrage, de transparence, voire de masquage (n'afficher qu'une partie de l'écran). L'addition ne peut qu'augmenter la couleur du pixel final, en augmenter la luminosité, ce qui la rend idéale pour appliquer des effets de lumière. À l'inverse, la soustraction ne peut que réduire la couleur finale d'un pixel, ce qui permet d'appliquer des ombres. La moyenne mélange deux pixels entre eux, qui sont à la même place à l'écran, mais n'ont pas la même profondeur, vu qu'ils sont dans des arrière-plans différents. En clair, elle applique des effets de transparences assez limités. Les pixels de deux arrière-plans sont mélangés avec une proportion fixe de 50-50. Ce qui permet d'appliquer des effets de transparence très limités.
Avec l'addition, il suffit de calculer les lumières à appliquer dans un arrière-plan, qui est additionné avec le reste de l'image. Pour appliquer des ombres, il faut faire la même chose mais avec la soustraction. Les ombres sont calculées dans un arrière-plan à part, qui est soustrait du résultat du reste. Pour être plus précis, l'arrière-plan contenait une image d'ombrage, qui indique quelles sont les parties de l'image dans l'ombre et l'intensité de l'ombre pour chaque pixel. Plus un pixel est dans l'ombre, plus sa couleur est proche du blanc (en RGB) : la palette de couleur est inversée. En soustrayant cette seconde image d'ombrage à l'image du ''framebuffer'' principal, on ombre les pixels.
Il faut noter que certaines consoles, comme la NES, permettaient en plus d'additionner une couleur fixe pour tous les pixels de l'écran, voire de la soustraire, voire de soustraire le pixel à cette couleur fixe. Cela permettait de biaiser les couleurs de base avec une couleur de base. Là encore, l'utilité était une question d'ombrage, d'éclairage, de transparence, ou autre. Cela permettait d'affiner les couleurs finales. La couleur de base était mémorisée dans un registre. Notons que le registre pouvait être changé entre l'affichage de deux lignes, via des ''raster interrupts'', ce qui permettait d'appliquer des dégradés de lumière verticaux, d'appliquer un effet d'horizon, d'ajouter du brouillard de distance.
===L'implémentation matérielle et le cas particulier de la SNES===
Utiliser plusieurs arrière-plans demande que le matériel soit adapté. Il faut typiquement ajouter de quoi gérer et mémoriser plusieurs arrière-plans et ajouter des circuits pour les combiner entre eux. Le hardware pour faire cette combinaison détermine quel pixel passe devant les autres, il peut gérer de la transparence, etc. Les ''sprites'' sont souvent gérés par un système de ''sprite'' matériel, ce qui fait que le défilement n'est pas tellement un problème pour eux.
Les arrières/avant-plans sont typiquement dans des mémoires séparées afin de faciliter la gestion du défilement . Avec plusieurs mémoires, la technique précédente (le ''framebuffer'' étendu et un viewport rectangulaire) peut s'appliquer à chaque arrière-plan. Et on peut les faire défiler à des vitesses différentes. Il est aussi possible d'utiliser une seule mémoire pour cela, qui mémorise plusieurs arrière-plans, c'est tout à fait possible.
La console de jeu SNES gérait 4 arrière-plans différents, mais elle n'utilisait pas plusieurs mémoires. Les 4 arrière-plans étaient combinés entre eux, ce qui donnait l'image finale à afficher. Il était possible d'activer ou de désactiver les arrière-plans si besoin. Il était par exemple possible de n'utiliser que 2 arrière-plans, ou 3 arrière-plans dont un soustrait de l'addition des deux autres, etc.
Elle disposait de plusieurs modes, appelés mode 1, mode 2, ..., mode 7. Le mode 7 est un mode de rendu avec un seul arrière-plan, mais où les techniques des sections précédentes sont utilisables. Les optimisations pour le défilement horizontal et vertical sont présentes, et le ''viewport'' peut même être déformé, tourné, etc. Il peut utiliser un second arrière-plan très particulier, dérivé du premier. Il utilise les mêmes tiles, avec cependant la possibilité de se retrouver devant les ''sprites'' ou les autres arrière-plans. Il était utilisé pour gérer des tunnels vus de dessus, ou pour quelques effets graphiques où des élèments de décor se retrouvaient à l'avant-plan.
{|class="wikitable"
|-
! Mode vidéo
! BG 1
! BG 2
! BG 3
! BG 4
|-
! 0
| 4 couleurs (2 bits) || 4 couleurs (2 bits) || 4 couleurs (2 bits) || 4 couleurs (2 bits)
|-
! 1
| 16 couleurs (4 bits) || 16 couleurs (4 bits) || 4 couleurs (2 bits) ||
|-
! 2
| 16 couleurs (4 bits) || 16 couleurs (4 bits) || ||
|-
! 3
| 256 couleurs (8 bits) || 16 couleurs (4 bits) || ||
|-
! 4
| 256 couleurs (8 bits) || 4 couleurs (2 bits) || ||
|-
! 5
| 16 couleurs (4 bits) || 4 couleurs (2 bits) || ||
|-
! 6
| 16 couleurs (4 bits) || || ||
|-
! 7
| 256 couleurs (8 bits) || EXTBG || ||
|}
==L'accélération matérielle du tracé de ligne et de figures géométriques==
<noinclude>[[File:Bresenham.svg|vignette|Tracé d'une ligne sur un écran.]]</noinclude>
La dernière optimisation du rendu 2D est le '''tracé de lignes et de figures géométriques''' accéléré en matériel. Quelques VDC incorporent cette optimisation, dont le nom est assez clair sur ce qu'elle fait. Tracer une ligne, un segment, est l'opération la plus courante sur de tels VDC, le tracé de cercles est déjà plus rare. Tracer des polygones est entre les deux, : plus rare que le tracé de ligne pur, moins que le tracé de cercles.
===Les circuits de tracé de ligne===
L'algorithme le plus utilisé par le matériel pour tracer des lignes est l''''algorithme de Bresenham'''. Il est très simple et s'implémente très facilement dans un circuit électronique. Il fut dit que cet algorithme utilise seulement des additions, des soustractions et des décalages, opérations très simples à implémenter en hardware. Il est de plus un des tout premiers algorithmes découvert dans le domaine du graphisme sur ordinateur. Il existe de nombreuses modifications de cet algorithme, qui vont de mineures à assez profondes. Et certaines d'entre elles sont plus faciles à implémenter en hardware que d'autres.
[[File:Bresenham line.png|vignette|Coordonnées du pixel de départ et d'arrivée.]]
[[File:LineBresenham.gif|vignette|Tracé d'une ligne sur un écran, pixel par pixel.]]
Le fonctionnement du circuit de tracé de ligne est le suivant. Premièrement, on précise le pixel de départ et le pixel d'arrivée en configurant des registres à l'intérieur du circuit, avec une paire de registre pour les coordonnées du pixel de départ, une autre parie pour les coordonnées du pixel d'arrivée. Le circuit dessine la ligne pixel par pixel, avec un pixel dessiné par cycle d'horloge.
===Le tracé de figures géométriques===
Passons maintenant au tracé de figures géométrique. Le tracé se fait là encore pixel par pixel, sur le principe. Généralement, le tracé est limité à des figures géométriques simples, que vous avez tous vu quand vous étiez au collège, en cours de maths. Tracer des carrés/rectangles/pentagones/trapèzes ou autres polygone est très simple : il suffit de tracer plusieurs lignes les unes à la suite des autres.
La vraie utilité est l'implémentation de courbes, comme des cercles, des ellipses, ou autres. L'algorithme de Bressenham peut être modifié pour implémenter des cercles, ce qui donne le '''''Midpoint circle algorithm'''''. D'autres extensions permettent de dessiner des ellipses, et même des courbes plus complexes comme des courbes de Bezier ou d'autres courbes assez complexes à expliquer.
===Le tracé et le remplissage de figures géométriques par le ''blitter''===
Outre le tracé des figures géométriques, il est aussi possible de gérer en hardware les opérations de '''remplissage'''. Cela veut dire dessiner l'intérieur d'une figure géométrique, comme remplir un carré ou un rectangle, avec une couleur uniforme. Par exemple : dessiner un carré en rouge, remplir un rectangle existant de bleu clair, etc. Le remplissage est souvent disponible pour certaines formes géométriques simples, comme des carrés ou rectangles, rarement plus. Pour le remplissage des triangles ou d'autres figures géométriques, le support matériel est encore plus rare, ne parlons même pas des cercles.
Le remplissage de rectangles est souvent réalisé par le ''blitter''. Pour faire une comparaison, la méthode utilisée est globalement la même que celle utilisée pour lire le ''viewport'' dans le ''framebuffer'', mais cette fois-ci réalisé par le ''blitter''. Le ''viewport'' est remplacé par le rectangle à remplir, et la lecture du pixel à envoyer à l'écran est remplacée par l'écriture d'une couleur précisée dans un registre.
Pour cela, on précise la couleur, la coordonnée X,Y et la largeur et la hauteur du rectangle dans des registres dédiés. Le remplissage commence à la coordonnée X,Y. L'adresse mémoire est alors incrémentée jusqu'à ce que la largeur voulue soit atteinte. On incrémente alors le compteur de ligne pour passer à la suivante, le compteur de colonne est réinitialisé avec la coordonnée X de la première colonne. Le remplissage s’arrête une fois que la hauteur voulue est atteinte.
Un exemple est le ''blitter'' des anciennes consoles Amiga, qui gère nativement des blocs en forme de rectangles, et de trapèzes dessinés à l'horizontale. Ils sont remplis en fournissant plusieurs informations : la position de leur premier pixel, 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. Le décalage est ajouté une fois le compteur de colonne réinitialisé à sa valeur précédente (au démarrage de la ligne précédente).
{{NavChapitre | book=Les cartes graphiques
| prev=Les systèmes à framebuffer
| prevText=Les systèmes à framebuffer
| next=Le mode texte et le rendu en tiles
| nextText=Le mode texte et le rendu en tiles
}}
{{autocat}}
jlljp00yr530bg06amo27lnkqjfmts9
774451
774450
2026-10-08T13:22:40Z
Mewtow
31375
/* La rotation des sprites */
774451
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu les CRTC. Il est maintenant temps de voir les '''''Video Interface Controlers''''', des VDC qui gèrent des fonctionnalités de rendu 2D avancées, voire plus. 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.
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'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|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 défilement 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 défilement, 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. Elles 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 défilement ou encore un support matériel du curseur de la souris, toutes dérivées des techniques d'accélération de rendu 2D.
==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é. D'ailleurs, il est souvent combiné à un contrôleur DMA, voire fusionné avec lui. Il pouvait non seulement faire des copies, mais aussi appliquer des opérations bit à bit sur les données copiées.
===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 place 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''.
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, prenons l'image ci-dessous à gauche et son masque à droite. Les parties blanches du masque 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 noirs. Le masque indique quels pixels sont à copier ou non, ce qui demande simplement 1 bit par pixel. Le pixel est associé dans le masque à une couleur noire ou blanche, pas de niveaux de gris permis.
{|
|[[File:Pacman--------2.gif|vignette|Image de Pacman.]]
|[[File:Pacman mask.gif|vignette|Masque de Pacman.]]
|}
Le ''blitter'' combine le ''sprite'', l'arrière-plan et le masque. Pour chaque pixel, il effectue l'opération suivante : ((arrière-plan) AND (masque)) OR (image de pacman).
[[File:Sprite rendering by binary image mask.png|centre|vignette|upright=2|Sprite rendering by binary image mask]]
Imaginons qu'on veut placer l'image ci-dessus (le point vert), en plusieurs endroits de l'arrière-plan.
[[File:XBlit dot.png|centre|vignette|upright=3|Image et masque.]]
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é, les parties noires sont ignorées. Au final, l'image finale est bel et bien celle qu'on attend.
{|
|[[File:Blit back.png|vignette|upright=3|Arrière-plan de l'exemple.]]
|[[File:XBlit and.png|vignette|upright=3|Application du masque - ET.]]
|[[File:XBlit final.png|vignette|upright=3|Application des ''sprites'' - OU.]]
|}
==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'accès au ''framebuffer'' par le CRTC.
Il faut noter que les ''sprites'' matériel ont une taille assez petite. Ils sont typiquement des carrés de 8 pixels de côté, rarement plus. Par contre, les ''sprites'' utilisés dans les jeux vidéos sont souvent plus grands, certains font 16 pixels de côtés, parfois plus. Aussi, les ''sprites'' d'un jeu vidéo sont parfois composés de plusieurs ''sprites'' matériels placés les uns à côté des autres. Par exemple, un ''sprite'' de 16 pixels de hauteur et de 8 pixels de largeur est composé de deux ''sprites'' 8 par 8, placés l'un au-dessus de l'autre.
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 pour respectivement 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, 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 !
===Les circuits d'accélération matérielle des ''sprites''===
Avec une implémentation très simple, les ''sprites'' sont stockés dans des registres, alors que l'image d'arrière-plan est stockée dans un ''framebuffer'', dans une mémoire 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. 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.
[[File:Hardware sprites.png|centre|vignette|upright=2.0|Sprites matériels.]]
Les jeux vidéos des consoles 8 ou 16 bits ne mémorisaient pas les ''sprites'' directement dans des registres. Les ''sprites'' étaient dans la cartouche de jeu, donc dans une mémoire ROM. Les registres de ''sprites'' mémorisaient l'adresse du ''sprite'', donc sa position dans la mémoire ROM. Les ''sprites'' étaient lus depuis la cartouche en cas de ''match'' avec les registres X et Y.
Les ''sprites'' allaient presque toujours de pair avec 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. 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.
Les VDC avaient des limitations en termes de ''sprites'' matériels supportés. Notamment, ils géraient un nombre maximal de ''sprites'' par image. Cette limitation par image était secondée par une limitation du nombre de ''sprites'' par ligne. Par exemple, la NES et la Master Sytem géraient 64 ''sprites'' par image, avec maximum 8 ''sprites'' par ligne. Cependant, ces limitations pouvaient être contournées par l'usage de ''raster interrupts'', et précisément l'''horizontal blank interrupt'' qui précise quand l'affichage d'une ligne précise est terminée. L'idée est de changer les ''sprites'' d'une image et de les repositionner, pendant le tracé de l'image, entre l'affichage de deux lignes. Les ''sprites'' qui ont déjà été affichés en haut de l'écran sont remplacés par des ''sprites'' à afficher plus bas. Les programmeurs utilisaient ce genre de ruses pour afficher plus de ''sprites'' à l'écran que ne peut en supporter la console.
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 ''sprites'' 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''.
Il faut noter que certaines consoles géraient des ''sprites'' situés sous l'arrière-plan. Quelques effets graphiques le requéraient. Par exemple, imaginez un personnage qui passe derrière un arbre. L'arbre est dessiné dans l'arrière-plan, afin d'économiser des ''sprites'', alors que le personage est composé de plusieurs ''sprites''. Avec des ''sprites'' pouvant passer derrière l'arrière-plan, on peut simuler cet effet de personnage qui passe derrière l'arbre, donc sous l'arrière-plan.
===La palette indicée des ''sprites''===
Certaines consoles de jeux utilisaient à la fois des ''sprites'' et une palette indicée. En soi, rien d'incompatible, les deux techniques sont totalement orthogonales. Mais il y a des consoles qui utilisaient deux palettes séparées : une pour les ''sprites'', l'autre pour l'arrière-plan. En général, les deux palettes ont une couleur commune : la couleur transparente. Elle est codée deux fois, une fois dans chaque palette. Mais les autres couleurs des palettes sont potentiellement différentes dans les deux palettes. L'usage de deux palettes indicées séparées est assez commun sur les consoles 8 bits.
L'utilité de ce système est de gérer deux fois plus de couleurs, tout en réduisant les besoins en mémoire. En théorie, on pourrait gérer deux fois plus de couleurs en utilisant une palette unique deux fois plus grande, ce qui serait plus flexible. Mais en utilisant une palette deux fois plus grande, on doit ajouter un bit en plus par pixel, pour encoder deux fois plus de couleurs. Avec deux palettes séparées pour les ''sprites'' et l'arrière-plan, pas besoin : le bit est implicite, il vaut 0 pour les ''sprites'' et 1 pour l'arrière-plan.
La Master System a deux palettes : une réservée à l'arrière-plan, l'autre servant à la fois pour les ''sprites'' et le décor. Les ''sprites'' ont à disposition 16 couleurs qu'ils peuvent configurer comme ils le souhaitent. La couleur de transparence n'est pas obligatoire. À l'inverse, la palette pour l'arrière-plan dispose de 15 couleurs configurables, et une couleur de transparence fixe. L'arrière-plan a donc à sa disposition 31 couleurs configurables : 16 provenant de la palette des ''sprites'', 15 provenant de l'autre palette, la 32ème couleur fixe étant la couleur de transparence.
La NES utilise un système de palette indicé assez complexe pour réduire encore plus le nombre de bits utilisé pour encoder un ''sprite''. Pour simplifier, elle dispose de 4 palettes pour les ''sprites''. Chaque palette gère 4 couleurs chacune, dont l'une est la couleur transparente, ce qui fait 3 couleurs configurables. Chaque ''sprite'' précise quelle palette utiliser avec deux bits de configuration précisés dans le registre associé au ''sprite''. L'avantage est que cela réduit grandement la quantité de mémoire utilisée pour stocker un ''sprite'' : deux bits par pixel du motif, deux pixels pour préciser la palette utilisée pour le ''sprite''. Au total, cela fait maximum 13 couleurs différentes : 3 couleurs fois 4 palettes plus la couleur de transparence. Même principe pour la palette de l'arrière-plan, qui est elle aussi découpée en 4 sous-palettes avec chacune ayant sa propre copie de la couleur de transparence.
De rares consoles 8 bits disposent de plus de deux palettes. Par exemple, la PC-Engine a 32 palettes indicées de 16 couleurs chacune. La première palette est toujours consacrée à l'arrière-plan, la dernière est toujours réservée aux ''sprites'', les 30 palettes restantes sont utilisables à volonté par le programmeur. L'avantage est que l'on peut utiliser une palette différente par ''sprite'', au lieu d'avoir une palette indicée partagée par tous les ''sprites''. Mine de rien, trouver un ensemble de couleur partagé par plus d'une dizaine de ''sprite'' est assez compliqué. Les graphismes sont donc plus colorés, sans que cela demande d'utiliser une palette trop large. Et quand on veut modifier la palette à la volée pour un seul ''sprite'', cette implémentation avec des palettes séparées est bien plus simple.
Les consoles comme la NES n'avaient que 4 couleurs par ''sprite''. Mais il y avait moyen de contourner ces limitations en superposant plusieurs ''sprites'' de couleurs différentes. Par exemple, on peut simuler un ''sprite'' de 8 couleurs avec deux ''sprites'' de 4 couleurs chacun. Quelques consoles avaient même des fonctionnalités matérielles pour faciliter cette superposition. Par exemple, la MSX 2 fait automatiquement un OU entre deux ''sprites'' superposés.
===Le zoom et la rotation des ''sprites''===
Il a existé des VDC qui pouvaient zoomer sur des ''sprites'', voire les faire tourner. Le ZOOM des sprites permet de faire grossir ou de réduire la taille d'un sprite dynamiquement. L'effet de zoom pouvait servir pour générer des effets de profondeur. Par exemple, quand on veut simuler un personnage qui se rapproche de l'écran en marchant/courant, il suffit de prendre son sprite et de grossir le zoom progressivement pour donner l'illusion d'un personnage qui se rapproche. Idem pour n'importe quel sprite : plus le sprite est loin, plus on en réduit la taille. L'idée est de gérer la taille des sprites en fonction de la profondeur du sprite. L'effet donnait un rendu en pseudo-3D. On parle alors de '''''Sprite Scaling'''''.
Les implémentations les plus simples permettaient de faire multiplier les dimensions d'un sprite par 2, 4, 8 fois. En clair, la largeur et la longueur du sprite étaient multipliés par 2, 4, 8, etc. Il y avait possibilité de séparer zoom vertical et horizontal. Quelques VDC ne supportaient que le zoom horizontal, d'autre que en vertical, mais la plupart de ceux qui supportaient le zoom supportaient les deux.
[[File:IllustrationOfAPixelReplicationProcess.svg|centre|vignette|upright=2|ZOOM Entier sur un "sprite".]]
Les implémentations plus complexes supportaient un zoom plus fluide, avec tous les intermédiaires possibles entre *1 et *X. Pour cela, le sprite devait subir des opérations d'interpolation pour avoir un rendu agréable à l'écran. Pour comprendre ce que cela veut dire : imaginez que vous souhaitez multiplier la taille d'un sprite par X. Pour multiplier par 2, 3, 4 ou tout autre entier, cela demande juste de dupliquer chaque pixel. Mais pour les valeurs fractionnaires, les choses sont plus compliquées. Vous devrez appliquer un filtre pour faire le zoom, filtre qui prend les valeurs des pixels et les mélange pour calculer les pixels finaux. Nous reviendrons sur les filtres possibles dans le chapitre sur le filtrage de texture. Vous avez bien lu : le zoom des sprites n'est pas si différent du filtrage de texture, les algorithmes utilisés pour zoomer sur les sprites et filtrer des textures sont d'ailleurs très similaires, voire identiques.
===L'effet miroir ou ''flipping''===
Une autre possibilité est de faire tourner les sprites. La technique la plus simple est un '''effet de miroir''', à savoir que le sprite est inversé horizontalement, il est tourné dans l'autre sens. Pour vous donner une idée de l'utilité de cette technique, imaginez que vous jouez à Mario : si vous allez à droite, Mario sera tourné vers la droite. Mais si vous allez à gauche, le sprite sera tourné à gauche. Mais il s'agit d'un seul sprite, qui est tourné dans un sens ou l'autre avec un effet de miroir.
Il est aussi possible d'inverser le sprite verticalement, pour le retourner. Mais cette possibilité est plus rarement utilisée. La raison est que les rares jeux où cela pourrait être utile sont des jeux en vue de dessus, par exemple les Zelda, les Pokemon, et d'autres jeux dans le genre. Mais ils utilisent souvent un effet de perspective qui fait que la caméra n'est pas placée exactement à la verticale, ce qui fait qu'inverser un sprite verticalement ne respecte pas la perspective.
Toutes les consoles 8 bits supportaient l'effet de miroir directement en matériel, celui-ci étant très utile. Une des rares exceptions était la Master System de Sega. Sur cette dernière, le GPU était un VDC TMS9918 de Texas Instrument qui ne gérait pas l'effet miroir. Deux autres consoles utilisaient ce VDC : la colevision et la Sega SG-1000. Elles avaient le même problème. Cependant, il était possible de gérer l'effet miroir en utilisant le CPU.
L'avantage de la technique est qu'elle réduit le nombre de ''sprites'' nécessaires, ce qui entraine un gain en mémoire ROM. Les concepteurs peuvent ainsi faire rentrer un jeu sur une cartouche plus facilement, voire peuvent en profiter pour rajouter des ''sprites''. Mais un défaut de cette méthode est que les personnages donnent l'impression d'être ambidextres ! Au passage, sur la Master System, les concepteurs de jeu n'avaient pas d'autre choix que d'utiliser des ''sprites'' séparés pour les personnages/ennemis vus de gauche et vu de droite. Et on peut le remarquer au fait que les ennemis/personnages ne sont pas ambidextres, les artistes ayant souvent pris en compte de détail.
<noinclude>Au passage, j'en profite pour vous renvoyer vers ce lien, qui détaille l'utilisation de l'effet de miroir dans plusieurs jeux :
* [https://upsilandre.over-blog.com/2019/09/asymetrie-ou-ambidextrie-il-faut-choisir.html ASYMÉTRIE OU AMBIDEXTRIE, IL FAUT CHOISIR], du blog "Upsilandre Retrogaming".</noinclude>
===La rotation des ''sprites''===
La '''rotation des ''sprites''''' regroupe plusieurs techniques diverses, qui vont de simples effets de miroir à des techniques de rotation complexes. L'effet miroir est techniquement une rotation de ''sprite'' particulière, mais ce n'est pas la seule. Les techniques plus évoluées permettent de faire tourner un sprite, pour faire comme si on voyait un objet de biais. Un sprite rectangulaire représente un objet vu soit de profil, soit de face. Mais si on veut donner l'illusion d'un objet faisant un angle différent, il faut modifier la forme du sprite. Le sprite rectangulaire est transformé en trapèze, qui est d'autant moins proche d'un rectangle que l'angle est important. Déformer un sprite rectangulaire en trapèze est un effet de rotation général. Le tout donne un '''effet de perspective'''.
[[File:Mode 7 Test-0000.png|centre|vignette|Mode 7, test.]]
[[File:Mode 7 process.svg|vignette|upright=1|Mode 7.]]
L'implémentation de la perspective est assez simple. Le sprite est une image formée de plusieurs lignes de pixels. L'idée est que la taille des lignes est d'autant plus réduite que la ligne est censée être loin. Les portions proches du sprite seront de taille normale, les autres sont réduites. Mais la technique demande cependant de multiplier la taille de chaque ligne par un coefficient, qui n'est pas forcément entier. Là encore, des algorithmes permettent de lisser les images des sprites pour les mettre à l'échelle. L'algorithme peut faire à la fois la gestion de la perspective et le zoom des sprites, et utiliser des algorithmes proches de ceux du filtrage de textures. Une implémentation complexe est celle du ''mode 7'' de la Super Nintendo, qui effectuait des rotations sur l'arrière-plan (pas les ''sprites'').
L'implémentation matérielle est paradoxalement assez simple. Mais la comprendre demande de faire la distinction entre les pixels du ''framebuffer'' et les pixels du ''sprite''. Le ''sprite'' est une image qui est composée de pixels. Pour faire la distinction avec les pixels du ''framebuffer'', nous allons appeler les pixels du ''sprite'' des '''texels'''. Terme qui est normalement utilisé pour les textures, mais il y a un lien qui sera fait dans quelques chapitres.
Placer le ''sprite'' sur l'arrière-plan demande de prendre les coordonnées de chaque ''texel'' et de faire un calcul qui donne les coordonnées finales x,Y dans le ''framebuffer''. Sans rotation et sans zoom, le calcul est simple : on additionne la position x,y du sprite et la coordonnée du texel dans le sprite. Pour faire tourner les sprites, il faut faire des calculs impliquant des matrices (des objets mathématiques en forme de tableaux de nombre), que je ne détaillerais pas du tout. Pour le dire très rapidement, il faut juste multiplier les coordonnées du texel par une matrice qui encode à la fois la rotation et le zoom, mais aussi le placement au bon endroit sur l'écran (la translation). La matrice est calculée par le processeur, le VDC ne fait que faire la multiplication matricielle.
==L'accélération matérielle du défilement==
[[File:Parallax scroll.gif|vignette|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 implémenté dans le CRTC===
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.
[[File:Implémentation du défilement vertical accéléré en hardware via un pointeur de framebuffer.png|vignette|Implémentation du défilement vertical accéléré en hardware via un pointeur de framebuffer]]
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. 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 !
Avec cette technique, il faut faire la différence entre le ''framebuffer'' et le '''''viewport'''''. Le ''viewport'' est la portion du ''framebuffer'' qui mémorise l'image à afficher à l'écran. Le ''framebuffer'', lui, mémorise plus que ce qu'il faut afficher à l'écran, il mémorise quelques lignes en plus, voire un niveau entier ! Le pointeur de ''framebuffer'' et la résolution indiquent la position du ''viewport'' dans le ''framebuffer'', qui monte ou descend en fonction du sens de défilement.
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 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.
===Le défilement horizontal et vertical implémenté par le CRTC===
Pour le défilement horizontal, il faut procéder de la même manière, mais en trichant un peu. Là encore, il y a une différence entre ''viewport'' et ''framebuffer'', et les deux n'ont pas la même résolution. Mais cette fois-ci, outre la résolution verticale qui est plus grande, la résolution horizontale l'est aussi. Par exemple, si je prends la console de jeux NES, elle a une résolution pour l'écran de 256 par 240, alors que l'arrière-plan a une résolution de 512 par 480.
L'implémentation utilise 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.
[[File:Implémentation du défilement horizontal avec in viewport.png|centre|vignette|upright=2|Implémentation du défilement horizontal avec in viewport]]
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. Malheureusement, le BIOS des cartes VGA n'implémentait pas les optimisations du défilement, et les programmeurs devaient tout coder à la main dans leurs applications pour les utiliser.
==Les ''raster effects'' : défilement partiel et ''sprites'' supplémentaires==
Les '''''raster effects''''' sont des effets graphiques qui sont implémentés en modifiant le VDC pendant l'affichage de l'image. Par exemple, il est possible de changer la couleur de l'arrière-plan à partir d'une certaine ligne, afin de séparer le ciel du sol. Un exemple classique est celui qui permet de contourner le nombre maximal de ''sprites'' à l'écran.
===La limite de ''sprites'' par écran===
Un VDC normal ne peut gérer qu'un nombre maximal de ''sprites'' par image/écran. Mais grâce aux ''raster effects'', il est possible de dépasser cette limite, en changeant les ''sprites'' à la volée, pendant que l'image s'affiche. Par exemple, les ''sprites'' déjà affichés sont recyclés et remplacés par d'autres ''sprites''. Leur coordonnée passe d'une portion affichée de l'image (typiquement en haut de l'écran) à une portion pas encore affichée (typiquement plus bas).
En faisant cela, on peut afficher plus de ''sprites'' que ce que gére le VDC. Par contre, on ne peut pas dépasser la limite de ''sprite'' par ''scanline''. Par exemple, si un VDC gère maximum 64 ''sprites'' par imaeg et 8 ''sprites'' par ligne, la première limite est facilement contournée via les ''raster effects'', mais pas la seconde. Autant changer des ''sprites'' entre l'affichage de deux lignes est possible, autant le faire pendant l'affichage d'une ligne ne l'est pas. En effet, le VDC n'est souvent pas reconfigurable par le processeur pendant l'affichage d'une ligne. Il l'est entre l'affichage de deux lignes, pas pendant.
===Le défilement partiel===
De nombreux effets graphiques demandent de ne faire défiler qu'une partie de l'écran, le reste de l'écran reste en place. Dans ce cas, nous parlerons de '''défilement partiel''', le terme partiel indiquant qu'il ne touche qu'une portion de l'image. De tels effets sont généralement implémentés avec des ''raster effect'', mais il faut que le matériel le supporte pleinement.
Pour donner un exemple, prenez les phases d’ascenseur dans les jeux de type ''beat'them up'', où votre personnage est sur une plateforme qui monte/descend, les ennemis arrivant dessus par vagues successives. Dans ce cas, la plateforme est immobile et le reste de l'image défile verticalement vers le haut (ascenseur descend) ou vers le bas (ascenseur monte).
Un cas particulier de défilement partiel est celui utilisé pour le HUD : les compteurs de vie, score, et autres. Il arrive que le HUD soit dessiné avec des ''sprites'', mais ce n'est pas la majorité des cas. En réalité, le HUD est placé dans l'arrière-plan directement. Et il ne doit pas être défilé, pour rester en place. par exemple, si le HUD est placé au sommet de l'écran, il doit y rester et ne pas subir de défilement vertical ou horizontal.
D'autres effets demandent de changer le défilement ligne par ligne : des effets de vagues, d'ondulation, ou autre. Par exemple, c'est ce qui permet de créer des virages sur les jeux de course ! Il n'y a qu'une seule image de route en ligne droite, qu'on décale à chaque ligne pour simuler un virage !
D'autres effets demandent un défilement partiel à la verticale. Il a été utilisé pour le HUD, les Boss de grande taille de certains jeux, des effets d'eau qui monte et bien d'autres encore. Voici un article qui explique le tout en détail :
* [https://upsilandre.over-blog.com/2019/07/les-arcanes-du-scrolling-vertical.html LES ARCANES DU SCROLLING VERTICAL SUR NES]
Un problème est que le défilement doit être reconfigurable lors de l'affichage de l'image. La quasi-totalité des consoles permettait cela, avec cependant quelques exceptions. Par exemple, la Master System permettait de changer les ''sprites'' à la volée, idem pour le défilement horizontal, mais ne permettait pas de changer le défilement vertical pendant l'affichage d'une image. Sur Master System, le registre de défilement vertical était pris en compte uniquement au début de l'affichage, le modifier en cours d'affichage n'avait pas le moindre effet sur l'affichage. On parle alors de '''''Vlock'''''. Il était possible de contourner le problème avec des solutions logicielles, avec un cout en performance et quelques limitations quant au résultat.
===L'implémentation des ''raster effects''===
Les VDC étaient souvent conçus avec les ''raster effects'' en tête. Ils supportent souvent des fonctionnalités qui facilitent l'implémentation des ''raster effects'' : compteurs de ligne, interruptions horizontales, et autres que nous allons voir dans ce qui suit. En théorie, les ''raster effects'' peuvent se faire même si le VDC ne supporte pas ces fonctionnalités. Mais cela demande de synchroniser le processeur et le VDC au cycle près, ou presque. Les programmeurs de l'Atari 2600 faisaient ainsi, mais il faut avouer que ce n'était pas pratique.
La première fonctionnalité est le '''compteur de ligne'''. Le nom est assez transparent : il s'agit d'un registre qui contient le numéro de la dernière ligne affichée. Par exemple, si le VDC a affiché la 20ème ligne et se prépare à afficher la 21ème, alors ce compteur contient le numéro 20. Le compteur de ligne permet au processeur/logiciel de savoir où il en est lors de l'affichage de l'écran. Il peut regarder régulièrement ce compteur et changer le défilement et/ou les ''sprites'' quand un certain numéro apparait dans ce compteur de ligne.
Le compteur de ligne en question existe déjà dans la plupart des VDC, vu qu'il s'agit de celui vu dans les chapitres précédents, qui sert pour l'adressage de la mémoire vidéo et quelques autres fonctionnalités. Mais il n'est pas forcément accesible par le processeur. Les VDC anciens ont un compteur de ligne interne, qui n'est accesible que par les circuits du VDC, mais le processeur n'y a pas accès. La fonctionnalité dont on parle ici rend ce registre visible par le processeur, un peu comme l'est le registre d'état.
Une seconde fonctionnalité, très liée au compteur de ligne, est l''''''horizontal blank interrupt''''', une cousine de l'interruption de ''vertical blank interrupt'' qui indique qu'une image complète a été affichée à l'écran. L'''horizontal blank interrupt'' fait la même chose, mais au niveau des lignes et d'une manière configurable. L'idée est que l'on peut demander au VDC de déclencher une interruption quand il atteint la ligne numéro X, par exemple quand il a fini d'afficher la 20ème ligne, la 50ème, etc. Le programmeur peut précise à quelle ligne se déclenche l'interruption. Pour le dire autrement, cette interruption se déclenche quand le compteur de ligne atteint une valeur bien précise, configurable par le programmeur.
Prenons l'exemple où programmeur veut afficher un HUD de 30 pixels de hauteur au-dessus d'un arrière-plan qui défile horizontalement. L'image ne défile pas initialement et le jeu affiche d'abord le HUD. Mais le programmeur a configuré l'interruption pour se déclencher à la 30ème ligne. L'interruption réactive le défilement de l'arrière-plan et change l'affichage pour basculer du HUD vers l'arrière-plan.
Une autre fonctionnalité, spécifique à la SNES, est le '''HDMA''', une amélioration de la fonctionnalité DMA (''Direct Memory Access''). Le DMA normal permet de faire des copies de la RAM vers la RAM, parfois de la RAM vers la mémoire vidéo (et inversement). Le '''HDMA''' de la SNES étend le comportement du DMA. Elle permet de faire copies RAM -> VDC, pour reconfigurer le VDC. De plus, les copies sont programmées à l'avance, elles ne se font pas en réaction à une interruption de ''blanking'' horizontal. Précisément, le VDC est reconfiguré à chaque période de ''blanking'' horizontal, entre l'affichage de deux lignes.
Pour cela, le processeur prépare une table HDMA qui précise comment configurer le VDC pour chaque ligne à l'écran. Pour simplifier, la table HDMA contient des entrées, chacune correspondant à une ligne de l'écran, qui indique quoi envoyer dans les registres de configuration du VDC. Le VDC lit les entrées de la première à la dernière, ligne par ligne. Le fonctionnement réel est un peu plus complexe. La table permet de conserver la même configuration pour N lignes consécutives, N allant de 1 à 128. Quand le VDC voit une de ces entrées, les données de configuration sont recopiées à l'identique pour N lignes consécutives.
==L'usage de plusieurs arrière-plans et d'avant-plans==
Une carte graphique 2D basique gére un arrière-plan et un avant-plan contenant plusieurs ''sprites'', avec éventuellement un avant-plan pour le HUD ou une interface sur laquelle afficher les vies, et d'autres informations. En tout, cela fait trois plans aux usages bien précis. Mais il est possible d'ajouter plusieurs arrière-plans, voir plusieurs avant-plans. L'utilité n'est pas évidente, mais cela sert pour de nombreux effets graphiques. Voyons lesquels.
===Les décors à l'avant-plan===
Il faut noter que les cartes graphiques peuvent aussi gérer des avant-plans, à savoir des décors qui passent devant les ''sprites''. Une forme d'avant-plan très utile est celui pour le HUD, les compteurs de vie, mana, munitions et autres. Implémenter un HUD avec du défilement est compliqué, car le HUD ne doit pas défiler : il doit rester à la même place sur l'écran, alors que le reste de l'écran défile.
Il faut noter que les avant-plans ne sont pas forcément séparés des arrière-plans. La seule différence entre les deux est la profondeur ! Et précisément la profondeur comparée à la couche pour les ''sprites''. Plus profond que les ''sprites'' : c'est un arrière-plan. Moins profond, c'est un avant-plan. Les cartes graphiques gèrent souvent des "couches" (''layers'') qui se superposent, avec une couche spécialisée pour les ''sprites'', et d'autres couches qu'on peut configurer à volonté comme arrière- ou avant-plan.
Les avant-plans sont aussi utilisés dans quelques jeux pour un effet purement esthétique, pour mettre un décor ou des objets devant les ''sprites''. De plus, ils sont occasionnellement nécessaires pour rendre certains effets graphiques, ou pour ajouter de la profondeur à une scène. Les avant-plans défilent à une vitesse différente des arrière-plans, sans quoi le résultat est absolument affreux. Aussi, l'usage d'avant-plans est très fortement corrélé à l'usage du défilement à parallaxe qu'on va voir dans ce qui suit.
===Le défilement horizontal à parallaxe===
[[File:Parallax-scroll-example-2.png|vignette|Défilement avec parallaxe.]]
[[File:Parallax-scroll-example.gif|vignette|Défilement avec parallaxe : illustration des plans.]]
Le '''défilement à parallaxe''' donne une illusion de profondeur dans l'environnement? Pour cela, il utilise plusieurs arrière-plans superposés qui défilent à des vitesses différentes. Le défilement à parallaxe demande que les éléments à l'arrière-plan bougent à des vitesses différentes, mais pas les sprites à l'avant-plan. Le défilement sans parallaxe fait bouger le ''framebuffer'' en bloc, ce qui fait bouger tout l'arrière-plan tout d'un coup.
Pour implémenter la technique, il est possible d'utiliser un défilement partiel basé sur des ''raster effects''. L'idée est de changer le défilement horizontal ou vertical pendant l'affichage. En général, l'écran est découpé en bandes qui défilent à des vitesses différentes, pour donner des effets parallaxe, parfois des effets de rouleau (le sol forme un tore) ou d'autres effets graphiques.
Une autre solution utilise des arrière-plans partiellement transparents qui se superposent. Les différents arrière-plans ont chacun une profondeur qui dit quel arrière-plan passe devant les autres. Il s'agit de l'implémentation la plus simple, mais elle demande que la carte graphique gère la transparence. Ce n'est cependant pas la seule option et quelques consoles de jeu se débrouillaient pour mélanger plusieurs arrière-plans sans pour autant gérer directement la transparence. Mais nous verrons cela plus tard.
===L'émulation de la transparence, des ombres et lumières===
L'usage de plusieurs arrière-plans marche bien si la carte graphique gère la transparence. C’est-à-dire que chaque pixel indique s'il est totalement opaque, totalement transparent, ou partiellement opaque. En faisant cela, déterminer quel pixel afficher est simple : on prend en compte la profondeur de chaque arrière-plan, et on détermine quel est le premier pixel visible. Par un exemple, si un pixel transparent est situé devant un pixel opaque, c'est ce dernier qui s'affiche. Pour les pixels partiellement transparents, il faut mélanger les couleurs des pixels en faisant une simple moyenne.
Mais cela implique que la carte graphique gère la transparence, que chaque pixel contienne une couleur alpha qui indique son niveau de transparence, couleur ajoutée aux couleurs RGB existantes. Des consoles comme la SNES utilisaient un système totalement différent. Elles utilisaient des arrière-plans dont les pixels étaient codés en RGB, sans composante de transparence alpha. Mais elles arrivaient à combiner plusieurs arrière-plans entre eux sans problèmes.
Les arrière-plans étaient combinés entre eux, ce qui donnait l'image finale à afficher. La combinaison pouvait utiliser plusieurs opérations, qui étaient appliquées pixel par pixel. Les opérations de combinaison sont : l'addition, la soustraction, la moyenne additive, et une soustraction suivie d'une division par deux. Pour résumer, on peut additionner/soustraire des pixels à la même place dans les différents arrière-plans, puis éventuellement diviser par deux le résultat. Précisons que les additions/soustractions sont dites saturées : elles n'ont pas de débordements d'entiers.
Les différentes opérations permettaient surtout d'implémenter des effets d'éclairage, d'ombrage, de transparence, voire de masquage (n'afficher qu'une partie de l'écran). L'addition ne peut qu'augmenter la couleur du pixel final, en augmenter la luminosité, ce qui la rend idéale pour appliquer des effets de lumière. À l'inverse, la soustraction ne peut que réduire la couleur finale d'un pixel, ce qui permet d'appliquer des ombres. La moyenne mélange deux pixels entre eux, qui sont à la même place à l'écran, mais n'ont pas la même profondeur, vu qu'ils sont dans des arrière-plans différents. En clair, elle applique des effets de transparences assez limités. Les pixels de deux arrière-plans sont mélangés avec une proportion fixe de 50-50. Ce qui permet d'appliquer des effets de transparence très limités.
Avec l'addition, il suffit de calculer les lumières à appliquer dans un arrière-plan, qui est additionné avec le reste de l'image. Pour appliquer des ombres, il faut faire la même chose mais avec la soustraction. Les ombres sont calculées dans un arrière-plan à part, qui est soustrait du résultat du reste. Pour être plus précis, l'arrière-plan contenait une image d'ombrage, qui indique quelles sont les parties de l'image dans l'ombre et l'intensité de l'ombre pour chaque pixel. Plus un pixel est dans l'ombre, plus sa couleur est proche du blanc (en RGB) : la palette de couleur est inversée. En soustrayant cette seconde image d'ombrage à l'image du ''framebuffer'' principal, on ombre les pixels.
Il faut noter que certaines consoles, comme la NES, permettaient en plus d'additionner une couleur fixe pour tous les pixels de l'écran, voire de la soustraire, voire de soustraire le pixel à cette couleur fixe. Cela permettait de biaiser les couleurs de base avec une couleur de base. Là encore, l'utilité était une question d'ombrage, d'éclairage, de transparence, ou autre. Cela permettait d'affiner les couleurs finales. La couleur de base était mémorisée dans un registre. Notons que le registre pouvait être changé entre l'affichage de deux lignes, via des ''raster interrupts'', ce qui permettait d'appliquer des dégradés de lumière verticaux, d'appliquer un effet d'horizon, d'ajouter du brouillard de distance.
===L'implémentation matérielle et le cas particulier de la SNES===
Utiliser plusieurs arrière-plans demande que le matériel soit adapté. Il faut typiquement ajouter de quoi gérer et mémoriser plusieurs arrière-plans et ajouter des circuits pour les combiner entre eux. Le hardware pour faire cette combinaison détermine quel pixel passe devant les autres, il peut gérer de la transparence, etc. Les ''sprites'' sont souvent gérés par un système de ''sprite'' matériel, ce qui fait que le défilement n'est pas tellement un problème pour eux.
Les arrières/avant-plans sont typiquement dans des mémoires séparées afin de faciliter la gestion du défilement . Avec plusieurs mémoires, la technique précédente (le ''framebuffer'' étendu et un viewport rectangulaire) peut s'appliquer à chaque arrière-plan. Et on peut les faire défiler à des vitesses différentes. Il est aussi possible d'utiliser une seule mémoire pour cela, qui mémorise plusieurs arrière-plans, c'est tout à fait possible.
La console de jeu SNES gérait 4 arrière-plans différents, mais elle n'utilisait pas plusieurs mémoires. Les 4 arrière-plans étaient combinés entre eux, ce qui donnait l'image finale à afficher. Il était possible d'activer ou de désactiver les arrière-plans si besoin. Il était par exemple possible de n'utiliser que 2 arrière-plans, ou 3 arrière-plans dont un soustrait de l'addition des deux autres, etc.
Elle disposait de plusieurs modes, appelés mode 1, mode 2, ..., mode 7. Le mode 7 est un mode de rendu avec un seul arrière-plan, mais où les techniques des sections précédentes sont utilisables. Les optimisations pour le défilement horizontal et vertical sont présentes, et le ''viewport'' peut même être déformé, tourné, etc. Il peut utiliser un second arrière-plan très particulier, dérivé du premier. Il utilise les mêmes tiles, avec cependant la possibilité de se retrouver devant les ''sprites'' ou les autres arrière-plans. Il était utilisé pour gérer des tunnels vus de dessus, ou pour quelques effets graphiques où des élèments de décor se retrouvaient à l'avant-plan.
{|class="wikitable"
|-
! Mode vidéo
! BG 1
! BG 2
! BG 3
! BG 4
|-
! 0
| 4 couleurs (2 bits) || 4 couleurs (2 bits) || 4 couleurs (2 bits) || 4 couleurs (2 bits)
|-
! 1
| 16 couleurs (4 bits) || 16 couleurs (4 bits) || 4 couleurs (2 bits) ||
|-
! 2
| 16 couleurs (4 bits) || 16 couleurs (4 bits) || ||
|-
! 3
| 256 couleurs (8 bits) || 16 couleurs (4 bits) || ||
|-
! 4
| 256 couleurs (8 bits) || 4 couleurs (2 bits) || ||
|-
! 5
| 16 couleurs (4 bits) || 4 couleurs (2 bits) || ||
|-
! 6
| 16 couleurs (4 bits) || || ||
|-
! 7
| 256 couleurs (8 bits) || EXTBG || ||
|}
==L'accélération matérielle du tracé de ligne et de figures géométriques==
<noinclude>[[File:Bresenham.svg|vignette|Tracé d'une ligne sur un écran.]]</noinclude>
La dernière optimisation du rendu 2D est le '''tracé de lignes et de figures géométriques''' accéléré en matériel. Quelques VDC incorporent cette optimisation, dont le nom est assez clair sur ce qu'elle fait. Tracer une ligne, un segment, est l'opération la plus courante sur de tels VDC, le tracé de cercles est déjà plus rare. Tracer des polygones est entre les deux, : plus rare que le tracé de ligne pur, moins que le tracé de cercles.
===Les circuits de tracé de ligne===
L'algorithme le plus utilisé par le matériel pour tracer des lignes est l''''algorithme de Bresenham'''. Il est très simple et s'implémente très facilement dans un circuit électronique. Il fut dit que cet algorithme utilise seulement des additions, des soustractions et des décalages, opérations très simples à implémenter en hardware. Il est de plus un des tout premiers algorithmes découvert dans le domaine du graphisme sur ordinateur. Il existe de nombreuses modifications de cet algorithme, qui vont de mineures à assez profondes. Et certaines d'entre elles sont plus faciles à implémenter en hardware que d'autres.
[[File:Bresenham line.png|vignette|Coordonnées du pixel de départ et d'arrivée.]]
[[File:LineBresenham.gif|vignette|Tracé d'une ligne sur un écran, pixel par pixel.]]
Le fonctionnement du circuit de tracé de ligne est le suivant. Premièrement, on précise le pixel de départ et le pixel d'arrivée en configurant des registres à l'intérieur du circuit, avec une paire de registre pour les coordonnées du pixel de départ, une autre parie pour les coordonnées du pixel d'arrivée. Le circuit dessine la ligne pixel par pixel, avec un pixel dessiné par cycle d'horloge.
===Le tracé de figures géométriques===
Passons maintenant au tracé de figures géométrique. Le tracé se fait là encore pixel par pixel, sur le principe. Généralement, le tracé est limité à des figures géométriques simples, que vous avez tous vu quand vous étiez au collège, en cours de maths. Tracer des carrés/rectangles/pentagones/trapèzes ou autres polygone est très simple : il suffit de tracer plusieurs lignes les unes à la suite des autres.
La vraie utilité est l'implémentation de courbes, comme des cercles, des ellipses, ou autres. L'algorithme de Bressenham peut être modifié pour implémenter des cercles, ce qui donne le '''''Midpoint circle algorithm'''''. D'autres extensions permettent de dessiner des ellipses, et même des courbes plus complexes comme des courbes de Bezier ou d'autres courbes assez complexes à expliquer.
===Le tracé et le remplissage de figures géométriques par le ''blitter''===
Outre le tracé des figures géométriques, il est aussi possible de gérer en hardware les opérations de '''remplissage'''. Cela veut dire dessiner l'intérieur d'une figure géométrique, comme remplir un carré ou un rectangle, avec une couleur uniforme. Par exemple : dessiner un carré en rouge, remplir un rectangle existant de bleu clair, etc. Le remplissage est souvent disponible pour certaines formes géométriques simples, comme des carrés ou rectangles, rarement plus. Pour le remplissage des triangles ou d'autres figures géométriques, le support matériel est encore plus rare, ne parlons même pas des cercles.
Le remplissage de rectangles est souvent réalisé par le ''blitter''. Pour faire une comparaison, la méthode utilisée est globalement la même que celle utilisée pour lire le ''viewport'' dans le ''framebuffer'', mais cette fois-ci réalisé par le ''blitter''. Le ''viewport'' est remplacé par le rectangle à remplir, et la lecture du pixel à envoyer à l'écran est remplacée par l'écriture d'une couleur précisée dans un registre.
Pour cela, on précise la couleur, la coordonnée X,Y et la largeur et la hauteur du rectangle dans des registres dédiés. Le remplissage commence à la coordonnée X,Y. L'adresse mémoire est alors incrémentée jusqu'à ce que la largeur voulue soit atteinte. On incrémente alors le compteur de ligne pour passer à la suivante, le compteur de colonne est réinitialisé avec la coordonnée X de la première colonne. Le remplissage s’arrête une fois que la hauteur voulue est atteinte.
Un exemple est le ''blitter'' des anciennes consoles Amiga, qui gère nativement des blocs en forme de rectangles, et de trapèzes dessinés à l'horizontale. Ils sont remplis en fournissant plusieurs informations : la position de leur premier pixel, 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. Le décalage est ajouté une fois le compteur de colonne réinitialisé à sa valeur précédente (au démarrage de la ligne précédente).
{{NavChapitre | book=Les cartes graphiques
| prev=Les systèmes à framebuffer
| prevText=Les systèmes à framebuffer
| next=Le mode texte et le rendu en tiles
| nextText=Le mode texte et le rendu en tiles
}}
{{autocat}}
gxi325tee53pviaps0r0wpj8cjvnvjh
Les cartes graphiques/Les processeurs de shaders
0
69558
774460
765334
2026-10-08T14:17:51Z
Mewtow
31375
/* La prédication avec une pile SIMT */
774460
wikitext
text/x-wiki
Les '''''shaders''''' sont des programmes informatiques exécutés par la carte graphique, et plus précisément par des processeurs de ''shaders''. Un point très important à comprendre est que chaque triangle ou pixel d'une scène 3D peut être traité indépendamment des autres. Le tout se résume comme suit :
: '''L’exécution d'un shader génère un grand nombre d'instances de ce shader, chacune traitant un paquet de pixels/sommets différent.'''
En conséquence, il est possible de traiter chaque instance d'un ''shader'' en parallèle des autres, en même temps, au lieu de traiter les instances l'une après l'autre.
La conséquence est que les cartes graphiques sont des architectures massivement parallèles, à savoir qu'elles sont capables d'effectuer un grand nombre de calculs indépendants en même temps. De plus, le parallélisme utilisé est du parallélisme de données, à savoir qu'on exécute le même programme sur des données différentes, chaque donnée étant traitée en parallèle des autres. Les cartes graphiques récentes incorporent toutes les techniques de parallélisme de donnée au niveau matériel, et nous allons toutes les détailler dans ce chapitre. S'il fallait résumer, elles ont plusieurs processeurs/cœurs, chaque cœur est capable d’exécuter des instructions SIMD (ils ne font que cela, à vrai dire), les cœurs sont fortement multithreadés, et j'en passe.
Le premier point est qu'une carte graphique contient de nombreux processeurs, qui eux-mêmes contiennent plusieurs unités de calcul. Savoir combien de cœurs contient une carte graphique est cependant très compliqué, car la terminologie utilisée par les fabricants de carte graphique est particulièrement confuse. Il n'est pas rare que ceux-ci appellent cœurs ou processeurs, ce qui correspond en réalité à une unité de calcul d'un processeur normal, sans doute histoire de gonfler les chiffres. Et on peut généraliser à la majorité de la terminologie utilisée par les fabricants, que ce soit pour les termes ''warps processor'', ou autre, qui ne sont pas aisés à interpréter.
L'architecture d'une carte graphique récente est illustrée ci-dessous. Rien de bien déroutant pour qui a déjà étudié les architectures à parallélisme de données, mais quelques rappels ou explications ne peuvent pas faire de mal. Le premier point est la présence d'un grand nombre de processeurs/cœurs, les rectangles en bleu/rouge. Chacun d'entre eux contient un grand nombre de circuits de calculs, avec des circuits de calcul simples mais nombreux en rouge, et une unité pour les calculs complexes (trigonométriques, racines carrées, autres) en rouge. Le tout est relié à une hiérarchie mémoire indiquée en vert, comprenant des mémoires locales en complément de la mémoire vidéo principale. Le tout est alimenté par une unité de répartition, le '''''Thread Execution Control Unit''''' en jaune, qui répartit les différentes instances du ''shader'' sur les différents processeurs. Elle est aussi appelée le '''processeur de commandes''', comme nous le verrons dans quelques chapitres. Nous utiliserons le terme processeur de commande dans ce qui suit.
[[File:NVIDIA GPU Accelerator Block Diagram.png|centre|vignette|upright=2.5|Ce schéma illustre l'architecture d'un GPU en utilisant la terminologie NVIDIA. Comme on le voit, la carte graphique contient plusieurs cœurs de processeur distincts. Chacun d'entre eux contient plusieurs unités de calcul généralistes, appelées processeurs de threads, qui s'occupent de calculs simples (en bleu). D'autres calculs plus complexes sont pris en charge par une unité de calcul spécialisée (en rouge). Ces cœurs sont alimentés en instructions par le processeur de commandes, ici appelé ''Thread Execution Control Unit'', qui répartit les différents shaders sur chaque cœur. Enfin, on voit que chaque cœur a accès à une mémoire locale dédiée, en plus d'une mémoire vidéo partagée entre tous les cœurs.]]
Les portions bleu, jaune et verte du schéma précédent méritent chacune un chapitre séparé. La hiérarchie mémoire en vert fera l'objet d'un chapitre ultérieur. Quant au répartiteur en jaune, il sera détaillé en profondeur dans le prochain chapitre. Dans ce chapitre, nous allons voir comment fonctionnent les processeurs de ''shaders'', la partie bleue. Nous allons voir que ceux-ci ne sont pas très différents des processeurs que l'on trouve dans les ordinateurs normaux, du moins dans les grandes lignes. Ce sont des processeurs séquentiels, qui exécutent des instructions les unes après les autres. Ils ont des instructions machines, des modes d'adressage, un assembleur, des registres et tout ce qui fait qu'un processeur est un processeur. Néanmoins, il y a une différence de taille : ce sont des processeurs adaptés pour effectuer un grand nombre de calculs en parallèle.
==Les registres des processeurs de shaders==
Un processeur de shaders contient beaucoup de '''registres généraux''', qui servent un peu à tout. Le programmeur de shader peut les utiliser à loisir. Tout processeur digne de ce nom possède des registres généraux, mais un processeur de ''shader'' dispose aussi de registres spécialisés, qu'on ne trouve que sur les processeurs de ''shaders''. Ils servent à l'interfacer avec le reste du pipeline graphique. Par exemple, des registres pour les textures, d'autres pour recevoir des données du rastériseur, etc.
[[File:Architecture carte graphique vertex avec texture.PNG|centre|vignette|upright=2|Architecture carte graphique vertex avec texture]]
===Les registres d'interface avec le pipeline graphique===
Les processeurs de ''vertex shader'' reçoivent des sommets provenant de l'''input assembler'' et envoient leur résultat au rastériseur. Les processeurs de ''pixel shader'' reçoivent des données de l'unité de rastérisation, et envoient un pixel éclairé aux ROPs. Et pour cela, le processeur de shader a des registres dédiés, qui servent d'interface avec le reste du pipeline graphique.
Les '''registres de sortie''' sont là où le processeur stocke les résultats à envoyer, soit au rastériseur pour un ''vertex shader'', soit aux ROP pour un ''pixel shader''. Les registres de sorties sont en écriture seule. Pour donner un exemple, les ''vertex shaders'' ont au minimum un registre pour la position du sommet dans l'espace (trois coordonnées), un autre pour la couleur/luminosité du sommet, un autre pour la couleur du brouillard, un autre pour les coordonnées de texture.
{|class="wikitable"
|+ Registres de sortie des ''pixel/vertex shaders''
|-
! Vertex shader
! Pixel shader
|-
| Couleur du pixel
| Couleur du sommet
|-
| Profondeur du pixel
| Position du sommet
|-
| rowspan="2" |
| Coordonnées de texture du sommet
|-
| Couleur de brouillard.
|}
Les '''registres d'entrée''', aussi appelés '''registres d'attributs''', réceptionnent soit les sommets provenant de l'''input assembler'', soit les pixels provenant de l'unité de rastérisation. Les registres d'entrée sont en lecture seule, du point de vue du processeur de shader, ils sont initialisés avant l'exécution de l'instance du ''shader''.
Les '''registres de constantes''' mémorisent des constantes utiles pour le ''shader''. Par exemple, pour les ''vertex shaders'', ils stockent les matrices servant aux différentes étapes de transformation ou d'éclairage. La différence avec les registres d'attribut est qu'ils mémorisent des données constantes pour un objet/modèle 3D (pour un ''draw call'', pour être plus précis) : des matrices de transformation, les adresses de texture, et bien d'autres. À l'opposé, les registres d'attributs mémorisent des sommets/pixels qui varient d'une instance de shader à l'autre.
Les ''pixel/vertex shaders'' 1.0 ne géraient que des constantes flottantes pour les ''vertex shaders'', entières pour les ''pixel shaders''. Les ''pixel/vertex shaders'' 2.0 et 3.0 ont ajouté des registres de constantes pour les nombres entiers et des opérandes booléennes. Il y en avait 16, comparé aux centaines de registres de constantes flottants. Les constantes entières et booléennes étaient utilisées pour gérer les boucles, guère plus. Avec les ''pixel/vertex shaders'' 4.0 et plus, les registres de constante n'ont plus de type prédéterminé, le programmeur gère ces registres comme il l'entend.
L'adressage des registres de constante est quelque peu particulier. Il faut dire qu'il y en a plusieurs milliers sur les processeurs de ''shaders'' modernes, au point qu'il serait plus juste de parler de mémoire RAM des constantes. Les registres de constante sont en effet un ''local store'' un peu spécial, intégré directement dans le processeur. Et le processeur accède à ce ''local store'' en utilisant une mode d'adressage semblable à celui utilisé pour la mémoire, avec un mode d'adressage indirect. L'adresse à lire dans ce ''local store'' est dans un registre, séparé du reste, appelé le '''registre d'adresse de constante'''.
===Les registres spécialisés internes===
D'autres registres spécialisés ne font pas l'interface avec le reste du GPU. Ils servent à stocker des constantes ou des données importantes, qui n'ont pas vraiment leur place dans les registres généraux.
Depuis les ''pixel/vertex shaders'' 3.0, les ''shaders'' sont capables d'effectuer des boucles et d'autres structures de contrôle familières pour les programmeurs. Et deux registres ont été intégrés afin d'améliorer les performances des structures de contrôle. Le premier est un registre à prédicat, qui sera vu dans la section sur le SIMD avec prédication. Le second est un '''registre compteur de boucle''', qui mémorise l'indice d'une boucle. Il est initialisé à 0, et est incrémenté à chaque fois qu'une boucle s'exécute.
Certains processeurs de shader ont aussi des '''registres de texture''' , qui servent d'interface avec la mémoire pour la gestion des textures. Ils mémorisent les texels lus par l'unité de texture. L'unité de texture lit un texel, plusieurs avec ''multitexturing'', et les place dans ces registres de texture. Les registres de texture sont parfois initialisés avant l'exécution du ''shader'', mais la plupart sont initialisé quand le ''shader'' termine une instruction de lecture de texture. Ils sont généralement en lecture seule, mais il y a des exceptions.
==Les processeurs de shaders modernes : les processeurs SIMD==
Maintenant, voyons quelles sont les instructions supportées par les processeurs de shaders modernes. Et si je dis moderne, c'est car nous ne parlerons que des GPU de l'époque DirectX 10 et après, pas des GPU de l'époque DirectX 9 et antérieur. La raison est que le jeu d'instruction des shaders a franchement évolué, avec le passage d'architectures VLIW à des architectures SIMD. Et cela a eu des conséquences assez profondes sur le jeu d'instruction et leur microarchitecture. Nous n'allons parler des GPU de type SIMD dans ce chapitre. Un chapitre dédié sera consacré aux GPU de type VLIW.
Le jeu d'instruction des GPU NVIDIA n'est pas encore connu à l'heure où j'écris ces lignes, la documentation du constructeur n'est pas disponible. Quelques chercheurs ont tenté de faire de la rétro-ingénierie du code de divers shaders pour retrouver le jeu d'instruction des divers GPU NVIDIA, ce qui fait qu'on a cependant une idée de ce dernier. Mais rien d'officiel. Par contre, AMD fournit librement cette documentation sur le net. <noinclude>Ce qui fait qu'on peut trouver des documents de ce genre :
* [https://developer.amd.com/wordpress/media/2012/12/AMD_Southern_Islands_Instruction_Set_Architecture.pdf Graphics Core Next 1 instruction set] ;
* [https://developer.amd.com/wordpress/media/2013/07/AMD_Sea_Islands_Instruction_Set_Architecture.pdf Graphics Core Next 2 instruction set] ;
* [https://developer.amd.com/wordpress/media/2013/12/AMD_GCN3_Instruction_Set_Architecture_rev1.1.pdf Graphics Core Next 3 and 4 instruction sets] ;
* [https://developer.amd.com/wp-content/resources/Vega_7nm_Shader_ISA.pdf "Vega" 7nm instruction set architecture] (also referred to as Graphics Core Next 5.1) ;
* [https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/rdna3-shader-instruction-set-architecture-feb-2023_0.pdf Jeu d'instruction des GPU de type RDNA3 d'AMD].</noinclude>
===Les instructions SIMD===
Les '''instructions SIMD''' manipulent plusieurs nombres en même temps. Elles manipulent plus précisément des '''vecteurs''', des ensembles de plusieurs nombres entiers ou nombres flottants placés les uns à côté des autres, le tout ayant une taille fixe, qui sont stockés dans des registres spécialisés. En général, tous les vecteurs ont une taille fixe, peu importe leur contenu. Cela implique que suivant la taille des données à manipuler, on pourra en placer plus ou moins dans un vecteur. Par exemple, un vecteur de 128 bits pourra contenir 4 entiers de 32 bits, 4 flottants 32 bits, ou 8 entiers de 16 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
Les vecteurs sont stockés dans des '''registres vectoriels''', aussi appelés '''registres SIMD'''. Un registre vectoriel peut contenir un vecteur complet, pas plus. En conséquence, ils ont une taille assez importante : ils font généralement 128, 256, voire 512 bits, comparé aux 32/64 bits des registres des CPU. Les cartes graphiques modernes contiennent un très grand nombre de registres SIMD.
{|
|+ Comparaison entre un processeur sans registres vectoriels, et avec registres vectoriels.
|[[File:Non-SIMD cpu diagram1.svg|vignette|upright=1.5|CPU Non-SIMD]]
|[[File:SIMD cpu diagram1.svg|vignette|upright=1.5|CPU SIMD]]
|}
Une instruction SIMD traite chaque donnée du vecteur indépendamment des autres. Par exemple, une instruction d'addition vectorielle va additionner ensemble les données qui sont à la même place dans deux vecteurs, et placer le résultat dans un autre vecteur, à la même place.
[[File:Instructions SIMD.png|centre|vignette|upright=2.5|Instructions SIMD]]
Sur les cartes graphiques modernes, les vecteurs sont généralement des vecteurs qui regroupent plusieurs nombres flottants. De plus, les flottants en question sont des flottants dits simple précision, codés sur 32 bits. Mais il y a quelques exceptions, comme [https://www.realworldtech.com/apple-custom-gpu/ certains GPU d'Apple, qui ne gèrent majoritairement que des flottants codés sur 16 bits], avec des fonctionnalités pour la simple précision. Les anciennes cartes graphiques ne géraient pas du tout de vecteurs contenant des nombres entiers.
===Les instruction scalaires entières, typiques des CPU===
Un processeur SIMD gère donc des instructions SIMD, et les anciennes cartes graphiques ne disposaient que d'instructions de ce type. Mais depuis au moins une décennie, les processeurs de shaders gèrent des instructions normales, non-SIMD. De telles instructions sont appelées des '''instruction scalaires'''. En clair, il s'agit des instructions qu'on retrouve normalement tous les processeurs principaux (les CPU).
Il s'agit généralement d''''instructions entières''', agissent sur des registres entiers non-SIMD. Elles ne traitent pas de vecteur, mais de simples nombres entiers indépendants, sans regroupement d'aucune sorte. Typiquement, il s'agit d'opérations d'addition, de soustraction, des opérations logiques, des comparaisons, guère plus. On trouve aussi des opérations un peu originales, comme des calculs de valeur absolue, du minimum/maximum de deux opérandes, des opérations à prédicat comme une instruction CMOV, etc. Les cartes graphiques supportent rarement la multiplication, mais les plus récentes supportent des multiplications sur des opérandes de 16/32 bits. Par contre, aucune ne gère de division entière.
Les GPU modernes gèrent aussi des instructions de test et de branchement, là encore sur des nombres entiers. Les instructions de test et branchement sont généralement considérées comme à part des instructions de calcul, mais ce sont des opérations scalaires. Les comparaisons se font entre deux entiers scalaires, pas entre deux vecteurs. Retenez bien ce détail, car il sera très important pour la suite.
Les GPU modernes gèrent aussi des '''instructions flottantes scalaires''', à savoir que des instructions qui ont pour opérandes des nombres flottants isolés, qui ne sont pas dans un vecteur. Les processeurs principaux (CPU) d'un ordinateur sont capables de faire beaucoup de calculs arithmétiques simples sur des nombres flottants, comme des additions, des multiplications, des opérations bit-à-bit, éventuellement des divisions, etc. Il en est de même sur les GPUS. Mais ces derniers gèrent aussi de nombreuses instructions flottantes que les CPU n'incorporent presque pas.
Il est rare que les CPU soient capables de faire des opérations flottantes complexes, comme des calculs trigonométriques, des exponentielles, des logarithmes, des racines carrées ou racines carrées inverse, etc. De tels calculs sont rares dans les programmes exécutables, alors que les calculs arithmétiques simples y sont légion. Mais le rendu 3D demande pas mal de calculs trigonométriques, de produits scalaires ou d'autres opérations. Par exemple, dans les chapitres précédents, nous avions abordé les calculs d'éclairage et avions vu qu'ils font beaucoup de calculs vectoriels avec des vecteurs comme la normale d'un sommet. Et ces calculs demandent de calculer des produits scalaires et vectoriels, qui eux-mêmes demandent des calculs trigonométriques comme le cosinus ou le sinus.
Aussi, les processeurs de ''shaders'' disposent souvent d'instructions flottantes spécialisées dans les calculs complexes : exponentielle/logarithme, racine carrée, racine carrée inverse, autres. Nous appellerons ces instructions des '''instructions transcendantales''', car elles effectuent des calculs de ce type.
Il faut noter que le processeur incorpore des registres dédiés aux scalaires, séparés des registres SIMD. Par séparés, on veut dire que ce sont des registres différents, adressés différemment, mais qu'ils sont aussi physiquement séparés dans le processeur, ils sont des bancs de registres différents.
===Les instructions en ''co-issue''===
Beaucoup de cartes graphiques récentes comme anciennes incorporent des '''instructions de ''co-issue''''' qui ne se trouvent que sur les cartes graphiques et n'ont aucun équivalent sur les CPUs. Les instructions de ''co-issue'' regroupent plusieurs opérations par instruction. Par exemple, elles peuvent combiner une opération vectorielle avec une opération scalaire. Ou encore, elles peuvent regrouper une opération scalaire, une opération vectorielle et un branchement. Il s'agit d'instructions qui ressemblent grandement à ce qu'on trouve sur les processeurs VLIW.
Un point important est que les cartes graphiques modernes disposent d'instructions à ''co-issue'' en plus des instructions normales. Les instructions à ''co-issue'' sont complémentaire des instructions normales, elles ne les remplacent pas. Les deux peuvent s'utiliser en même temps, dans un même shader. Il a cependant existé des cartes graphiques assez anciennes sur lesquelles toutes les instructions étaient des instructions à ''co-issue'' : certains processeurs de shaders VLIW anciens sont de ce type.
Il y a de nombreuses contraintes quant au regroupement des deux opérations. On ne peut pas regrouper n'importe quelle opération avec n'importe quelle autre. L'exemple type de ''co-issue'' est la ''co-issue'' entre opérations scalaires et vectorielles : il n'est pas possible de regrouper deux instructions scalaires ou deux instructions vectorielles. La seule possibilité est de regrouper une opération scalaire et une opération vectorielle. La raison à cela est qu'opérations scalaires et vectorielles sont calculées dans des circuits séparés : le processeur incorpore une unité de calcul scalaire et une unité de calcul SIMD, et peut utiliser les deux en parallèle, en même temps. Mais nous verrons cela dans quelques chapitres.
Pour simplifier, cette technique permettait d’exécuter deux opérations arithmétiques en même temps, en parallèle : une opération vectorielle appliquée aux couleurs R, G, et B, et une opération scalaire appliquée à la couleur de transparence. Si cela semble intéressant sur le papier, cela complexifie fortement le processeur de shader, ainsi que la traduction à la volée des shaders en instructions machine.
===Un exemple : le jeu d’instruction du GPU de la Geforce 3===
La première carte graphique commerciale grand public à disposer d'une unité de vertex programmable est la Geforce 3. Celui-ci respectait le format de vertex shader 1.1. L'ensemble des informations à savoir sur cette unité est disponible dans l'article [https://cseweb.ucsd.edu/~ravir/6160-fall04/papers/p149-lindholm.pdf "A user programmable vertex engine"], disponible sur le net. . Le processeur de cette carte était capable de gérer un seul type de données : les nombres flottants de norme IEEE754. Toutes les informations concernant la coordonnée d'une vertice, voire ses différentes couleurs, doivent être encodées en utilisant ces flottants.
Les processeurs de vertices de la Geforce 3 disposent de registres registres SIMD qui font 128 bits, soit 4 flottants de 32 bits. Elle contient 16 registres d'entrée, 16 registres de sortie, 32 registres généraux. La mémoire des constantes contient 512 "registres".
Le processeur de la Geforce 3 est capable d’exécuter 17 instructions différentes, dont voici les principales :
{|class="wikitable"
|-
!OpCode!!Nom!!Description
|-
! colspan="3" | Opérations mémoire
|-
|MOV||Move||vector -> vector
|-
|ARL||Address register load||miscellaneous
|-
! colspan="3" | Opérations arithmétiques
|-
|ADD||Add||vector -> vector
|-
|MUL||Multiply||vector -> vector
|-
|MAD||Multiply and add||vector -> vector
|-
|MIN||Minimum||vector -> vector
|-
|MAX||Maximum||vector -> vector
|-
|SLT||Set on less than||vector -> vector
|-
|SGE||Set on greater or equal||vector -> vector
|-
|LOG||Log base 2||miscellaneous
|-
|EXP||Exp base 2||miscellaneous
|-
|RCP||Reciprocal||scalar-> replicated scalar
|-
|RSQ||Reciprocal square root||scalar-> replicated scalar
|-
! colspan="3" | Opérations trigonométriques
|-
|DP3||3 term dot product||vector-> replicated scalar
|-
|DP4||4 term dot product||vector-> replicated scalar
|-
|DST||Distance||vector -> vector
|-
! colspan="3" | Opérations d'éclairage géométrique
|-
|LIT||Phong lighting||Calcule l'éclairage de Gouraud
|}
L'instruction la plus intéressante est clairement la dernière : elle éclaire un sommet, en utilisant un éclairage de Phong. Les autres instructions permettent d'implémenter un autre algorithme si besoin, mais cette forme d'éclairage est déjà là à la base.
Les autres instructions sont surtout des instructions arithmétiques : multiplications, additions, exponentielles, logarithmes, racines carrées, etc. Pour les instructions d'accès à la mémoire, on trouve une instruction MOV qui déplace le contenu d'un registre dans un autre et une instruction de calcul d'adresse, mais aucune instruction d'accès à la mémoire sur le processeur de la Geforce 3. Plus tard, les unités de ''vertex shader'' ont acquis la possibilité de lire des données dans une texture.
On remarque que la division est absente. Il faut dire que la contrainte qui veut que toutes ces instructions s’exécutent en un cycle d'horloge pose quelques problèmes avec la division, qui est une opération plutôt lourde en hardware. À la place, on trouve l'instruction RCP, capable de calculer 1/x, avec x un flottant. Cela permet ainsi de simuler une division : pour obtenir Y/X, il suffit de calculer 1/X avec RCP, et de multiplier le résultat par Y.
==La prédication et le SIMT==
Les cartes graphiques récentes peuvent effectuer des branchements, mais ceux-ci sont tout sauf performants. Dès qu'un branchement survient, le processeur est obligé de traiter chaque élément du vecteur un par un, au lieu de tous les traiter en même temps en parallèle. Les performances s'en ressentent, ce qui fait que les branchements sont à éviter le plus possible. Pour améliorer la gestion des conditions, les cartes graphiques modernes incorporent des instructions spécialisées qui permettent de remplacer des codes remplis de branchements par des codes plus simples, compatibles avec l'organisation des données en vecteurs.
Si on met de côté le support de certaines instructions courantes, comme la valeur absolue, ou le calcul du minimum/maximum, la technique la plus importante est la technique dite de '''prédication'''. L'idée est que quand une instruction effectue un calcul sur un ou deux vecteurs, certains éléments du vecteur sont ignorés. Les éléments à ignorer sont choisis suivant le résultat d'une instruction de comparaison, qui effectue un test : les éléments pour lesquels ce test est respecté sont pris en compte, ceux qui ne passent pas le test sont ignorés.
Pour donner un exemple d'utilisation, imaginons que l'on ait un vecteur dans lequel on veut remplacer toutes les valeurs négatives par des 0. Dans ce cas, on utilise :
* une instruction de comparaison, qui compare chaque élément du vecteur avec 0 et génère plusieurs bits de résultat ;
* suivi d'une instruction à prédicat qui met à zéro les éléments pour lesquels les bits de résultat précédents sont à 1.
Elle est implémentée grâce à un registre appelé le '''''Vector Mask Register'''''. Celui-ci permet de stocker des informations qui permettront de sélectionner certaines données et pas d'autres pour faire notre calcul. Il est mis à jour par des instructions de comparaison. le ''Vector Mask Register'' stocke un bit pour chaque flottant présent dans le vecteur à traiter, bit qui indique s'il faut appliquer l'instruction sur ce flottant. Si ce bit est à 1, notre instruction doit s’exécuter sur la donnée associée à ce bit. Sinon, notre instruction ne doit pas la modifier. On peut ainsi traiter seulement une partie des registres stockant des vecteurs SIMD.
[[File:Vector mask register.png|centre|vignette|upright=2.5|''Vector mask register'']]
===La prédication avec une pile SIMT===
Au niveau du jeu d’instruction, les architectures SIMT implémentent de la prédication, sous une forme améliorée. La prédication est améliorée pour gérer les structures de contrôle imbriquées, à savoir une boucle placée à l'intérieur d'une autre boucle, un IF...ELSE dans un autre IF...ELSE, etc. Elles utilisent pour cela la prédication, combinée avec des mécanismes annexes. Le premier d'entre eux est l'usage de plusieurs registres de masques organisés d'une manière bien précise, l'autre est l'usage de compteurs d'activité. Voyons ces deux techniques.
La '''pile de masques''' remplace le ou les registres de masque. Sans elle, le processeur SIMD incorpore un registre de masque qui est adressé implicitement ou explicitement. Éventuellement, le processeur peut contenir plusieurs registres de masque séparés adressables via un nom de registre. Avec elle, le processeur SIMD incorpore plusieurs registres de masque organisé en pile. Le registre de masque est donc remplacé par une mémoire LIFO, une pile, dans laquelle plusieurs masques sont empilés.
Le tout forme une pile, similaire à la pile d'appel, sauf qu'elle est utilisée pour empiler des masques. Un masque est calculé et empilé à chaque entrée dans une structure de contrôle, puis dépilé une fois la structure de contrôle exécutée. L'empilement et le dépilement des masques est effectué par des instructions PUSH et POP, présentes dans le jeu d'instruction du processeur SIMD.
Le calcul des masques doit répondre à plusieurs impératifs.
* Premièrement, chaque masque se calcule en faisant un ET entre le masque précédent et le masque calculé par l'instruction de test. Cela permet de ne pas réveiller d’élément au beau milieu d'une structure imbriquée. Si in IF désactive certains éléments du vecteur, une condition imbriquée dans ce IF ne doit pas réveiller cet élément. Le fait de faire un ET entre les masques garantit cela.
* Deuxièmement, les masques doivent être empilés et dépilés correctement. Au moment de rentrer dans une structure de contrôle, on effectue une instruction de test associée à la structure de contrôle, qui calcule un masque, et on empile le masque calculé. Au moment de sortir de la structure de contrôle, on dépile le masque en question.
L'implémentation demande d'utiliser une mémoire LIFO pour stocker la pile de masques, et quelques circuits annexes. Il faut notamment un circuit relié à l'ALU qui récupère les conditions, les résultats des comparaisons, et qui effectue le ET pour combiner les masques.
Pour donner un exemple, prenons le code suivant, qui est volontairement simpliste et ne sert qu'à des fins d'explication :
<syntaxhighlight lang="c">
if ( condition 1 )
{
if ( condition 2 )
{
...
}
else
{
...
}
Autres instructions
}
Instructions après le IF...
</syntaxhighlight>
Imaginons que l'on traite des vecteurs de 8 éléments.
Pour le vecteur considéré, la première condition (a > 0) n'est respectée que par les 4 premiers éléments. L'instruction de condition calcule alors le masque correspondant : 1111 0000. Le masque est alors calculé, puis empilé au sommet de la pile.
La seconde instruction de test, qui teste la variable b, est maintenant valide pour les 4 bits du milieu du masque. Mais n'allez pas croire que le masque correspondant soit 0011 11100 : il faut tenir compte de la condition précédente, qui a éliminé les 4 derniers éléments. Pour cela, on fait un ET logique entre le masque précédent, et le masque calculé par la condition. Le masque au sommet de la pile est donc lu, combiné avec le masque calculé par l'instruction, ce qui donne le masque final. Le masque final est alors empilé au sommet de la pile.
On exécute alors l'instruction du IF, en tenant compte du masque qui est au sommet de la pile. Si le IF était plus compliqué, toutes les instructions suivantes tiendraient compte du masque. En fait, le masque est pris en compte tant qu'il n'est pas dépilé. Une fois que le IF est terminé, le masque est dépilé.
On passe alors au ELSE, et rebelotte. Le masque pour le ELSE est calculé en combinant le masque au sommet de la pile avec la condition du ELSE. Le masque au sommet de la pile est celui calculé à l'entrée du premier IF, pas le second qui a été dépilé. Les instructions du ELSE sont alors exécutées en tenant compte de ce masque. Une fois qu'elles sont toutes exécutées, le masque est dépilé.
Puis vient l'exécution des instructions après le ELSE. Elles utilisent le masque empilé au sommet de la pile, qui correspond à celui à l'entrée du IF.
Puis vient le moment d'exécuter les instructions après le IF : pas de masque, on exécute sur tout le vecteur.
===Les compteurs d'activité===
Une variante de la technique précédente remplace la pile de masques par des '''compteurs d'activité'''. La technique est similaire, si ce n'est qu'elle utilise moins de circuits. Avant , on avait une pile de masques de même taille, dont les bits sont à 0 ou 1 suivant que la condition est remplie. La pile de masque ressemble donc à ceci :
{|class="wikitable"
|-
! masque 1
| 1 || 1 || 1 || 1
|-
! masque 2
| 0 || 1 || 1 || 1
|-
! masque 3
| 0 || 1 || 1 || 1
|-
! masque 4
| 0 || 0 || 0 || 1
|-
! masque 1
| colspan="4" | vide
|}
Une manière équivalente de représenter cette pile de masque est de compter combien de bits sont à 0 dans chaque colonne. Attention : j'ai bien dit à 0 ! On obtient alors :
{|class="wikitable"
|-
! masque 1
| 3 || 1 || 1 || 0
|}
Et c'est le principe caché derrière la technique des compteurs d'activité. Chaque élément dans un vecteur, chaque place, se voit attribuer un compteur. Un compteur non-nul indique qu'il ne faut pas prendre en compte l’élément. Ce n'est qu'une fois que le compteur est nul que l'on effectue des opérations sur l’élément associé du vecteur.
À chaque fois qu'on entre dans une structure de contrôle, on teste une condition sur chaque élément. Si la condition est respectée pour un élément, alors le compteur ne change pas. Mais si la condition n'est pas respectée, alors on incrémente le compteur associé. En sortant de la structure de contrôle, on décrémente le compteur associé. Notons que les compteurs qui n'ont pas été incrémentés en entrant dans la structure de contrôle ne sont pas décrémentés en sortant. En clair, là où on empilait/dépilait un masque, on se contente d'incrémenter/décrémenter un compteur.
Utiliser un compteur en lieu et place d'une colonne entière dans la pile de masque utilise moins de bits. Et c'est sans doute pour cette raison que certaines cartes graphiques, comme les cartes graphiques intégrées d'Intel depuis 2004, utilisent cette technique.
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=L'évolution vers la programmabilité : les GPUs
| prevText=L'évolution vers la programmabilité : les GPUs
| next=La microarchitecture des processeurs de shaders
| nextText=La microarchitecture des processeurs de shaders
}}{{autocat}}
</noinclude>
b1azcm63f6psqoftz4f16bsf5kngmld
Fonctionnement d'un ordinateur/Sommaire
0
69596
774468
773936
2026-10-08T23:17:26Z
Mewtow
31375
/* Les jeux d'instruction anciens, avant les registres généraux ou exotiques */
774468
wikitext
text/x-wiki
__NOTOC__
* [[Fonctionnement d'un ordinateur/Introduction|Introduction]]
==Le codage des informations==
* [[Fonctionnement d'un ordinateur/L'encodage des données|L'encodage des données]]
* [[Fonctionnement d'un ordinateur/Le codage des nombres|Le codage des nombres]]
* [[Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur|Les codes de détection/correction d'erreur]]
==Les circuits électroniques==
* [[Fonctionnement d'un ordinateur/Les portes logiques|Les portes logiques]]
===Les circuits combinatoires===
* [[Fonctionnement d'un ordinateur/Les circuits combinatoires|Les circuits combinatoires]]
* [[Fonctionnement d'un ordinateur/Les circuits de masquage|Les circuits de masquage]]
* [[Fonctionnement d'un ordinateur/Les circuits de sélection|Les circuits de sélection]]
* [[Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs|Les circuits incrémenteurs/décrémenteurs]]
===Les circuits séquentiels===
* [[Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit|Les bascules : des mémoires de 1 bit]]
* [[Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones|Les circuits synchrones et asynchrones]]
* [[Fonctionnement d'un ordinateur/Les registres et mémoires adressables|Les registres et mémoires adressables]]
* [[Fonctionnement d'un ordinateur/Les compteurs et timers|Les compteurs et timers]]
* [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR|Les registres à décalage et les LSFR]]
===Les circuits de calcul et de comparaison===
* [[Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation|Les circuits de décalage et de rotation]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction|Les circuits pour l'addition et la soustraction]]
* [[Fonctionnement d'un ordinateur/Les circuits de comparaison|Les circuits de comparaison]]
* [[Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)|Les unités arithmétiques et logiques entières (simples)]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande|Les circuits pour l'addition multiopérande]]
* [[Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division|Les circuits pour la multiplication et la division]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit|Les circuits de calcul logique et bit à bit]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul flottant|Les circuits de calcul flottant]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques|Les circuits de calcul trigonométriques]]
===Les circuits intégrés à semi-conducteurs===
* [[Fonctionnement d'un ordinateur/Les transistors et portes logiques|Les transistors et portes logiques]]
* [[Fonctionnement d'un ordinateur/Les circuits intégrés|Les circuits intégrés]]
* [[Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus|L'interface électrique entre circuits intégrés et bus]]
==L'architecture d'un ordinateur==
* [[Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur|L'architecture de base d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La hiérarchie mémoire|La hiérarchie mémoire]]
* [[Fonctionnement d'un ordinateur/La performance d'un ordinateur|La performance d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques|La loi de Moore et les tendances technologiques]]
* [[Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur|Les techniques de réduction de la consommation électrique d'un processeur]]
==Les bus électroniques et la carte mère==
* [[Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS|La carte mère, chipset et BIOS]]
* [[Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)|Les bus et liaisons point à point (généralités)]]
* [[Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus|Les encodages spécifiques aux bus]]
* [[Fonctionnement d'un ordinateur/Les liaisons point à point|Les liaisons point à point]]
* [[Fonctionnement d'un ordinateur/Les bus électroniques|Les bus électroniques]]
* [[Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point|Quelques exemples de bus et de liaisons point à point]]
==Les mémoires RAM/ROM==
* [[Fonctionnement d'un ordinateur/Les différents types de mémoires|Les différents types de mémoires]]
* [[Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique|L'interface d'une mémoire électronique]]
* [[Fonctionnement d'un ordinateur/Le bus mémoire|Le bus mémoire]]
===La micro-architecture d'une mémoire adressable===
* [[Fonctionnement d'un ordinateur/Les cellules mémoires|Les cellules mémoires]]
* [[Fonctionnement d'un ordinateur/Le plan mémoire|Le plan mémoire]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire interne|Le contrôleur mémoire interne]]
* [[Fonctionnement d'un ordinateur/Mémoires évoluées|Les mémoires évoluées]]
===Les mémoires primaires===
* [[Fonctionnement d'un ordinateur/Les mémoires ROM|Les mémoires ROM : Mask ROM, PROM, EPROM, EEPROM, Flash]]
* [[Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones|Les mémoires SRAM synchrones]]
* [[Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)|Les mémoires RAM dynamiques (DRAM)]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire externe|Le contrôleur mémoire externe]]
===Les mémoires exotiques===
* [[Fonctionnement d'un ordinateur/Les mémoires associatives|Les mémoires associatives]]
* [[Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO|Les mémoires FIFO et LIFO]]
==Le processeur==
===L'architecture externe===
* [[Fonctionnement d'un ordinateur/Langage machine et assembleur|Langage machine et assembleur]]
* [[Fonctionnement d'un ordinateur/Les registres du processeur|Les registres du processeur]]
* [[Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme|Le modèle mémoire : alignement et boutisme]]
* [[Fonctionnement d'un ordinateur/Les modes d'adressage|Les modes d'adressage]]
* [[Fonctionnement d'un ordinateur/L'encodage des instructions|L'encodage des instructions]]
* [[Fonctionnement d'un ordinateur/Les jeux d'instructions|Les jeux d'instructions]]
* [[Fonctionnement d'un ordinateur/La pile d'appel et les fonctions|La pile d'appel et les fonctions]]
* [[Fonctionnement d'un ordinateur/Les interruptions et exceptions|Les interruptions et exceptions]]
===La micro-architecture===
* [[Fonctionnement d'un ordinateur/Les composants d'un processeur|Les composants d'un processeur]]
* [[Fonctionnement d'un ordinateur/Le chemin de données|Le chemin de données]]
* [[Fonctionnement d'un ordinateur/L'unité de chargement et le program counter|L'unité de chargement et le program counter]]
* [[Fonctionnement d'un ordinateur/L'unité de contrôle|L'unité de contrôle]]
* [[Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements|L'implémentation matérielle des branchements]]
===Les jeux d'instruction anciens===
* [[Fonctionnement d'un ordinateur/Les architectures à accumulateur|Les architectures à accumulateur]]
* [[Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire|Les architectures à pile et mémoire-mémoire]]
* [[Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins|Les processeurs 8 bits et moins]]
===L'espace d'adressage du processeur et la multiprogrammation===
* [[Fonctionnement d'un ordinateur/L'espace d'adressage du processeur|L'espace d'adressage du processeur]]
* [[Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle|L'abstraction mémoire et la mémoire virtuelle]]
==Les entrées-sorties et périphériques==
* [[Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques|Les méthodes de synchronisation entre processeur et périphériques]]
* [[Fonctionnement d'un ordinateur/L'adressage des périphériques|L'adressage des périphériques]]
* [[Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension|Les périphériques et les cartes d'extension]]
==Les mémoires de stockage==
* [[Fonctionnement d'un ordinateur/Les mémoires de masse : généralités|Les mémoires de masse : généralités]]
* [[Fonctionnement d'un ordinateur/Les disques durs|Les disques durs]]
* [[Fonctionnement d'un ordinateur/Les solid-state drives|Les solid-state drives]]
* [[Fonctionnement d'un ordinateur/Les disques optiques|Les disques optiques]]
* [[Fonctionnement d'un ordinateur/Compléments sur les mémoires de masse|Compléments sur les mémoires de masse]]
==La ou les mémoires caches==
* [[Fonctionnement d'un ordinateur/Les mémoires cache|Les mémoires cache]]
* [[Fonctionnement d'un ordinateur/Le préchargement|Le préchargement]]
* [[Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer|Le ''Translation Lookaside Buffer'']]
==Le parallélisme d’instructions==
* [[Fonctionnement d'un ordinateur/Le pipeline|Le pipeline]]
===Les branchements et le ''front-end''===
* [[Fonctionnement d'un ordinateur/La prédiction de branchement|La prédiction de branchement]]
* [[Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions|Les optimisations du chargement des instructions]]
===Les pipelines multicycles simples===
* [[Fonctionnement d'un ordinateur/Les pipelines multicycles|Les pipelines multicycles]]
* [[Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions|L'émission dans l'ordre des instructions]]
* [[Fonctionnement d'un ordinateur/Le contournement (data forwarding)|Le contournement (data forwarding)]]
* [[Fonctionnement d'un ordinateur/Les premiers processeurs Intel|Les premiers processeurs Intel]]
===L’exécution dans le désordre===
* [[Fonctionnement d'un ordinateur/L'exécution dans le désordre|L'exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le renommage de registres|Le renommage de registres]]
* [[Fonctionnement d'un ordinateur/Les schedulers larges et leurs optimisations|Les schedulers larges et leurs optimisations]]
* [[Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo|Le scoreboarding et l'algorithme de Tomasulo]]
===Les optimisations des accès mémoire===
* [[Fonctionnement d'un ordinateur/La désambiguïsation mémoire|La désambiguïsation mémoire]]
* [[Fonctionnement d'un ordinateur/Le parallélisme mémoire|Le parallélisme mémoire]]
===Les processeurs superscalaires===
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires|Les processeurs superscalaires in-order]]
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires out-of-order|Les processeurs superscalaires out-of-order]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance|Les microarchitectures pour le x86 : haute performance]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : basse consommation|Les microarchitectures pour le x86 : basse consommation]]
===Le parallélisme d'instruction explicite===
* [[Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC|Les processeurs VLIW et EPIC]]
* [[Fonctionnement d'un ordinateur/Les architectures dataflow|Les architectures dataflow]]
==Les architectures parallèles==
* [[Fonctionnement d'un ordinateur/Les architectures parallèles|Les architectures parallèles]]
* [[Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs|Les architectures multiprocesseurs et multicœurs]]
* [[Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading|Les architectures multithreadées et Hyperthreading]]
* [[Fonctionnement d'un ordinateur/Les architectures à parallélisme de données|Les architectures à parallélisme de données]]
* [[Fonctionnement d'un ordinateur/La cohérence des caches|La cohérence des caches]]
* [[Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire|Les sections critiques et le modèle mémoire]]
==Annexes==
===Les nombres flottants : FPUs et coprocesseurs===
* [[Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87|Un exemple de jeu d'instruction : l'extension x87]]
* [[Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO|Les coprocesseurs : FPU et IO]]
===Les jeux d’instruction spécialisés===
* [[Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]]
* [[Fonctionnement d'un ordinateur/Les processeurs de traitement du signal|Les processeurs de traitement du signal]]
* [[Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement|Les architectures actionnées par déplacement]]
===Réseaux de neurones et accélérateurs d'IA===
* [[Fonctionnement d'un ordinateur/Les architectures systoliques|Les architectures systoliques]]
* [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]]
===Les autres annexes===
* [[Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation|L'accélération matérielle de la virtualisation]]
* [[Fonctionnement d'un ordinateur/Le matériel réseau|Le matériel réseau]]
* [[Fonctionnement d'un ordinateur/La tolérance aux pannes|La tolérance aux pannes]]
* [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires|Les ordinateurs de première génération : tubes à vide et mémoires]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires|Les ordinateurs à encodages non-binaires]]
* [[Fonctionnement d'un ordinateur/Les circuits réversibles|Les circuits réversibles]]
{{autocat}}
9edhw0im7gmy6fbq94muumg7hsrru8f
774469
774468
2026-10-08T23:17:37Z
Mewtow
31375
/* L'espace d'adressage du processeur et la multiprogrammation */
774469
wikitext
text/x-wiki
__NOTOC__
* [[Fonctionnement d'un ordinateur/Introduction|Introduction]]
==Le codage des informations==
* [[Fonctionnement d'un ordinateur/L'encodage des données|L'encodage des données]]
* [[Fonctionnement d'un ordinateur/Le codage des nombres|Le codage des nombres]]
* [[Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur|Les codes de détection/correction d'erreur]]
==Les circuits électroniques==
* [[Fonctionnement d'un ordinateur/Les portes logiques|Les portes logiques]]
===Les circuits combinatoires===
* [[Fonctionnement d'un ordinateur/Les circuits combinatoires|Les circuits combinatoires]]
* [[Fonctionnement d'un ordinateur/Les circuits de masquage|Les circuits de masquage]]
* [[Fonctionnement d'un ordinateur/Les circuits de sélection|Les circuits de sélection]]
* [[Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs|Les circuits incrémenteurs/décrémenteurs]]
===Les circuits séquentiels===
* [[Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit|Les bascules : des mémoires de 1 bit]]
* [[Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones|Les circuits synchrones et asynchrones]]
* [[Fonctionnement d'un ordinateur/Les registres et mémoires adressables|Les registres et mémoires adressables]]
* [[Fonctionnement d'un ordinateur/Les compteurs et timers|Les compteurs et timers]]
* [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR|Les registres à décalage et les LSFR]]
===Les circuits de calcul et de comparaison===
* [[Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation|Les circuits de décalage et de rotation]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction|Les circuits pour l'addition et la soustraction]]
* [[Fonctionnement d'un ordinateur/Les circuits de comparaison|Les circuits de comparaison]]
* [[Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)|Les unités arithmétiques et logiques entières (simples)]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande|Les circuits pour l'addition multiopérande]]
* [[Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division|Les circuits pour la multiplication et la division]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit|Les circuits de calcul logique et bit à bit]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul flottant|Les circuits de calcul flottant]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques|Les circuits de calcul trigonométriques]]
===Les circuits intégrés à semi-conducteurs===
* [[Fonctionnement d'un ordinateur/Les transistors et portes logiques|Les transistors et portes logiques]]
* [[Fonctionnement d'un ordinateur/Les circuits intégrés|Les circuits intégrés]]
* [[Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus|L'interface électrique entre circuits intégrés et bus]]
==L'architecture d'un ordinateur==
* [[Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur|L'architecture de base d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La hiérarchie mémoire|La hiérarchie mémoire]]
* [[Fonctionnement d'un ordinateur/La performance d'un ordinateur|La performance d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques|La loi de Moore et les tendances technologiques]]
* [[Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur|Les techniques de réduction de la consommation électrique d'un processeur]]
==Les bus électroniques et la carte mère==
* [[Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS|La carte mère, chipset et BIOS]]
* [[Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)|Les bus et liaisons point à point (généralités)]]
* [[Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus|Les encodages spécifiques aux bus]]
* [[Fonctionnement d'un ordinateur/Les liaisons point à point|Les liaisons point à point]]
* [[Fonctionnement d'un ordinateur/Les bus électroniques|Les bus électroniques]]
* [[Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point|Quelques exemples de bus et de liaisons point à point]]
==Les mémoires RAM/ROM==
* [[Fonctionnement d'un ordinateur/Les différents types de mémoires|Les différents types de mémoires]]
* [[Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique|L'interface d'une mémoire électronique]]
* [[Fonctionnement d'un ordinateur/Le bus mémoire|Le bus mémoire]]
===La micro-architecture d'une mémoire adressable===
* [[Fonctionnement d'un ordinateur/Les cellules mémoires|Les cellules mémoires]]
* [[Fonctionnement d'un ordinateur/Le plan mémoire|Le plan mémoire]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire interne|Le contrôleur mémoire interne]]
* [[Fonctionnement d'un ordinateur/Mémoires évoluées|Les mémoires évoluées]]
===Les mémoires primaires===
* [[Fonctionnement d'un ordinateur/Les mémoires ROM|Les mémoires ROM : Mask ROM, PROM, EPROM, EEPROM, Flash]]
* [[Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones|Les mémoires SRAM synchrones]]
* [[Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)|Les mémoires RAM dynamiques (DRAM)]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire externe|Le contrôleur mémoire externe]]
===Les mémoires exotiques===
* [[Fonctionnement d'un ordinateur/Les mémoires associatives|Les mémoires associatives]]
* [[Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO|Les mémoires FIFO et LIFO]]
==Le processeur==
===L'architecture externe===
* [[Fonctionnement d'un ordinateur/Langage machine et assembleur|Langage machine et assembleur]]
* [[Fonctionnement d'un ordinateur/Les registres du processeur|Les registres du processeur]]
* [[Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme|Le modèle mémoire : alignement et boutisme]]
* [[Fonctionnement d'un ordinateur/Les modes d'adressage|Les modes d'adressage]]
* [[Fonctionnement d'un ordinateur/L'encodage des instructions|L'encodage des instructions]]
* [[Fonctionnement d'un ordinateur/Les jeux d'instructions|Les jeux d'instructions]]
* [[Fonctionnement d'un ordinateur/La pile d'appel et les fonctions|La pile d'appel et les fonctions]]
* [[Fonctionnement d'un ordinateur/Les interruptions et exceptions|Les interruptions et exceptions]]
===La micro-architecture===
* [[Fonctionnement d'un ordinateur/Les composants d'un processeur|Les composants d'un processeur]]
* [[Fonctionnement d'un ordinateur/Le chemin de données|Le chemin de données]]
* [[Fonctionnement d'un ordinateur/L'unité de chargement et le program counter|L'unité de chargement et le program counter]]
* [[Fonctionnement d'un ordinateur/L'unité de contrôle|L'unité de contrôle]]
* [[Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements|L'implémentation matérielle des branchements]]
===Les jeux d'instruction anciens===
* [[Fonctionnement d'un ordinateur/Les architectures à accumulateur|Les architectures à accumulateur]]
* [[Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire|Les architectures à pile et mémoire-mémoire]]
* [[Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins|Les processeurs 8 bits et moins]]
===L'espace d'adressage du processeur===
* [[Fonctionnement d'un ordinateur/L'espace d'adressage du processeur|L'espace d'adressage du processeur]]
* [[Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle|L'abstraction mémoire et la mémoire virtuelle]]
==Les entrées-sorties et périphériques==
* [[Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques|Les méthodes de synchronisation entre processeur et périphériques]]
* [[Fonctionnement d'un ordinateur/L'adressage des périphériques|L'adressage des périphériques]]
* [[Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension|Les périphériques et les cartes d'extension]]
==Les mémoires de stockage==
* [[Fonctionnement d'un ordinateur/Les mémoires de masse : généralités|Les mémoires de masse : généralités]]
* [[Fonctionnement d'un ordinateur/Les disques durs|Les disques durs]]
* [[Fonctionnement d'un ordinateur/Les solid-state drives|Les solid-state drives]]
* [[Fonctionnement d'un ordinateur/Les disques optiques|Les disques optiques]]
* [[Fonctionnement d'un ordinateur/Compléments sur les mémoires de masse|Compléments sur les mémoires de masse]]
==La ou les mémoires caches==
* [[Fonctionnement d'un ordinateur/Les mémoires cache|Les mémoires cache]]
* [[Fonctionnement d'un ordinateur/Le préchargement|Le préchargement]]
* [[Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer|Le ''Translation Lookaside Buffer'']]
==Le parallélisme d’instructions==
* [[Fonctionnement d'un ordinateur/Le pipeline|Le pipeline]]
===Les branchements et le ''front-end''===
* [[Fonctionnement d'un ordinateur/La prédiction de branchement|La prédiction de branchement]]
* [[Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions|Les optimisations du chargement des instructions]]
===Les pipelines multicycles simples===
* [[Fonctionnement d'un ordinateur/Les pipelines multicycles|Les pipelines multicycles]]
* [[Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions|L'émission dans l'ordre des instructions]]
* [[Fonctionnement d'un ordinateur/Le contournement (data forwarding)|Le contournement (data forwarding)]]
* [[Fonctionnement d'un ordinateur/Les premiers processeurs Intel|Les premiers processeurs Intel]]
===L’exécution dans le désordre===
* [[Fonctionnement d'un ordinateur/L'exécution dans le désordre|L'exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le renommage de registres|Le renommage de registres]]
* [[Fonctionnement d'un ordinateur/Les schedulers larges et leurs optimisations|Les schedulers larges et leurs optimisations]]
* [[Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo|Le scoreboarding et l'algorithme de Tomasulo]]
===Les optimisations des accès mémoire===
* [[Fonctionnement d'un ordinateur/La désambiguïsation mémoire|La désambiguïsation mémoire]]
* [[Fonctionnement d'un ordinateur/Le parallélisme mémoire|Le parallélisme mémoire]]
===Les processeurs superscalaires===
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires|Les processeurs superscalaires in-order]]
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires out-of-order|Les processeurs superscalaires out-of-order]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance|Les microarchitectures pour le x86 : haute performance]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : basse consommation|Les microarchitectures pour le x86 : basse consommation]]
===Le parallélisme d'instruction explicite===
* [[Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC|Les processeurs VLIW et EPIC]]
* [[Fonctionnement d'un ordinateur/Les architectures dataflow|Les architectures dataflow]]
==Les architectures parallèles==
* [[Fonctionnement d'un ordinateur/Les architectures parallèles|Les architectures parallèles]]
* [[Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs|Les architectures multiprocesseurs et multicœurs]]
* [[Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading|Les architectures multithreadées et Hyperthreading]]
* [[Fonctionnement d'un ordinateur/Les architectures à parallélisme de données|Les architectures à parallélisme de données]]
* [[Fonctionnement d'un ordinateur/La cohérence des caches|La cohérence des caches]]
* [[Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire|Les sections critiques et le modèle mémoire]]
==Annexes==
===Les nombres flottants : FPUs et coprocesseurs===
* [[Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87|Un exemple de jeu d'instruction : l'extension x87]]
* [[Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO|Les coprocesseurs : FPU et IO]]
===Les jeux d’instruction spécialisés===
* [[Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]]
* [[Fonctionnement d'un ordinateur/Les processeurs de traitement du signal|Les processeurs de traitement du signal]]
* [[Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement|Les architectures actionnées par déplacement]]
===Réseaux de neurones et accélérateurs d'IA===
* [[Fonctionnement d'un ordinateur/Les architectures systoliques|Les architectures systoliques]]
* [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]]
===Les autres annexes===
* [[Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation|L'accélération matérielle de la virtualisation]]
* [[Fonctionnement d'un ordinateur/Le matériel réseau|Le matériel réseau]]
* [[Fonctionnement d'un ordinateur/La tolérance aux pannes|La tolérance aux pannes]]
* [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires|Les ordinateurs de première génération : tubes à vide et mémoires]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires|Les ordinateurs à encodages non-binaires]]
* [[Fonctionnement d'un ordinateur/Les circuits réversibles|Les circuits réversibles]]
{{autocat}}
go712gfp7vkik9cost6eo3n5mrz3md4
Les cartes graphiques/Le rendu d'une scène 3D : concepts de base
0
79234
774457
767779
2026-10-08T13:58:29Z
Mewtow
31375
/* Les bases du rendu 3D */
774457
wikitext
text/x-wiki
Le premier jeu à utiliser de la "vraie 3D" texturée fut le jeu Quake, premier du nom. Et depuis sa sortie, la grande majorité des jeux vidéo utilisent de la 3D, même s'il existe encore quelques jeux en 2D. Face à la prolifération des jeux vidéo en 3D, les fabricants de cartes graphiques ont inventé les cartes accélératrices 3D, des cartes vidéo capables d'accélérer le rendu en 3D. Dans ce chapitre, nous allons voir comment elles fonctionnent et comment elles ont évolué dans le temps. Pour comprendre comment celles-ci fonctionnent, il faut faire quelques rapides rappels sur les bases du rendu 3D.
==Les bases du rendu 3D==
[[File:Rectangular parallelepiped labeled el.svg|vignette|Parallélépipède.]]
Une '''scène 3D''' est composée d'un espace en trois dimensions, dans laquelle le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple parallélépipède. Un des coins de ce parallélogramme sert d’origine à un système de coordonnées : il est à la position (0, 0, 0), et les axes partent de ce point en suivant les arêtes. Les objets seront placés à des coordonnées bien précises dans ce parallélogramme.
===Les objets 3D et leur géométrie===
<noinclude>[[File:Dolphin triangle mesh.png|vignette|Illustration d'un dauphin, représenté avec des triangles.]]</noinclude>
Dans la quasi-totalité des jeux vidéo actuels, les objets et la scène 3D sont modélisés par un assemblage de triangles collés les uns aux autres, ce qui porte le nom de '''maillage''', (''mesh'' en anglais). Il a été tenté dans le passé d'utiliser des quadrilatères (rendu dit en ''quad'') ou d'autres polygones, mais les contraintes techniques ont fait que ces solutions n'ont pas été retenues.
[[File:CG WIKI.jpg|centre|vignette|upright=2|Exemple de modèle 3D.]]
Les modèles 3D sont définis par leurs sommets, aussi appelés '''vertices''' dans le domaine du rendu 3D. Chaque sommet possède trois coordonnées, qui indiquent sa position dans la scène 3D : abscisse, ordonnée, profondeur. Les sommets sont regroupés en triangles, qui sont formés en combinant trois sommets entre eux. Les anciennes cartes graphiques géraient aussi d'autres formes géométriques, comme des points, des lignes, ou des quadrilatères. Les quadrilatères étaient appelés des ''quads'', et ce terme reviendra occasionnellement dans ce cours. De telles formes basiques, gérées nativement, sont appelées des '''primitives'''.
La représentation exacte d'un objet est donc une liste plus ou moins structurée de sommets. La liste doit préciser les coordonnées de chaque sommet, ainsi que comment les relier pour former des triangles. Pour cela, l'objet est représenté par une structure qui contient la liste des sommets, mais aussi de quoi savoir quels sont les sommets reliés entre eux par un segment. Nous en dirons plus dans le chapitre sur le rendu de la géométrie.
===La caméra : le point de vue depuis l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par :
* une position ;
* par la direction du regard (un vecteur).
À la caméra, il faut ajouter tout ce qui permet de déterminer le '''champ de vision'''. Le champ de vision contient tout ce qui est visible à l'écran. Et sa forme dépend de la perspective utilisée. Dans le cas le plus courant dans les jeux vidéos en 3D, il correspond à une '''pyramide de vision''' dont la pointe est la caméra, et dont les faces sont délimitées par les bords de l'écran. À l'intérieur de la pyramide, il y a un rectangle qui représente l'écran du joueur, appelé le '''''viewport'''''.
<noinclude>[[File:ViewFrustum.jpg|centre|vignette|upright=2|Caméra.]]</noinclude>
[[File:ViewFrustum.svg|vignette|upright=1|Volume délimité par la caméra (''view frustum'').]]
La majorité des jeux vidéos ajoutent deux plans :
* un ''near plane'' en-deça duquel les objets ne sont pas affichés. Il élimine du champ de vision les objets trop proches.
* Un ''far plane'', un '''plan limite''' au-delà duquel on ne voit plus les objets. Il élimine les objets trop lointains.
Avec ces deux plans, le champ de vision de la caméra est donc un volume en forme de pyramide tronquée, appelé le '''''view frustum'''''. Le tout est parfois appelée, bien que par abus de langage, la pyramide de vision. Avec d'autres perspectives moins utilisées, le ''view frustum'' est un pavé, mais nous n'en parlerons pas plus dans le cadre de ce cours car elles ne sont presque pas utilisés dans les jeux vidéos actuels.
===Les textures===
Tout objet à rendre en 3D est donc composé d'un assemblage de triangles, et ceux-ci sont éclairés et coloriés par divers algorithmes. Pour rajouter de la couleur, les objets sont recouverts par des '''textures''', des images qui servent de papier peint à un objet. Un objet géométrique est donc recouvert par une ou plusieurs textures qui permettent de le colorier ou de lui appliquer du relief.
[[File:Texture+Mapping.jpg|centre|vignette|upright=2|Texture Mapping]]
Notons que les textures sont des images comme les autres, codées pixel par pixel. Pour faire la différence entre les pixels de l'écran et les pixels d'une texture, on appelle ces derniers des '''texels'''. Ce terme est assez important, aussi profitez-en pour le mémoriser, nous le réutiliserons dans quelques chapitres.
Un autre point lié au fait que les textures sont des images est leur compression, leur format. N'allez pas croire que les textures sont stockées dans un fichier .jpg, .png ou tout autre format de ce genre. Les textures utilisent des formats spécialisés, comme le DXTC1, le S3TC ou d'autres, plus adaptés à leur rôle de texture. Mais qu'il s'agisse d'images normales (.jpg, .png ou autres) ou de textures, toutes sont compressées. Les textures sont compressées pour prendre moins de mémoire. Songez que la compression de texture est terriblement efficace, souvent capable de diviser par 6 la mémoire occupée par une texture. S'en est au point où les textures restent compressées sur le disque dur, mais aussi dans la mémoire vidéo ! Nous en reparlerons dans le chapitre sur la mémoire d'une carte graphique.
Plaquer une texture sur un objet peut se faire de deux manières, qui portent les noms de placage de texture inverse et direct. Le placage de texture direct a été utilisé au tout début de la 3D, sur des bornes d'arcade et les consoles de jeu 3DO, PS1, Sega Saturn. De nos jours, on utilise uniquement la technique de placage de texture inverse. Les deux seront décrites dans le détail plus bas.
===La différence entre rastérisation et lancer de rayons===
Les techniques de rendu 3D sont nombreuses, mais on peut les classer en deux grands types : le ''lancer de rayons'' et la ''rasterization''. Sans décrire les deux techniques, sachez cependant que le lancer de rayon n'est pas beaucoup utilisé pour les jeux vidéo. Il est surtout utilisé dans la production de films d'animation, d'effets spéciaux, ou d'autres rendu spéciaux. Dans les jeux vidéos, il est surtout utilisé pour quelques effets graphiques, la rasterization restant le mode de rendu principal.
La raison principale est que le lancer de rayons demande beaucoup de puissance de calcul. Une autre raison est que créer des cartes accélératrices pour le lancer de rayons n'est pas simple. Il a existé des cartes accélératrices permettant d'accélérer le rendu en lancer de rayons, mais elles sont restées confidentielles. Les cartes graphiques modernes incorporent quelques circuits pour accélérer le lancer de rayons, mais ils restent d'un usage marginal et servent de compléments au rendu par rastérization. Un chapitre entier sera dédié aux cartes accélératrices de lancer de rayons et nous verrons pourquoi le lancer de rayons est difficile à implémenter avec des performances convenables, ce qui explique que les jeux vidéo utilisent la ''rasterization''.
La rastérisation est structurée autour de trois étapes principales :
* Une étape purement logicielle, effectuée par le processeur, où le moteur physique calcule la géométrie de la scène 3D.
* Une étape de '''traitement de la géométrie''', qui gère tout ce qui a trait aux sommets et triangles.
* Une étape de '''rastérisation''' qui détermine sur quels pixels de l'écran est affiché le triangle.
* Une étape de '''traitement des pixels''', qui colorie les pixels et gère les textures.
Il existe plusieurs rendus différents et la rastérisation ne se fait pas de la même manière selon le rendu utilisé. Il existe des rendus sans textures, d'autres avec, d'autres avec éclairage, d'autres sans, etc. Par contre, l'étape de calcul de la géométrie est la même quel que soit le rendu ! Mieux : le calcul de la géométrie se fait de la même manière entre rastérisation et lancer de rayons, il est le même quelle que soit la technique de rendu 3D utilisée.
Les trois étapes précédentes sont réalisées dans des circuits ou processeurs séparés, comme on le verra plus tard. Et cela permet d'utiliser la technique dite du '''pipeline'''. Concrètement, supposons que la carte graphique traite les données par paquets de triangles (en réalité, c'est des paquets de sommets, mais passons). L'étape de traitement de la géométrie peut travailler sur un paquet de triangle, pendant que le paquet précédent est dans l'étape de rastérisation, et que le paquet encore précédent est en train de traiter ses pixels. Cela permet de traiter trois paquets de triangles en même temps, mais à des états d'avancements différents.
Mieux que cela : le traitement de la géométrie est lui-même composé d'une succession de sous-étapes, la rasterisation est elle-même découpée en plusieurs sous-étapes, et ainsi de suite. Le nombre d'étapes pour une carte graphique moderne dépasse la dizaine. La rastérisation calcule un rendu 3D avec une suite d'étapes consécutives qui doivent s'enchainer dans un ordre bien précis. L'ensemble de ces étapes est appelé le '''pipeline graphique''', qui sera détaillé dans ce qui suit.
==Le calcul de la géométrie==
Le calcul de la géométrie regroupe plusieurs manipulations différentes. La principale demande juste de placer les modèles 3D dans la scène, de placer les objets dans le monde. Puis, il faut centrer la scène 3D sur la caméra. Les deux changements ont pour point commun de demander des changements de repères. Par changement de repères, on veut dire que l'on passe d'un système de coordonnées à un autre. En tout, il existe trois changements de repères distincts qui sont regroupés dans l''''étape de transformation''' : un premier qui place chaque objet 3D dans la scène 3D, un autre qui centre la scène du point de vue de la caméra, et un autre qui corrige la perspective.
===Les trois étapes de transformation===
La première étape place les objets 3D dans la scène 3D. Un modèle 3D est représentée par un ensemble de sommets, qui sont reliés pour former sa surface. Les données du modèle 3D indiquent, pour chaque sommet, sa position par rapport au centre de l'objet qui a les coordonnées (0, 0, 0). La première étape place l'objet 3D à une position dans la scène 3D, déterminée par le moteur physique, qui a des coordonnées (X, Y, Z). Une fois placé dans la scène 3D, le centre de l'objet passe donc des coordonnées (0, 0, 0) aux coordonnées (X, Y, Z) et tous les sommets de l'objet doivent être mis à jour.
De plus, l'objet a une certaine orientation : il faut aussi le faire tourner. Enfin, l'objet peut aussi subir une mise à l'échelle : on peut le gonfler ou le faire rapetisser, du moment que cela ne modifie pas sa forme, mais simplement sa taille. En clair, le modèle 3D subit une translation, une rotation et une mise à l'échelle, les trois impliquant une modification des coordonnées des sommets..
[[File:Similarity and congruence transformations.svg|centre|vignette|upright=1.5|Transformations géométriques possibles pour chaque triangle.]]
Une fois le placement des différents objets effectué, la carte graphique effectue un changement de coordonnées pour centrer le monde sur la caméra. Au lieu de considérer un des bords de la scène 3D comme étant le point de coordonnées (0, 0, 0), il va passer dans le référentiel de la caméra. Après cette transformation, le point de coordonnées (0, 0, 0) sera la caméra. La direction de la vue du joueur sera alignée avec l'axe de la profondeur (l'axe Z).
[[File:View transform.svg|centre|vignette|upright=2|Étape de transformation dans un environnement en deux dimensions : avant et après. On voit que l'on centre le monde sur la position de la caméra et dans sa direction.]]
Enfin, il faut aussi corriger la perspective, ce qui est le fait de l'étape de projection, qui modifie la forme du ''view frustum'' sans en modifier le contenu. Différents types de perspective existent et celles-ci ont un impact différent les unes des autres sur le ''view frustum''. Dans le cas qui nous intéresse, le ''view frustum'' passe d’une forme de trapèze tridimensionnel à une forme de pavé dont l'écran est une des faces.
===Les changements de coordonnées se font via des multiplications de matrices===
Les trois étapes précédentes demande de faire des changements de coordonnées, chaque sommet voyant ses coordonnées remplacées par de nouvelles. Or, un changement de coordonnée s'effectue assez simplement, avec des matrices, à savoir des tableaux organisés en lignes et en colonnes avec un nombre dans chaque case. Un changement de coordonnées se fait simplement en multipliant le vecteur (X, Y, Z) des coordonnées d'un sommet par une matrice adéquate. Il existe des matrices pour la translation, la mise à l'échelle, d'autres pour la rotation, une autre pour la transformation de la caméra, une autre pour l'étape de projection, etc.
Un changement de coordonnée s'effectue assez simplement en multipliant le vecteur-coordonnées (X, Y, Z) d'un sommet par une matrice adéquate. Un petit problème est que les matrices qui le permettent sont des matrices avec 4 lignes et 4 colonnes. Or, la multiplication demande que le nombre de coordonnées du vecteur soit égal au nombre de colonnes. Pour résoudre ce petit problème, on ajoute une 4éme coordonnée aux sommets, la coordonnée homogène, qui ne sert à rien, et est souvent mise à 1, par défaut. Mais oublions ce détail.
Il se trouve que multiplier des matrices amène certaines simplifications. Au lieu de faire plusieurs multiplications de matrices, il est possible de fusionner les matrices en une seule, ce qui permet de simplifier les calculs. Ce qui fait que le placement des objets, changement de repère pour centrer la caméra, et d'autres traitements forts différents sont regroupés ensemble.
Le traitement de la géométrie implique, sans surprise, des calculs de géométrie dans l'espace. Et cela implique des opérations mathématiques aux noms barbares : produits scalaires, produits vectoriels, et autres calculs impliquant des vecteurs et/ou des matrices. Et les calculs vectoriels/matriciels impliquent beaucoup d'additions, de soustractions, de multiplications, de division, mais aussi des opérations plus complexes : calculs trigonométriques, racines carrées, inverse d'une racine carrée, etc.
Au final, un simple processeur peut faire ce genre de calculs, si on lui fournit le programme adéquat, l'implémentation est assez aisée. Mais on peut aussi implémenter le tout avec un circuit spécialisé, non-programmable. Les deux solutions sont possibles, tant que le circuit dispose d'assez de puissance de calcul. Les cartes graphiques anciennes contenaient un ou plusieurs circuits de multiplication de matrices spécialisés dans l'étape de transformation. Chacun de ces circuits prend un sommet et renvoie le sommet transformé. Ils sont composés d'un gros paquet de multiplieurs et d'additionneurs flottants. Pour plus d'efficacité, les cartes graphiques comportent plusieurs de ces circuits, afin de pouvoir traiter plusieurs sommets en même temps.
==L'élimination des surfaces cachées==
Un point important du rendu 3D est que ce que certaines portions de la scène 3D ne sont pas visibles depuis la caméra. Et idéalement, les portions de la scène 3D qui ne sont pas visibles à l'écran ne doivent pas être calculées. A quoi bon calculer des choses qui ne seront pas affichées ? Ce serait gâcher de la puissance de calcul. Et pour cela, de nombreuses optimisations visent à éliminer les calculs inutiles. Elles sont regroupées sous les termes de '''''clipping''''' ou de '''''culling'''''. La différence entre ''culling'' et ''clipping'' n'est pas fixée et la terminologie n'est pas claire. Dans ce qui va suivre, nous n'utiliserons que le terme ''culling''.
Les cartes graphiques modernes embarquent diverses méthodes de ''culling'' pour abandonner les calculs quand elles s’aperçoivent que ceux-ci portent sur une partie non-affichée de l'image. Cela fait des économies de puissance de calcul assez appréciables et un gain en performance assez important. Précisons que le ''culling'' peut être plus ou moins précoce suivant le type de rendu 3D utilisé, mais nous verrons cela dans la suite du chapitre.
===Les différentes formes de ''culling''/''clipping''===
La première forme de ''culling'' est le '''''view frustum culling''''', dont le nom indique qu'il s'agit de l'élimination de tout ce qui est situé en-dehors du ''view frustum''. Ce qui est en-dehors du champ de vision de la caméra n'est pas affiché à l'écran n'est pas calculé ou rendu, dans une certaine mesure. Le ''view frustum culling'' est assez trivial : il suffit d'éliminer ce qui n'est pas dans le ''view frustum'' avec quelques calculs de coordonnées assez simples. Quelques subtilités surviennent quand un triangle est partiellement dans le ''view frustrum'', ce qui arrive parfois si le triangle est sur un bord de l'écran. Mais rien d'insurmontable.
[[File:View frustum culling.svg|centre|vignette|upright=1|''View frustum culling'' : les parties potentiellement visibles sont en vert, celles invisibles en rouge et celles partiellement visibles en bleu.]]
Les autres formes de ''culling'' visent à éliminer ce qui est dans le ''view frustum'', mais qui n'est pas visible depuis la caméra. Pensez à des objets cachés par un autre objet plus proche, par exemple. Ou encore, pensez aux faces à l'arrière d'un objet opaque qui sont cachées par l'avant. Ces deux cas correspondent à deux types de ''culling''. L'élimination des objets masqués par d'autres est appelé l'''occlusion culling''. L'élimination des parties arrières d'un objet est appelé le ''back-face culling''. Dans les deux cas, nous parlerons d''''élimination des surfaces cachées'''.
[[File:Occlusion culling example PL.svg|centre|vignette|''Occlusion culling'' : les objets en bleu sont visibles, ceux en rouge sont masqués par les objets en bleu.]]
Le lancer de rayons n'a pas besoin d'éliminer les surfaces cachées, il ne calcule que les surfaces visibles. Par contre, la rastérisation demande d'éliminer les surfaces cachées. Sans cela, le rendu est incorrect dans le pire des cas, ou alors le rendu calcule des surfaces invisibles pour rien. Il existe de nombreux algorithmes logiciels pour implémenter l'élimination des surfaces cachées, mais la carte graphique peut aussi s'en charger.
L'''occlusion culling'' demande de connaitre la distance à la caméra de chaque triangle. La distance à la caméra est appelée la '''profondeur''' du triangle. Elle est déterminée à l'étape de rastérisation et est calculée à chaque sommet. Lors de la rastérisation, chaque sommet se voit attribuer trois coordonnées : deux coordonnées x et y qui indiquent sa position à l'écran, et une coordonnée de profondeur notée z.
===L'algorithme du peintre===
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les triangles du plus lointain au plus proche. L'idée est que si deux triangles se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Quelque chose qui devrait vous rappeler le rendu 2D, où les sprites sont rendus du plus lointain au plus proche. Il ne s'agit ni plus ni moins que de l''''algorithme du peintre'''.
[[File:Polygons cross.svg|vignette|Polygons cross]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
Un autre problème est que l'algorithme demande de trier les triangles d'une scène 3D selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
===Le tampon de profondeur===
[[File:Z-buffer no text.jpg|vignette|Z-buffer correspondant à un rendu]]
Une autre solution utilise ce qu'on appelle un '''tampon de profondeur''', aussi appelé un ''z-buffer''. Il s'agit d'un tableau, stocké en mémoire vidéo, qui mémorise la coordonnée z de l'objet le plus proche pour chaque pixel. Par défaut, ce tampon de profondeur est initialisé avec la valeur de profondeur maximale, celle du ''far plane'' du ''viewfrustum''. Au fur et à mesure que les objets seront calculés, le tampon de profondeur est mis à jour, conservant ainsi la trace de l'objet le plus proche de la caméra.
Si jamais un triangle a une coordonnée z plus grande que celle du tampon de profondeur, cela veut dire qu'il est situé derrière un objet déjà rendu. Il est éliminé (sauf si transparence il y a) et le tampon de profondeur n'a pas à être mis à jour. Dans le cas contraire, l'objet est plus près de la caméra et sa coordonnée z remplace l'ancienne valeur z dans le tampon de profondeur.
[[File:Z-buffer.svg|centre|vignette|upright=2.0|Illustration du processus de mise à jour du Z-buffer.]]
Il existe des techniques alternatives pour coder la coordonnée de profondeur, qui se distinguent par le fait que la coordonnée z n'est pas proportionnelle à la distance entre le fragment et la caméra. Mais il s'agit là de détails assez mathématiques que je me permets de passer sous silence. Dans la suite de ce cours, nous allons juste parler de profondeur pour regrouper toutes ces techniques, conventionnelles ou alternatives.
Toutes les cartes graphiques modernes utilisent un système de ''z-buffer''. C'est la seule solution pour avoir des performances dignes de ce nom. Il faut cependant noter qu'elles utilisent des tampons de profondeur légèrement modifiés, qui ne mémorisent pas la coordonnée de profondeur, mais une valeur dérivée. Pour simplifier, ils ne mémorisent pas la coordonnée de profondeur z, mais son inverse 1/z. Les raisons à cela ne peuvent pas encore être expliquées à ce moment du cours, aussi nous allons simplement dire que c'est une histoire de correction de perspective.
Les coordonnées z et 1/z sont codées sur quelques bits, allant de 16 bits pour les anciennes cartes graphiques, à 24/32 bits pour les cartes plus récentes. De nos jours, les Z-buffer de 16 bits sont abandonnés et toutes les cartes graphiques utilisent des coordonnées z de 24 à 32 bits. La raison est que les Z-buffer de 16 bits ont une précision insuffisante, ce qui fait que des artefacts peuvent survenir. Si deux objets sont suffisamment proches, le tampon de profondeur n'a pas la précision suffisante pour discriminer les deux objets. Pour lui, les deux objets sont à la même place. Conséquence : il faut bien choisir un des deux objets et ce choix se fait pixel par pixel, ce qui fait des artefacts visuels apparaissent. On parle alors de '''''z-fighting'''''. Voici ce que cela donne :
[[File:Z-fighting.png|centre|vignette|Z-fighting]]
Un défaut du tampon de profondeur est qu'il ne gère pas correctement les objets transparents. Dès que de la transparence est présente dans une scène 3D, le tampon de profondeur ne peut pas être utilisé. Une solution pour cela est de rendre une scène 3D en deux phases : une pour les objets opaques, une avec les objets transparents. La où on rend les objets opaques utilise le tampon de profondeur, mais il est désactivé lors de la seconde.
==La rastérisation et les textures==
Dans cette section, nous allons voir ensemble l'étape de rastérisation et l'étape de traitement des pixels. La rastérisation est difficile à expliquer, surtout que son rôle exact dépend de la technique de rendu utilisée. Pour simplifier, elle projette un rendu en 3D sur un écran en 2D. Une autre explication tout aussi vague est qu'elle s'occupe la traduction des triangles en un affichage pixelisé à l'écran. Elle détermine à quoi ressemble la scène visible sur l'écran. C'est par exemple lors de cette étape que sont appliquées certaines techniques de ''culling'', qui éliminent les portions non-visibles de l'image, ainsi qu'une correction de la perspective et diverses opérations d'interpolation dont nous parlerons dans plusieurs chapitres.
La rastérisation et placage de textures sont deux opérations très liées entre elles. Il existe deux manières principales pour lier les textures à la géométrie : la méthode directe et la méthode inverse (''UV Mapping''). Et les deux font que la rastérisation se fait de manière très différente. Précisons cependant que les rendus les plus simples n'utilisent pas de textures du tout. Ils se contentent de colorier les triangles, voire d'un simple rendu en fil de fer basé sur du tracé de lignes. Dans la suite de cette section, nous allons voir les quatre types de rendu principaux : le rendu en fils de fer, le rendu colorié, et deux rendus utilisant des textures.
===Le rendu en fil de fer===
[[File:Obj lineremoval.png|vignette|Rendu en fil de fer d'un objet 3D.]]
Le '''rendu 3D en fils de fer''' est illustré ci-contre. Il s'agit d'un rendu assez ancien, utilisé au tout début de la 3D, sur des machines qu'on aurait du mal à appeler ordinateurs. Il se contente de tracer des lignes à l'écran, lignes qui connectent deux sommets, qui ne sont autres que les arêtes de la géométrie de la scène rendue. Le tout était suffisant pour réaliser quelques jeux vidéos rudimentaires. Les tout premiers jeux vidéos utilisaient ce rendu, l'un d'entre eux étant Maze War, le tout premier FPS.
{|
|[[File:Maze war.jpg|vignette|Maze war]]
|[[File:Maze representation using wireframes 2022-01-10.gif|centre|vignette|Maze representation using wireframes 2022-01-10]]
|}
Le monde est calculé en 3D, il y a toujours un calcul de la géométrie, la scène est rastérisée normalement, les portions invisbles de l'image sont retirées, mais il n'y a pas d'application de textures après rastérisation. À la place, un algorithme de tracé de ligne trace les lignes à l'écran. Quand un triangle passe l'étape de rastérisation, l'étape de rastérisation fournit la position des trois sommets sur l'écran. En clair, elle fournit les coordonnées de trois pixels, un par sommet. À la suite, un algorithme de tracé de ligne trace trois lignes, une par paire de sommet.
L'implémentation demande juste d'avoir une unité de calcul géométrique, une unité de rastérisation, et un VDC qui supporte le tracé de lignes. Elle est donc assez simple et ne demande pas de circuits de gestion des textures ni de ROP. Le VDC écrit directement dans le ''framebuffer'' les lignes à tracer.
Il a existé des proto-cartes graphiques spécialisées dans ce genre de rendu, comme le '''''Line Drawing System-1''''' de l'entreprise Eans & Sutherland. Nous détaillerons son fonctionnement dans quelques chapitres.
===Le rendu à primitives colorées===
[[File:MiniFighter.png|vignette|upright=1|Exemple de rendu pouvant être obtenu avec des sommets colorés.]]
Une amélioration du rendu précédent utilise des triangles/''quads'' coloriés. Chaque triangle ou ''quad'' est associé à une couleur, et cette couleur est dessinée sur le triangle/''quad''après la rastérisation. Le rendu est une amélioration du rendu en fils de fer. L'idée est que chaque triangle/''quad'' est associé à une couleur, qui est dessinée sur le triangle/''quad'' après la rastérisation. La technique est nommée ''colored vertices'' en anglais, nous parlerons de '''rendu à maillage coloré'''.
[[File:Malla irregular de triángulos modelizando una superficie convexa.png|centre|vignette|upright=2|Maillage coloré.]]
La couleur est propagée lors des calculs géométriques et de la rastérisation, sans subir de modifications. Une fois un rendu en fils de fer effectué, la couleur du triangle est récupérée. Le triangle/''quad'' rendu correspond à un triangle/''quad'' à l'écran. Et l'intérieur de ce triangle/''quad'' est colorié avec la couleur transmise. Pour cela, on utilise encore une fois une fonction du VDC : celle du remplissage de figure géométrique. Nous l’avions vu en parlant des VDC à accélération 2D, mais elle est souvent prise en charge par les ''blitters''. Ils peuvent remplir une figure géométrique avec une couleur unique, on réutilise cette fonction pour colorier le triangle/''quad''. L'étape de rastérisation fournit les coordonnées des sommets de la figure géométrique, le ''blitter'' les utilise pour colorier la figure géométrique.
Niveau matériel, quelques bornes d'arcade ont utilisé ce rendu. La toute première borne d'arcade utilisant le rendu à maillage coloré est celle du jeu I Robot, d'Atari, sorti en 1983. Par la suite, dès 1988, les cartes d'arcades Namco System 21 et les bornes d'arcades Sega Model 1 utilisaient ce genre de rendu. On peut s'en rendre compte en regardant les graphismes des jeux tournant sur ces bornes d'arcade. Des jeux comme Virtua Racing, Virtua Fighter ou Virtua Formula sont assez parlants à ce niveau. Leurs graphismes sont assez anguleux et on voit qu'ils sont basés sur des triangles uniformément colorés.
Pour ceux qui veulent en savoir plus sur la toute première borne d'arcade en rendu à maillage colorée, la borne ''I Robot'' d'Atari, voici une vidéo youtube à ce sujet :
* [https://www.youtube.com/watch?v=6miEkPENsT0 I Robot d'Atari, le pionnier de la 3D Flat.]
===Le placage de textures direct===
Les deux rendus précédents sont très simples, mais n'utilisent pas de textures. Et il est temps de voir les deux rendus qui utilisent des textures. Il y en a deux types, appelés rendu avec placage de texture direct et indirect, nous allons voir le '''rendu par placage de texture direct''' en premier. Et nous l'appellerons ''rendu direct'' dans ce qui suit, pour simplifier les explications. Le rendu direct est une sorte d'intermédiaire entre rendu 2D à base de ''sprite'' et rendu 3D moderne. La géométrie est rendue en 3D pour générer des ''quads'', mais ces ''quads'' ne servent à guider la copie des sprites/textures dans le ''framebuffer''.
Le rendu direct peut utiliser aussi bien des triangles que des ''quads'', mais nous allons partir du principe qu'il utilise des '''''quads''''', à savoir que les objets 3D sont composés de quadrilatères. Lorsqu'un ''quad'' est rastérisé, sa forme à l'écran est un rectangle déformé par la perspective. On obtient un rectangle si le ''quad'' est vu de face, un trapèze si on le voit de biais. Pour résumer, un 'quad' correspond à un quadrilatère sur l'écran. Et le placage de texture est effectué quand on sait quelles sont les coordonnées de ce quadrilatère à l'écran.
Le rendu direct associe à chaque ''quad'', une texture rectangulaire, assez semblable à un ''sprite'' si on regarde de très loin. L'idée est de remplir le quadrilatère à l'écran avec ce ''sprite'', cette texture. La seule subtilité est que la texture/''sprite'' doit être déformé pour rentrer dans le quadrilatère, cette déformation dépendant de la perspective, de l'angle entre le ''quad'' et la caméra, et ainsi de suite. Une fois déformée, le ''sprite''/texture est directement recopié au bon endroit dans le ''framebuffer'', pour remplir le quadrilatère.
Le sprite doit être déformé de deux manières : il doit être agrandi/réduit en fonction de la taille de la figure affichée à l'écran, tourné en fonction de l'orientation du ''quad'', déformé pour gérer la perspective. Pour cela, il faut connaitre les coordonnées de profondeur de chaque bord d'un ''quad'', et de faire quelques calculs. N'importe quel VDC incluant un ''blitter'' avec une gestion du zoom/rotation des sprites peut le faire. Au-delà de cette déformation, il vaut mieux appliquer un filtre d'interpolation pour lisser l'image des ''sprite'' déformés, afin d'obtenir une bonne qualité d'image. Le filtre en question et n'est pas très différent du filtrage de texture qui lisse les textures à l'écran.
[[File:TextureMapping.png|centre|vignette|upright=2|Exemple caricatural de placage de texture sur un ''quad''.]]
Un point important est que les ''quads'' doivent être rendus du plus lointain au plus proche. Sans cela, on obtient rapidement des erreurs de rendu. L'idée est que si deux quads se chevauchent, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. L'écriture du sprite du second quad écrasera les données du premier quad, pour les portions recouvertes, lors de l'écriture du sprite dans le ''framebuffer''. Quelque chose qui devrait vous rappeler le rendu 2D avec l'algorithme du peintre, où les sprites sont rendus du plus lointain au plus proche.
Le rendu inverse utilise très souvent des triangles pour la géométrie, alors que le rendu direct a tendance à utiliser des ''quads'', mais il ne s'agit pas d'une différence stricte. L'usage de triangles/''quads'' peut se faire aussi bien avec un rendu direct comme avec un rendu inverse. Cependant, le rendu en ''quad'' se marie très bien au rendu direct, alors que le rendu en triangle colle mieux au rendu inverse.
L'avantage de cette technique est qu'on parcourt les textures dans un ordre bien précis. Par exemple, on peut parcourir la texture ligne par ligne, l'exploiter par blocs de 4*4 pixels, etc. Et accéder à une texture de manière prédictible se marie bien avec l'usage de mémoires caches, ce qui est un avantage en matière de performances. Mais un même pixel du ''framebuffer'' est écrit plusieurs fois quand plusieurs quads se superposent, alors que le rendu inverse gère la situation avec une seule écriture (sauf si usage de la transparence). De plus, la gestion de la transparence était compliquée et les jeux devaient ruser en utilisation des solutions logicielles assez complexes.
Niveau implémentation matérielle, une carte graphique en rendu direct demande juste trois circuits. Le premier est un circuit de calcul géométrique, qui rend la scène 3D. Le tri des quads est souvent réalisé par le processeur principal, et non pas par un circuit séparé. Toutes les étapes au-delà de l'étape de rastérisation étaient prises en charge par un VDC amélioré, qui écrivait des sprites/textures directement dans le ''framebuffer''.
{|class="wikitable"
|-
! Géométrie
| Processeurs dédiés programmé pour émuler le pipeline graphique
|-
! Tri des quads du plus lointain au plus proche
| Processeur principal (implémentation logicielle)
|-
! Application des textures
| ''Blitter'' amélioré, capable de faire tourner et de zoomer sur des ''sprites''.
|}
L'implémentation était très simple et réutilisait des composants déjà existants : des VDC 2D pour l'application des textures, des processeurs dédiés pour la géométrie. Les unités de calcul de la géométrie étaient généralement implémentées avec un ou plusieurs processeurs dédiés. Vu qu'on savait déjà effectuer le rendu géométrique en logiciel, pas besoin de créer un circuit sur mesure. Il suffisait de dédier un processeur spécialisé rien que pour les calculs géométriques et on lui faisait exécuter un code déjà bien connu à la base. En clair, ils utilisaient un code spécifique pour émuler un circuit fixe. C'était clairement la solution la plus adaptée pour l'époque.
Les unités géométriques étaient des processeurs RISC, normalement utilisés dans l'embarqué ou sur des serveurs. Elles utilisaient parfois des DSP. Pour rappel, les DSP des processeurs de traitement de signal assez communs, pas spécialement dédiés aux rendu 3D, mais spécialisé dans le traitement de signal audio, vidéo et autre. Ils avaient un jeu d'instruction assez proche de celui des cartes graphiques actuelles, et supportaient de nombreuses instructions utiles pour le rendu 3D.
Le rendu direct a été utilisé dans la période de transition entre rendu 2D et rendu 3D, car il était très adapté pour faire cette transition. Coupler un VDC à un processeur pour la géométrie était particulièrement simple à l'époque. Le rendu direct a été utilisé sur des bornes d'arcade dès les années 90. Outre les bornes d'arcade, quelques consoles de 5ème génération utilisaient le rendu direct, avec les mêmes solutions matérielles. La géométrie était calculée sur plusieurs processeurs dédiés. Le reste du pipeline était géré par un VDC 2D qui implémentait le placage de textures. Deux consoles étaient dans ce cas : la 3DO, et la Sega Saturn. Le rendu direct est aujourd'hui abandonné.
===Le placage de textures inverse===
Le rendu précédent, le rendu direct, permet d'appliquer des textures directement dans le ''framebuffer''. Mais comme dit plus haut, il existe une seconde technique pour plaquer des textures, appelé le '''placage de texture inverse''', aussi appelé l'''UV Mapping''. Elle associe une texture complète pour un modèle 3D,contrairement au placage de tecture direct qui associe une texture par ''quad''/triangle. L'idée est que l'on attribue un texel à chaque sommet. Plus précisémment, chaque sommet est associé à des '''coordonnées de texture''', qui précisent quelle texture appliquer, mais aussi où se situe le texel à appliquer dans la texture. Par exemple, la coordonnée de texture peut dire : je veux le pixel qui est à ligne 5, colonne 27 dans cette texture. La correspondance entre texture et géométrie est réalisée lorsque les créateurs de jeu vidéo conçoivent le modèle de l'objet.
[[File:Texture Mapping example.png|centre|vignette|upright=2|Exemple de placage de texture.]]
Dans les faits, on n'utilise pas de coordonnées entières de ce type, mais deux nombres flottants compris entre 0 et 1. La coordonnée 0,0 correspond au texel en bas à gauche, celui de coordonnée 1,1 est tout en haut à droite. L'avantage est que ces coordonnées sont indépendantes de la résolution de la texture, ce qui aura des avantages pour certaines techniques de rendu, comme le ''mip-mapping''. Les deux coordonnées de texture sont notées u,v avec DirectX, ou encore s,t dans le cas général : u est la coordonnée horizontale, v la verticale.
[[File:UVMapping.png|centre|vignette|upright=2|UV Mapping]]
Avec le placage de texture inverse, la rastérisation se fait grosso-modo en trois étapes : la rastérisation proprement dite, le placage de textures, et les opérations finales qui écrivent un pixel dans le ''framebuffer''. Au niveau du matériel, ainsi que dans la plupart des API 3D, les trois étapes sont réalisées par des circuits séparés.
[[File:01 3D-Rasterung-a.svg|vignette|Illustration du principe de la rasterization. La surface correspondant à l'écran est subdivisée en pixels carrés, de coordonnées x et y. La caméra est placée au point e. Pour chaque pixel, on trace une droite qui part de la caméra et qui passe par le pixel considéré. L'intersection entre une surface et cette droite se fait en un point, appartenant à un triangle.]]
Lors de la rasterisation, chaque triangle se voit attribuer un ou plusieurs pixels à l'écran. Pour bien comprendre, imaginez une ligne droite qui part de caméra et qui passe par un pixel sur le plan de l'écran. Cette ligne intersecte 0, 1 ou plusieurs objets dans la scène 3D. Les triangles situés ces intersections entre cette ligne et les objets rencontrés seront associés au pixel correspondant. L'étape de rastérisation prend en entrée un triangle et renvoie la coordonnée x,y du pixel associé.
Il s'agit là d'une simplification, car un triangle tend à occuper plusieurs pixels sur l'écran. L'étape de rastérisation fournit la liste de tous les pixels occupés par un triangle, et les traite un par un. Quand un triangle est rastérisé, le rasteriseur détermine la coordonnée x,y du premier pixel, applique une texture dessus, puis passe au suivant, et rebelote jusqu'à ce que tous les pixels occupés par le triangles aient été traités.
L'implémentation matérielle du placage de texture inverse est beaucoup plus complexe que pour les autres techniques. Pour être franc, nous allons passer le reste du cours à parler de l'implémentation matérielle du placage de texture inverse, ce qui prendra plus d'une dizaine de chapitres.
==La transparence, les fragments et les ROPs==
Dans ce qui suit, nous allons parler uniquement de la rastérisation avec placage de textures inverse. Les autres formes de rastérisation ne seront pas abordées. La raison est que tous les GPUs modernes utilisent cette forme de rastérisation, les exceptions étant rares. De même, ils utilisent un tampon de profondeur, pour l'élimination des surfaces cachées.
La rastérisation effectue donc des calculs géométriques, suivis d'une étape de rastérisation, puis de placage des textures. Ces trois étapes sont réalisées par une unité géométrique, une unité de rastérisation, et un circuit de placage de textures. Du moins sur le principe, car les cartes graphiques modernes ont fortement optimisé l'implémentation et n'ont pas hésité à fusionner certains circuits. Mais nous verrons cela en temps voulu, nous n'allons pas résumer plusieurs décennies d'innovation technologique en quelques paragraphes.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Placage de textures
|}
Mais où mettre le tampon de profondeur ? Intuitivement, on se dit qu'il vaut mieux faire l'élimination des surfaces cachées le plus tôt possible, dès que la coordonnée de profondeur est connue. Et elle est connu à l'étape de rastérisation, une fois les sommets transformés.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Tampon de profondeur
| Placage de textures
|}
En réalité, la profondeur des fragments est gérée par un circuit appelé le '''''Raster Operations Pipeline''''' (ROP), situé à la toute fin du pipeline graphique. Dans ce qui suit, nous utiliserons l'abréviation ROP pour simplifier les explications. Le ROP effectue quelques traitements sur les fragments, avant d'enregistrer l'image finale dans la mémoire vidéo. Il est placé à la fin du pipeline pour gérer correctement la transparence. Et nous allons voir pourquoi la transparence est gérée à la fin du pipeline.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Placage de textures
| ''Raster Operations Pipeline''
|}
===Le mélange ''alpha''===
La transparence se manifeste quand plusieurs objets sont l'un derrière l'autre. Histoire de simplifier les explications, nous allons d'abord voir le cas où un objet semi-transparent est devant un objet opaque. La couleur perçue est alors un mélange de la couleur de l'objet opaque et celle de l'objet semi-transparent. Le mélange dépend d'à quel point l'objet semi-transparent est transparent. Avec un objet parfaitement transparent, seul l'objet opaque est visible. Avec un objet à moitié transparent, la couleur finale sera pour moitié celle de l'objet opaque, pour moitié celle de l'objet semi-transparent. Et c'est pareil pour les cas intermédiaires entre un objet totalement transparent et un objet totalement opaque.
La transparence d'un objet/pixel est définie par un nombre, appelé la '''composante ''alpha'''''. Plus la composante alpha est élevée, plus le pixel est opaque. Elle vaut 0 pour un objet opaque et 1 pour un objet transparent. Elle est ajoutée aux composantes RGB, ce qui fait que tout fragment contient une "couleur de transparence" en plus des couleurs RGB. Elle agit comme un coefficient qui dit comment mélanger la couleur d'un objet transparent et d'un objet opaque. Le calcul de la transparence est une moyenne pondérée par la composante alpha. On parle alors d''''''alpha blending'''''.
: <math>\text{Couleur finale} = \alpha \times \text{Couleur de l'objet transparent} + (1 - \alpha) \times \text{Couleur de l'objet opaque}</math>
[[File:Texture splatting.png|centre|vignette|upright=2.0|Calcul de transparence. La première ligne montre le produit pour l'objet transparent, la seconde ligne est celle de l'objet opaque. La troisième ligne est celle de l'addition finale.]]
Maintenant, qu'en est-il du cas où plusieurs objets sont superposés ? Si vous tracez une demi-droite dont l'origine est la caméra et qui passe par le pixel, il arrive qu'elle intersecte la géométrie en plusieurs points, un point par objet sur la ligne du regarde. Sans transparence, l'objet le plus proche cache tous les autres et c'est donc lui qui décide de la couleur du pixel. Mais avec un objet transparent, la couleur finale est un mélange de la couleur de plusieurs points d'intersection. Il faut donc calculer un pseudo-pixel pour chaque point d'intersection, auquel on donne le nom de '''fragment'''. Un fragment possède une position à l'écran, une coordonnée de profondeur, une couleur, ainsi que quelques autres informations potentiellement utiles.
Les fragments attribués à un même pixel, qui sont à la même position sur l'écran, sont combinés pour obtenir la couleur finale de ce pixel. Il est possible d'utiliser le mélange ''alpha'' pour cela. Il suffit de faire le mélange ''alpha'' entre le fragment qui vient d'être calculé, et le pixel dans le ''framebuffer''. Pour cela, le fragment a une composante ''alpha'', qui est ajouté aux trois couleurs RGB. Le pixel déjà dans le ''framebuffer'' est un résultat temporaire, né du mélange ''alpha'' de tous les fragments précédents.
Un défaut de cette méthode est qu'elle fonctionne assez mal avec un tampon de profondeur. Si le tampon de profondeur est activé, le mélange ''alpha'' ne fonctionne que si les objets sont rendus du plus lointain au plus proche. Et procéder dans cet ordre a un défaut : on dessine des objets dans le ''framebuffer'', pour qu'ensuite les objets devant écrasent ce qui a déjà été dessiné. Un même pixel peut donc être dessiné plusieurs fois, dont une seule sera pertinente. Et ces écritures utilisent de la bande passante mémoire, qui est une ressource précieuse sur un GPU moderne. Il s'agit d'un phénomène appelé '''''overdraw''''', ou sur-dessinage en français. Quelques optimisations permettent d'éliminer l'''overdraw'' en rendant les objets du plus proche au plus lointain, d'autres permettent de dessiner des objets dans un ordre arbitraire, mais nous ne pouvons pas en parler ici.
Beaucoup de moteurs 3D rendent séparément les objets opaques et transparents. Une première passe rend les objets opaques, puis les objets transparents sont rendus dans une seconde passe. Les objets opaques sont rendus dans le désordre, ce qui fait qu'on n'a pas à les trier, alors que les objets transparents doivent être triés selon leur distance. un autre avantage est que le mélange ''alpha'' est désactivé lors de la première passe, alors que c'est la mise à jour du tampon de profondeur qui est désactivé lors de la seconde passe, ce qui augmente un peu les performances dans les deux cas.
===Le test ''alpha''===
Le test ''alpha'' est une technique qui permet d'annuler le rendu d'un fragment en fonction de sa transparence. Si la composante alpha est en-dessous ou au-dessus d'un seuil, le fragment est simplement abandonné. Le seuil en question est configurable, de même que la comparaison utilisée : on peut éliminer le fragment si sa transparence est au-dessus d'un certain seuil, en-dessous, égal, différent, etc.
Il s'agit d'une optimisation qui est utile dans certains scénarios spécifiques. Par exemple, si l'objet a une transparence très élevée, du genre 95%, autant le compter comme complétement transparent, afin d'éviter des opérations de mélange ''alpha''. En effet, les opérations de mélange ''alpha'' sont très lentes, car elles demandent de faire des opérations de lecture-écriture en mémoire vidéo : on lit un pixel dans le ''framebuffer'', on applique le mélange ''alpha'' et on écrit le résultat en mémoire vidéo. L'''alpha test'' permet donc de gagner en performance au prix d'une baisse de la qualité d'image.
Il y a cependant des cas où l'usage du test ''alpha'' est primordial, au-delà d'une question de performances. Un exemple classique est celui du rendu du feuillage dans un jeu 3D. Un feuillage est composé en assemblant plusieurs images de feuilles. Chaque feuille est un carré sur lequel on place une texture de feuille, qui est opaque pour la partie verte des feuilles, transparente pour le reste. Les carrés ne sont cependant pas superposés, mais s'intersectent fortement, ce qui fait que le mélange ''alpha'' ne donne pas de bons résultats. L'usage du test ''alpha'' permet d'obtenir un rendu correct. Pour d'informations via ce lien :
* [https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f Anti-aliased Alpha Test: The Esoteric Alpha To Coverage].
===Les effets de brouillard===
Les '''effets de brouillard''' sont nécessaires dans certains jeux vidéo pour l'ambiance (pensez à des jeux d'horreur comme Silent Hill), mais ils ont surtout été utilisés pour économiser des calculs. L'idée est de ne pas calculer les graphismes au-delà d'une certaine distance, sans que cela se voie. Le ''view frustum'' utilise alors un plan limite, au-delà duquel on ne voit pas les objets. Mais ce plan limite donne une cassure inesthétique dans le rendu. Pour masquer cette cassure, les programmeurs ajoutaient un effet de brouillard. Les objets au-delà du plan limite étaient totalement dans le brouillard, puis ce brouillard se réduisait progressivement en se rapprochant de la caméra, avant de s'annuler à partir d'une certaine distance.
Pour calculer le brouillard, on effectue un mélange ''alpha'' entre la couleur du pixel et une ''couleur de brouillard''. La différence est que l'on n'utilise pas la transparence pour faire le mélange, mais un '''coefficient de brouillard''', noté <math>\text{fog}(z)</math>.
: <math>\text{Couleur finale} = \text{fog}(z) \times \text{Couleur de brouillard} + [ 1 - \text{fog}(z) ] \times \text{Couleur du pixel}</math>
Le coefficient de brouillard dépend de la coordonnée de profondeur, de la distance du pixel par rapport à la caméra. Le brouillard démarre à une distance <math>z_{fog-start}</math>, et masque totalement les objets à partir d'une distance <math>z_{fog-end}</math>. Entre les deux, le coefficient de brouillard dépend de la distance. OpenGL autorise trois formules de calcul suivantes :
: <math>\text{fog}(z) = \frac{z_{fog-end} - z}{z_{fog-end} - z_{fog-start}}</math>
: <math>\text{fog}(z) = e^{- k \times z}</math>
: <math>\text{fog}(z) = e^{- (k \times z)^2}</math>
==L'éclairage d'une scène 3D==
L'éclairage d'une scène 3D calcule les ombres, mais aussi la luminosité de chaque pixel, ainsi que bien d'autres effets graphiques. Les algorithmes d'éclairage ont longtemps été implémentés directement en matériel, les cartes graphiques géraient l'éclairage dans des circuits spécialisés. Aussi, il est important de voir ces algorithmes d'éclairage. Il est possible d'implémenter l'éclairage à deux endroits différents du pipeline : juste avant la rastérisation, et après la rastérisation.
===Les sources de lumière et les couleurs associées===
L'éclairage d'une scène 3D provient de sources de lumières, comme des lampes, des torches, le soleil, etc. Il existe de nombreux types de sources de lumière, et nous n'allons parler que des principales. Elles sont au nombre de quatre et elles sont illustrées ci-dessous.
[[File:3udUJ.gif|centre|vignette|upright=2|Types de sources de lumière.]]
[[File:Graphics lightmodel directional.png|vignette|upright=1.0|Source de lumière directionnelle.]]
Les '''sources directionnelles''' servent à modéliser des sources de lumière très éloignées, comme le soleil ou la lune. Elles sont simplement définies par un vecteur qui indique la direction de la lumière, rien de plus.
Les '''sources ponctuelles''' sont des points, qui émettent de la lumière dans toutes les directions. Elles sont définies par une position, et une intensité lumineuse, éventuellement la couleur de la lumière émise. Il existe deyux types de sources de lumière ponctuelles.
* Le premières émettent de manière égale dans toutes les directions. Elles sont appelées des ''point light'' dans le schéma du dessus.
* Les secondes émettent de la lumière dans une '''direction privilégiée'''. L'exemple le plus parlant est celui d'une lampe-torche : elle émet de la lumière "tout droit", dans la direction où la lampe est orientée. Elles sont appelées des ''sport light'' dans le schéma du dessus. La direction privilégiée est un vecteur, notée v dans le schéma du dessous.
[[File:Graphics lightmodel ambient.png|vignette|upright=1.0|Lumière ambiante.]]
En théorie, la lumière rebondit sur les surfaces et a tendance à se disperser un peu partout à force de rebondir. C'est ce qui explique qu'on arrive à voir à l'intérieur d'une pièce si une fenêtre est ouverte. Il en résulte un certain '''éclairage ambiant''', qui est assez difficile à représenter dans un moteur de rendu 3D. Auparavant, l'éclairage ambiant était simulé par une lumière égale en tout point de la scène 3D, appelée simplement la '''lumière ambiante'''. Précisément, on suppose que la lumière ambiante en un point vient de toutes les directions et a une intensité constante, identique dans toutes les directions. Le tout est illustré ci-contre. C'est assez irréaliste, mais ça donne une bonne approximation de la lumière ambiante.
===La lumière incidente : le terme géométrique===
Pour simplifier, nous allons supposer que l'éclairage est calculé pour chaque sommet, pas par triangle. C'est de loin le cas le plus courant, aussi ce n'est pas une simplification abusive. La lumière qui arrive sur un sommet est appelée la '''lumière incidente'''.
La couleur d'un sommet dépend de deux choses : la lumière incidente directe, comment il réfléchit cette lumière. Mathématiquement, il est possible de résumer cela avec le produit de deux termes : l'intensité de la lumière incidente, une fonction qui indique comment la surface réfléchit la lumière incidente. La fonction en question est appelée la '''réflectivité bidirectionnelle'''. Le terme anglais est ''bidirectional reflectance distribution function'', abrévié en BRDF, et nous utiliserons cette abréviation dans ce qui suit.
: <math>\text{Couleur finale} = \text{Lumière incidente} \times BRDF(...)</math>
La lumière incidente vient soit directement des sources de lumière, soit de la lumière qui a rebondit sur d'autres objets proches. La première est appelée la lumière directe, celle qui vient des rebonds s'appelle la lumière indirecte. Pour simplifier, la lumière indirecte est gérée par la lumière ambiante, nous passons sous silence les techniques d'illumination globale. En clair : nous allons nous limiter au cas où la lumière incidente vient directement d'une source de lumière, pas d'un rebond.
Intuitivement, la lumière incidente est simplement égale à l'intensité de la source de lumière. Sauf que ce n'est qu'une approximation, et une assez mauvaise. En réalité, l'approximation est bonne si la lumière arrive proche de la verticale, mais elle est d'autant plus mauvaise que la lumière arrive penchée, voire rasante.
La raison : la lumière incidente sera étalée sur une surface plus grande, si elle arrive penchée. Si vous vous souvenez de vos cours de collège, c'est le même principe qui explique les saisons. La lumière du soleil est proche de la verticale en été, mais est de plus en plus penché quand on s'avance vers l'Hiver. La lumière solaire est donc étalée sur une surface plus grande, ce qui fait qu'un point de la surface recevra moins de lumière, celle-ci étant diluée, étalée.
[[File:Radiación solar.png|centre|vignette|upright=2|Exemple avec la lumière solaire.]]
[[File:Angle of incidence.svg|vignette|upright=1|Angle d'incidence.]]
En clair, tout dépend de l''''angle d'incidence''' de la lumière. Reste à voir comment calculer cet angle. La lumière incidente est définie par un vecteur, qui part de la source de lumière et atterrit sur le sommet considéré. Imaginez simplement que ce vecteur suit un rayon lumineux provenant de la source de lumière. Le vecteur pour la lumière incidente sera noté L. L'angle d'incidence est l'angle que fait ce vecteur avec la verticale de la surface, au niveau du sommet considéré.
[[File:Graphics lightmodel ptsource.png|vignette|Normale de la surface.]]
Pour cela, les calculs d'éclairage ont besoin de connaitre la verticale d'un sommet. Un sommet est donc associé à un vecteur, appelé la '''normale''', qui indique la verticale en ce point. Deux sommets différents peuvent avoir deux normales différentes, même s'ils sont proches. Elles sont d'autant plus différentes que la surface est rugueuse, non-lisse. La normale est prédéterminée lors de la création du modèle 3D, il n'y a pas besoin de le calculer. Par contre, elle est modifiée lors de l'étape de transformation, quand on place le modèle 3D dans la scène 3D. Les deux autres vecteurs sont à calculer à chaque image, car ils changent quand on bouge le sommet.
La lumière qui arrive sur la surface dépend de l'angle entre la normale et le vecteur L. Précisément, elle dépend du cosinus de cet angle. En multipliant ce cosinus avec l'intensité de la lumière, on a la lumière arrivante. La couleur finale d'un pixel est donc :
: <math>\text{Couleur finale} = I \times \cos{(N, L)} \times BRDF(...)</math>
Le terme <math>I \times \cos{N, L}</math> ne dépend pas de la surface considérée. Juste de la position de la source de lumière, de la position du sommet et de son orientation par rapport à la lumière. Aussi, il est parfois appelé le '''terme géométrique''', en opposition aux propriétés de la surface. Les propriétés de la surface sont définies par un '''''material''''', qui indique comment il réfléchit la lumière, ainsi que sa texture.
===Le produit scalaire de deux vecteurs===
Calculer le terme géométrique demande de calculer le cosinus d'un angle. Et il n'est pas le seul : les autres calculs d'éclairage que nous allons voir demandent de calculer des cosinus. Or, les calculs trigonométriques sont très gourmands pour le GPU. Pour éviter le calcul d'un cosinus, les GPU utilisent une opération mathématique appelée le ''produit scalaire''. Le produit scalaire agit sur deux vecteurs, que l'on notera A et B. Un produit scalaire prend : la longueur des deux vecteurs, et l'angle entre les deux vecteurs noté <math>\omega</math>. Le produit scalaire est équivalent à la formule suivante :
: <math>\text{Produit scalaire de deux vecteurs A et B} = \vec{A} \cdot \vec{B} = A \times B \times \cos{(\omega)}</math>, avec A et B la longueur des deux vecteurs A et B.
L'avantage est que le produit scalaire se calcule simplement avec des additions, soustractions et multiplications, des opérations que les cartes graphiques savent faire très facilement. Le produit scalaire de deux vecteurs de coordonnées x,y,z est le suivant :
: <math>\vec{A} \cdot \vec{B} = x_A \times x_B + y_A \times y_B + z_A \times z_B</math>
En clair, on multiplie les coordonnées identiques, et on additionne les résultats. Rien de compliqué.
Un avantage est que tous les vecteurs vus précédemment sont normalisés, à savoir qu'ils ont une longueur qui vaut 1. Ainsi, le calcul du produit scalaire devient équivalent au calcul du produit scalaire.
===La réflexion de la lumière sur la surface===
[[File:Ray Diagram 2.svg|vignette|Reflection de la lumière sur une surface parfaitement lisse.]]
Maintenant que nous venons de voir le terme géométrique, voyons le BRDF, qui définit comment la surface de l'objet 3D réfléchit la lumière. Vos cours de collège vous ont sans doute appris que la lumière est réfléchie avec le même angle d'arrivée. L'angle d'incidence et l'angle de réflexion sont égaux, comme illustré ci-contre. On parle alors de '''réflexion parfaite'''.
Mais cela ne vaut que pour une surface parfaitement lisse, comme un miroir parfait. Dans la réalité, une surface a tendance à renvoyer des rayons dans toutes les directions. La raison est qu'une surface réelle est rugueuse, avec de petites aspérités et des micro-reliefs, qui renvoient la lumière dans des directions "aléatoires". La lumière « rebondit » sur la surface de l'objet et une partie s'éparpille dans un peu toutes les directions. On parle alors de '''réflexion diffuse'''.
{|
|-
|[[File:Dioptre reflexion diffuse speculaire refraction.svg|vignette|upright=1.4|Différence entre réflexion diffuse et spéculaire.]]
|[[File:Diffuse reflection.svg|vignette|upright=1|Réflexion diffuse.]]
|}
Maintenant, imaginons que la surface n'ait qu'une réflexion diffuse, pas d'autres formes de réflexion. Et imaginons aussi que cette réflexion diffuse soit parfaite, à savoir que la lumière réfléchie soit renvoyée à l'identique dans toutes les directions, sans aucune direction privilégiée. On a alors le ''material'' le plus simple qui soit, appelé un '''''diffuse material'''''.
Vu que la lumière est réfléchie à l'identique dans toutes les directions, elle sera identique peu importe où on place la caméra. La lumière finale ne dépend donc que des propriété de la surface, que de sa couleur. En clair, il suffit de donner une '''couleur diffuse''' à chaque sommet. La couleur diffuse est simplement multipliée par le terme géométrique, pour obtenir la lumière réfléchie finale. Rien de plus, rien de moins. Cela donne l'équation suivante, avec les termes suivants :
* L est le vecteur pour la lumière incidente ;
* N est la normale du sommet ;
* I est l'intensité de la source de lumière ;
* <math>C_d</math> est la couleur diffuse.
: <math>\text{Illumination diffuse} = C_d \times \left[ I \times (\vec{N} \cdot \vec{L}) \right]</math>
Rajoutons maintenant l'effet de la lumière ambiante à un ''material'' de ce genre. Pour rappel, la lumière ambiante vient de toutes les directions à part égale, ce qui fait que son angle d'incidence n'a donc pas d'effet. L'intensité de la lumière ambiante est déterminée lors de la création de la scène 3D, c'est une constante qui n'a pas à être calculée. Pour obtenir l'effet de la lumière ambiante sur un objet, il suffit de multiplier sa couleur diffuse par l'intensité de la lumière ambiante. Cependant, de nombreux moteurs de jeux ajoutent une '''couleur ambiante''', différente de la couleur diffuse.
: <math>\text{Illumination ambiante} = C_a \times I_a</math> avec <math>C_a</math> la couleur ambiante du point de surface et <math>I_a</math> l'intensité de la lumière ambiante.
En plus de la réflexion diffuse parfaite, de nombreux matériaux ajoutent une '''réflexion spéculaire''', qui n'est pas exactement la réflexion parfaite, en est très proche. Les rayons réfléchis sont très proches de la direction de réflexion parfaite, et s'atténuent très vite en s'en éloignant. Le résultat ressemble à une sorte de petit "point blanc", très lumineux, orienté vers la source de lumière, appelé le '''''specular highlight'''''. La réflexion diffuse est prédominante pour les matériaux rugueux, alors que la réflexion spéculaire est dominante sur les matériaux métalliques ou très lisses.
[[File:Phong components version 4.png|centre|vignette|upright=3.0|Couleurs utilisées dans l'algorithme de Phong.]]
[[File:Phong Vectors.svg|vignette|Vecteurs utilisés dans l'algorithme de Phong (et dans le calcul de l'éclairage, de manière générale).]]
Pour calculer la réflexion spéculaire, il faut d'abord connaitre le vecteur pour la réflexion parfaite, que nous noterons R dans ce qui suit. Le vecteur R peut se calculer avec la formule ci-dessous :
: <math>\vec{R} = 2 (\vec{L} \cdot \vec{N}) \times \vec{N} - \vec{L} </math>
La réflexion spéculaire dépend de l'angle entre la direction du regard et la normale : plus celui-ci est proche de l'angle de réflexion parfaite, plus la réflexion spéculaire sera intense. Le vecteur pour la direction du regard sera noté V, pour vue ou vision. La réflexion spéculaire est une fonction qui dépend de l'angle entre les vecteurs R et V. Le calcul de la réflexion spéculaire utilise une '''couleur spéculaire''', qui est l'équivalent de la couleur diffuse pour la réflexion spéculaire.
: <math>\text{BRDF spéculaire} = C_s \times f(\vec{R} \cdot \vec{V}) </math>
La fonction varie grandement d'un modèle de calcul spéculaire à l'autre. Aussi, je ne rentre pas dans le détail. L'essentiel est que vous compreniez que le calcul de l'éclairage utilise de nombreux calculs géométriques, réalisés avec des produits scalaires. Les calculs géométriques utilisent la couleur d'un sommet, la normale du sommet, et le vecteur de la lumière incidente. Les autres informations sont calculées à l'exécution.
===Les algorithmes d'éclairage basiques : par triangle, par sommet et par pixel===
Dans tout ce qui a été dit précédemment, l'éclairage est calculé pour chaque sommet, ce qui fait qu'on parle d''''éclairage par sommet''', ou ''vertex lighting''. Il attribue une illumination/couleur à chaque sommet de la scène 3D. Mais une fois qu'on a obtenu la couleur des sommets, reste à colorier les triangles.
Pour obtenir la couleur finale d'un pixel, l'éclairage par sommet fait une moyenne de la couleur de chaque sommet. La moyenne est une moyenne pondérée, qui tient compte de la distance du sommet avec le pixel. Plus le pixel est loin d'un sommet, plus son coefficient est petit. Typiquement, le coefficient varie entre 0 et 1 : de 1 si le pixel est sur le sommet, à 0 si le pixel est sur un des sommets adjacents. La moyenne effectuée est généralement une interpolation bilinéaire, qui est prise en charge par l'étape de rastérisation.
L'éclairage par sommet a eu son heure de gloire, mais il est maintenant remplacé par l''''éclairage par pixel''' (''per-pixel lighting''), qui calcule l'éclairage pixel par pixel. En clair, l’éclairage est finalisé après l'étape de rastérisation, il ne se fait pas qu'au niveau de la géométrie. Il existe plusieurs types d'éclairage par pixel, mais on peut les classer en deux grands types : l'éclairage de Phong et le ''bump/normal mapping''.
L''''éclairage de Phong''' calcule l'éclairage pixel par pixel. Avec cet algorithme, la géométrie n'est pas éclairée : les couleurs des sommets ne sont pas calculées. À la place, les normales sont envoyées à l'étape de rastérisation, qui effectue une opération d'interpolation, qui renvoie une normale pour chaque pixel. Les calculs d'éclairage utilisent alors ces normales pour faire les calculs d'éclairage pour chaque pixel.
{|
|-
|[[File:Per vertex lighting.png|vignette|upright=1|Eclairage par sommet.]]
|[[File:Per fragment lighting.png|vignette|upright=1|Eclairage de Phong.]]
|-
|[[File:Per vertex lighting example.png|vignette|upright=1|Eclairage par sommet.]]
|[[File:Per fragment lighting example.png|vignette|upright=1|Eclairage de Phong.]]
|}
La technique du '''''normal mapping''''' est assez simple à expliquer, sans compter que plusieurs cartes graphiques l'ont implémentée directement dans leurs circuits. Là où l'éclairage de Phong interpole les normales pour chaque pixel, le ''normal-mapping'' précalcule les normales d'une surface dans une texture, appelée la ''normal-map''. Lors du rendu 3D, la carte graphique lit les normales adéquates directement depuis cette texture, puis fait les calculs d'éclairage avec. Le ''normal mapping'' permettent d'ajouter du relief et des détails sur des surfaces planes en jouant sur l'éclairage. Il permet de simplifier grandement la géométrie rendue, tout en utilisant l'éclairage pour compenser.
[[File:WallSimpleAndNormalMapping.png|centre|vignette|upright=2|Différence sans et avec ''normal-mapping''.]]
===Les ''shaders'' : des programmes exécutés sur le GPU===
Maintenant que nous venons de voir les algorithmes d'éclairages, il est temps de voir comment les réaliser sur une carte graphique. Nous venons de voir qu'il y a une différence entre l'éclairage par pixel et par sommet. Intuitivement, l'éclairage par sommet devrait se faire avec les calculs géométriques, alors que l'éclairage par pixel devrait se faire après avoir appliqué les textures.
Les toutes premières cartes graphiques ne géraient ni l'éclairage par sommet, ni l'éclairage par pixel. Elles laissaient les calculs géométriques au CPU. Par la suite, la Geforce 256 a intégré '''circuit de ''Transform & Lightning''''', qui s'occupait de tous les calculs géométriques, éclairage par sommet inclus (d'où le L de T&L). Elle gérait alors l'éclairage par sommet, mais un algorithme particulier, qui n'était pas très flexible. Il ne gérait que des ''material'' bien précis (des ''Phong materials''), rien de plus.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Unité de T&L : géométrie
| Rastérisation
| Placage de textures
| ''Raster Operations Pipeline''
|}
L'amélioration suivante est venue sur la Geforce 3 : l'unité de T&L est devenue programmable. Au vu le grand nombre d'algorithmes d'éclairages possibles et le grand nombre de ''materials'' possibles, c'était la seule voie possibles. Les programmeurs pouvaient programmer leurs propres algorithmes d'éclairage par sommet, même s'ils devaient aussi programmer les étapes de transformation et de projection. Mais nous détaillerons cela dans un chapitre dédié sur l'historique des GPUs.
Ce qui est important est que la Geforce 3 a introduit une fonctionnalité absolument cruciale pour le rendu 3D moderne : les '''''shaders'''''. Il s'agit de programmes informatiques exécutés par la carte graphique, qui servaient initialement à coder des algorithmes d'éclairage. D'où leur nom : ''shader'' pour ''shading'' (éclairage en anglais). Cependant, l'usage modernes des ''shaders'' dépasse le cadre des algorithmes d'éclairage.
L'avantage est que cela simplifie grandement l'implémentation des algorithmes d'éclairage. Pas besoin de les intégrer dans la carte graphique pour les utiliser, pas besoin d'un circuit distinct pour chaque algorithme. Sans ''shaders'', si la carte graphique ne gère pas un algorithme d'éclairage, on ne peut pas l'utiliser. À la rigueur, il est parfois possible de l'émuler avec des contournements logiciels, mais au prix de performances souvent désastreuses. Avec des ''shaders'', il est possible de programmer l'algorithme d'éclairage de notre choix, pour l'exécuter sur la carte graphique, avec des performances plus que convenables.
[[File:Implémentation de l'éclairage sur les cartes graphiques.png|vignette|Implémentation de l'éclairage sur les cartes graphiques]]
Il existe plusieurs types de ''shaders'', mais les deux principaux sont les '''''vertex shaders''''' et les '''''pixel shaders'''''. Les pixels ''shaders'' s'occupent de l'éclairage par pixel, leur nom est assez parlent. Les vertex ''shaders'' s'occupent de l'éclairage par sommet, mais aussi des étapes de transformation/projection. Je parle bien des trois étapes de transformation vues plus haut, qui effectuent des calculs de transformation de coordonnées avec des matrices. La raison à cela est que les calculs de transformation ressemblent beaucoup aux calculs d'éclairage par sommet. Ils impliquent tous deux des calculs vectoriels, comme des produits scalaires et des produits vectoriels, qui agissent sur des sommets/triangles. Si la carte graphique incorpore un processeur de ''shader'' capable de faire de tels calculs, alors il peut servir pour les deux.
Pour implémenter les ''shaders'', il a fallu ajouter des processeurs à la carte graphique. Les processeurs en question exécutent les ''shaders'', ils peuvent lire ou écrire dans des textures, mais ne font rien d'autres. Les ''vertex shaders'' font tout ce qui a trait à la géométrie, ils remplacent l'unité de T&L. Les pixels ''shaders'' sont entre la rastérisation et les ROPs, ils sont très liés à l'unité de texture.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| rowspan="2" class="f_rouge" | ''Vertex shader''
| rowspan="2" | Rastérisation
| Placage de textures
| rowspan="2" |''Raster Operations Pipeline''
|-
| class="f_rouge" | ''Pixel shader''
|}
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=Les cartes d'affichage des anciens PC
| prevText=Les cartes d'affichage des anciens PC
| next=Avant les GPUs : les cartes accélératrices 3D
| nextText=Avant les GPUs : les cartes accélératrices 3D
}}{{autocat}}
</noinclude>
o485q275wyr6muol4zr8t82jij3ya0q
774458
774457
2026-10-08T13:59:52Z
Mewtow
31375
/* Les changements de coordonnées se font via des multiplications de matrices */
774458
wikitext
text/x-wiki
Le premier jeu à utiliser de la "vraie 3D" texturée fut le jeu Quake, premier du nom. Et depuis sa sortie, la grande majorité des jeux vidéo utilisent de la 3D, même s'il existe encore quelques jeux en 2D. Face à la prolifération des jeux vidéo en 3D, les fabricants de cartes graphiques ont inventé les cartes accélératrices 3D, des cartes vidéo capables d'accélérer le rendu en 3D. Dans ce chapitre, nous allons voir comment elles fonctionnent et comment elles ont évolué dans le temps. Pour comprendre comment celles-ci fonctionnent, il faut faire quelques rapides rappels sur les bases du rendu 3D.
==Les bases du rendu 3D==
[[File:Rectangular parallelepiped labeled el.svg|vignette|Parallélépipède.]]
Une '''scène 3D''' est composée d'un espace en trois dimensions, dans laquelle le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple parallélépipède. Un des coins de ce parallélogramme sert d’origine à un système de coordonnées : il est à la position (0, 0, 0), et les axes partent de ce point en suivant les arêtes. Les objets seront placés à des coordonnées bien précises dans ce parallélogramme.
===Les objets 3D et leur géométrie===
<noinclude>[[File:Dolphin triangle mesh.png|vignette|Illustration d'un dauphin, représenté avec des triangles.]]</noinclude>
Dans la quasi-totalité des jeux vidéo actuels, les objets et la scène 3D sont modélisés par un assemblage de triangles collés les uns aux autres, ce qui porte le nom de '''maillage''', (''mesh'' en anglais). Il a été tenté dans le passé d'utiliser des quadrilatères (rendu dit en ''quad'') ou d'autres polygones, mais les contraintes techniques ont fait que ces solutions n'ont pas été retenues.
[[File:CG WIKI.jpg|centre|vignette|upright=2|Exemple de modèle 3D.]]
Les modèles 3D sont définis par leurs sommets, aussi appelés '''vertices''' dans le domaine du rendu 3D. Chaque sommet possède trois coordonnées, qui indiquent sa position dans la scène 3D : abscisse, ordonnée, profondeur. Les sommets sont regroupés en triangles, qui sont formés en combinant trois sommets entre eux. Les anciennes cartes graphiques géraient aussi d'autres formes géométriques, comme des points, des lignes, ou des quadrilatères. Les quadrilatères étaient appelés des ''quads'', et ce terme reviendra occasionnellement dans ce cours. De telles formes basiques, gérées nativement, sont appelées des '''primitives'''.
La représentation exacte d'un objet est donc une liste plus ou moins structurée de sommets. La liste doit préciser les coordonnées de chaque sommet, ainsi que comment les relier pour former des triangles. Pour cela, l'objet est représenté par une structure qui contient la liste des sommets, mais aussi de quoi savoir quels sont les sommets reliés entre eux par un segment. Nous en dirons plus dans le chapitre sur le rendu de la géométrie.
===La caméra : le point de vue depuis l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par :
* une position ;
* par la direction du regard (un vecteur).
À la caméra, il faut ajouter tout ce qui permet de déterminer le '''champ de vision'''. Le champ de vision contient tout ce qui est visible à l'écran. Et sa forme dépend de la perspective utilisée. Dans le cas le plus courant dans les jeux vidéos en 3D, il correspond à une '''pyramide de vision''' dont la pointe est la caméra, et dont les faces sont délimitées par les bords de l'écran. À l'intérieur de la pyramide, il y a un rectangle qui représente l'écran du joueur, appelé le '''''viewport'''''.
<noinclude>[[File:ViewFrustum.jpg|centre|vignette|upright=2|Caméra.]]</noinclude>
[[File:ViewFrustum.svg|vignette|upright=1|Volume délimité par la caméra (''view frustum'').]]
La majorité des jeux vidéos ajoutent deux plans :
* un ''near plane'' en-deça duquel les objets ne sont pas affichés. Il élimine du champ de vision les objets trop proches.
* Un ''far plane'', un '''plan limite''' au-delà duquel on ne voit plus les objets. Il élimine les objets trop lointains.
Avec ces deux plans, le champ de vision de la caméra est donc un volume en forme de pyramide tronquée, appelé le '''''view frustum'''''. Le tout est parfois appelée, bien que par abus de langage, la pyramide de vision. Avec d'autres perspectives moins utilisées, le ''view frustum'' est un pavé, mais nous n'en parlerons pas plus dans le cadre de ce cours car elles ne sont presque pas utilisés dans les jeux vidéos actuels.
===Les textures===
Tout objet à rendre en 3D est donc composé d'un assemblage de triangles, et ceux-ci sont éclairés et coloriés par divers algorithmes. Pour rajouter de la couleur, les objets sont recouverts par des '''textures''', des images qui servent de papier peint à un objet. Un objet géométrique est donc recouvert par une ou plusieurs textures qui permettent de le colorier ou de lui appliquer du relief.
[[File:Texture+Mapping.jpg|centre|vignette|upright=2|Texture Mapping]]
Notons que les textures sont des images comme les autres, codées pixel par pixel. Pour faire la différence entre les pixels de l'écran et les pixels d'une texture, on appelle ces derniers des '''texels'''. Ce terme est assez important, aussi profitez-en pour le mémoriser, nous le réutiliserons dans quelques chapitres.
Un autre point lié au fait que les textures sont des images est leur compression, leur format. N'allez pas croire que les textures sont stockées dans un fichier .jpg, .png ou tout autre format de ce genre. Les textures utilisent des formats spécialisés, comme le DXTC1, le S3TC ou d'autres, plus adaptés à leur rôle de texture. Mais qu'il s'agisse d'images normales (.jpg, .png ou autres) ou de textures, toutes sont compressées. Les textures sont compressées pour prendre moins de mémoire. Songez que la compression de texture est terriblement efficace, souvent capable de diviser par 6 la mémoire occupée par une texture. S'en est au point où les textures restent compressées sur le disque dur, mais aussi dans la mémoire vidéo ! Nous en reparlerons dans le chapitre sur la mémoire d'une carte graphique.
Plaquer une texture sur un objet peut se faire de deux manières, qui portent les noms de placage de texture inverse et direct. Le placage de texture direct a été utilisé au tout début de la 3D, sur des bornes d'arcade et les consoles de jeu 3DO, PS1, Sega Saturn. De nos jours, on utilise uniquement la technique de placage de texture inverse. Les deux seront décrites dans le détail plus bas.
===La différence entre rastérisation et lancer de rayons===
Les techniques de rendu 3D sont nombreuses, mais on peut les classer en deux grands types : le ''lancer de rayons'' et la ''rasterization''. Sans décrire les deux techniques, sachez cependant que le lancer de rayon n'est pas beaucoup utilisé pour les jeux vidéo. Il est surtout utilisé dans la production de films d'animation, d'effets spéciaux, ou d'autres rendu spéciaux. Dans les jeux vidéos, il est surtout utilisé pour quelques effets graphiques, la rasterization restant le mode de rendu principal.
La raison principale est que le lancer de rayons demande beaucoup de puissance de calcul. Une autre raison est que créer des cartes accélératrices pour le lancer de rayons n'est pas simple. Il a existé des cartes accélératrices permettant d'accélérer le rendu en lancer de rayons, mais elles sont restées confidentielles. Les cartes graphiques modernes incorporent quelques circuits pour accélérer le lancer de rayons, mais ils restent d'un usage marginal et servent de compléments au rendu par rastérization. Un chapitre entier sera dédié aux cartes accélératrices de lancer de rayons et nous verrons pourquoi le lancer de rayons est difficile à implémenter avec des performances convenables, ce qui explique que les jeux vidéo utilisent la ''rasterization''.
La rastérisation est structurée autour de trois étapes principales :
* Une étape purement logicielle, effectuée par le processeur, où le moteur physique calcule la géométrie de la scène 3D.
* Une étape de '''traitement de la géométrie''', qui gère tout ce qui a trait aux sommets et triangles.
* Une étape de '''rastérisation''' qui détermine sur quels pixels de l'écran est affiché le triangle.
* Une étape de '''traitement des pixels''', qui colorie les pixels et gère les textures.
Il existe plusieurs rendus différents et la rastérisation ne se fait pas de la même manière selon le rendu utilisé. Il existe des rendus sans textures, d'autres avec, d'autres avec éclairage, d'autres sans, etc. Par contre, l'étape de calcul de la géométrie est la même quel que soit le rendu ! Mieux : le calcul de la géométrie se fait de la même manière entre rastérisation et lancer de rayons, il est le même quelle que soit la technique de rendu 3D utilisée.
Les trois étapes précédentes sont réalisées dans des circuits ou processeurs séparés, comme on le verra plus tard. Et cela permet d'utiliser la technique dite du '''pipeline'''. Concrètement, supposons que la carte graphique traite les données par paquets de triangles (en réalité, c'est des paquets de sommets, mais passons). L'étape de traitement de la géométrie peut travailler sur un paquet de triangle, pendant que le paquet précédent est dans l'étape de rastérisation, et que le paquet encore précédent est en train de traiter ses pixels. Cela permet de traiter trois paquets de triangles en même temps, mais à des états d'avancements différents.
Mieux que cela : le traitement de la géométrie est lui-même composé d'une succession de sous-étapes, la rasterisation est elle-même découpée en plusieurs sous-étapes, et ainsi de suite. Le nombre d'étapes pour une carte graphique moderne dépasse la dizaine. La rastérisation calcule un rendu 3D avec une suite d'étapes consécutives qui doivent s'enchainer dans un ordre bien précis. L'ensemble de ces étapes est appelé le '''pipeline graphique''', qui sera détaillé dans ce qui suit.
==Le calcul de la géométrie==
Le calcul de la géométrie regroupe plusieurs manipulations différentes. La principale demande juste de placer les modèles 3D dans la scène, de placer les objets dans le monde. Puis, il faut centrer la scène 3D sur la caméra. Les deux changements ont pour point commun de demander des changements de repères. Par changement de repères, on veut dire que l'on passe d'un système de coordonnées à un autre. En tout, il existe trois changements de repères distincts qui sont regroupés dans l''''étape de transformation''' : un premier qui place chaque objet 3D dans la scène 3D, un autre qui centre la scène du point de vue de la caméra, et un autre qui corrige la perspective.
===Les trois étapes de transformation===
La première étape place les objets 3D dans la scène 3D. Un modèle 3D est représentée par un ensemble de sommets, qui sont reliés pour former sa surface. Les données du modèle 3D indiquent, pour chaque sommet, sa position par rapport au centre de l'objet qui a les coordonnées (0, 0, 0). La première étape place l'objet 3D à une position dans la scène 3D, déterminée par le moteur physique, qui a des coordonnées (X, Y, Z). Une fois placé dans la scène 3D, le centre de l'objet passe donc des coordonnées (0, 0, 0) aux coordonnées (X, Y, Z) et tous les sommets de l'objet doivent être mis à jour.
De plus, l'objet a une certaine orientation : il faut aussi le faire tourner. Enfin, l'objet peut aussi subir une mise à l'échelle : on peut le gonfler ou le faire rapetisser, du moment que cela ne modifie pas sa forme, mais simplement sa taille. En clair, le modèle 3D subit une translation, une rotation et une mise à l'échelle, les trois impliquant une modification des coordonnées des sommets..
[[File:Similarity and congruence transformations.svg|centre|vignette|upright=1.5|Transformations géométriques possibles pour chaque triangle.]]
Une fois le placement des différents objets effectué, la carte graphique effectue un changement de coordonnées pour centrer le monde sur la caméra. Au lieu de considérer un des bords de la scène 3D comme étant le point de coordonnées (0, 0, 0), il va passer dans le référentiel de la caméra. Après cette transformation, le point de coordonnées (0, 0, 0) sera la caméra. La direction de la vue du joueur sera alignée avec l'axe de la profondeur (l'axe Z).
[[File:View transform.svg|centre|vignette|upright=2|Étape de transformation dans un environnement en deux dimensions : avant et après. On voit que l'on centre le monde sur la position de la caméra et dans sa direction.]]
Enfin, il faut aussi corriger la perspective, ce qui est le fait de l'étape de projection, qui modifie la forme du ''view frustum'' sans en modifier le contenu. Différents types de perspective existent et celles-ci ont un impact différent les unes des autres sur le ''view frustum''. Dans le cas qui nous intéresse, le ''view frustum'' passe d’une forme de trapèze tridimensionnel à une forme de pavé dont l'écran est une des faces.
===Les changements de coordonnées se font via des multiplications de matrices===
Les trois étapes précédentes demande de faire des changements de coordonnées, chaque sommet voyant ses coordonnées remplacées par de nouvelles. Or, un changement de coordonnée s'effectue assez simplement, avec des matrices, à savoir des tableaux organisés en lignes et en colonnes avec un nombre dans chaque case. Un changement de coordonnées se fait simplement en multipliant le vecteur (X, Y, Z) des coordonnées d'un sommet par une matrice adéquate. Il existe des matrices pour la translation, la mise à l'échelle, d'autres pour la rotation, une autre pour la transformation de la caméra, une autre pour l'étape de projection, etc.
Un changement de coordonnée s'effectue assez simplement en multipliant le vecteur-coordonnées (X, Y, Z) d'un sommet par une matrice adéquate. Un petit problème est que les matrices qui le permettent sont des matrices avec 4 lignes et 4 colonnes. Or, la multiplication demande que le nombre de coordonnées du vecteur soit égal au nombre de colonnes. Pour résoudre ce petit problème, on ajoute une 4éme coordonnée aux sommets, la coordonnée homogène, qui ne sert à rien pour le moment. Elle est souvent mise à 1, par défaut. Mais oublions ce détail.
Il se trouve que multiplier des matrices amène certaines simplifications. Au lieu de faire plusieurs multiplications de matrices, il est possible de fusionner les matrices en une seule, ce qui permet de simplifier les calculs. Ce qui fait que le placement des objets, changement de repère pour centrer la caméra, et d'autres traitements forts différents sont regroupés ensemble.
Le traitement de la géométrie implique, sans surprise, des calculs de géométrie dans l'espace. Et cela implique des opérations mathématiques aux noms barbares : produits scalaires, produits vectoriels, et autres calculs impliquant des vecteurs et/ou des matrices. Et les calculs vectoriels/matriciels impliquent beaucoup d'additions, de soustractions, de multiplications, de division, mais aussi des opérations plus complexes : calculs trigonométriques, racines carrées, inverse d'une racine carrée, etc.
Au final, un simple processeur peut faire ce genre de calculs, si on lui fournit le programme adéquat, l'implémentation est assez aisée. Mais on peut aussi implémenter le tout avec un circuit spécialisé, non-programmable. Les deux solutions sont possibles, tant que le circuit dispose d'assez de puissance de calcul. Les cartes graphiques anciennes contenaient un ou plusieurs circuits de multiplication de matrices spécialisés dans l'étape de transformation. Chacun de ces circuits prend un sommet et renvoie le sommet transformé. Ils sont composés d'un gros paquet de multiplieurs et d'additionneurs flottants. Pour plus d'efficacité, les cartes graphiques comportent plusieurs de ces circuits, afin de pouvoir traiter plusieurs sommets en même temps.
==L'élimination des surfaces cachées==
Un point important du rendu 3D est que ce que certaines portions de la scène 3D ne sont pas visibles depuis la caméra. Et idéalement, les portions de la scène 3D qui ne sont pas visibles à l'écran ne doivent pas être calculées. A quoi bon calculer des choses qui ne seront pas affichées ? Ce serait gâcher de la puissance de calcul. Et pour cela, de nombreuses optimisations visent à éliminer les calculs inutiles. Elles sont regroupées sous les termes de '''''clipping''''' ou de '''''culling'''''. La différence entre ''culling'' et ''clipping'' n'est pas fixée et la terminologie n'est pas claire. Dans ce qui va suivre, nous n'utiliserons que le terme ''culling''.
Les cartes graphiques modernes embarquent diverses méthodes de ''culling'' pour abandonner les calculs quand elles s’aperçoivent que ceux-ci portent sur une partie non-affichée de l'image. Cela fait des économies de puissance de calcul assez appréciables et un gain en performance assez important. Précisons que le ''culling'' peut être plus ou moins précoce suivant le type de rendu 3D utilisé, mais nous verrons cela dans la suite du chapitre.
===Les différentes formes de ''culling''/''clipping''===
La première forme de ''culling'' est le '''''view frustum culling''''', dont le nom indique qu'il s'agit de l'élimination de tout ce qui est situé en-dehors du ''view frustum''. Ce qui est en-dehors du champ de vision de la caméra n'est pas affiché à l'écran n'est pas calculé ou rendu, dans une certaine mesure. Le ''view frustum culling'' est assez trivial : il suffit d'éliminer ce qui n'est pas dans le ''view frustum'' avec quelques calculs de coordonnées assez simples. Quelques subtilités surviennent quand un triangle est partiellement dans le ''view frustrum'', ce qui arrive parfois si le triangle est sur un bord de l'écran. Mais rien d'insurmontable.
[[File:View frustum culling.svg|centre|vignette|upright=1|''View frustum culling'' : les parties potentiellement visibles sont en vert, celles invisibles en rouge et celles partiellement visibles en bleu.]]
Les autres formes de ''culling'' visent à éliminer ce qui est dans le ''view frustum'', mais qui n'est pas visible depuis la caméra. Pensez à des objets cachés par un autre objet plus proche, par exemple. Ou encore, pensez aux faces à l'arrière d'un objet opaque qui sont cachées par l'avant. Ces deux cas correspondent à deux types de ''culling''. L'élimination des objets masqués par d'autres est appelé l'''occlusion culling''. L'élimination des parties arrières d'un objet est appelé le ''back-face culling''. Dans les deux cas, nous parlerons d''''élimination des surfaces cachées'''.
[[File:Occlusion culling example PL.svg|centre|vignette|''Occlusion culling'' : les objets en bleu sont visibles, ceux en rouge sont masqués par les objets en bleu.]]
Le lancer de rayons n'a pas besoin d'éliminer les surfaces cachées, il ne calcule que les surfaces visibles. Par contre, la rastérisation demande d'éliminer les surfaces cachées. Sans cela, le rendu est incorrect dans le pire des cas, ou alors le rendu calcule des surfaces invisibles pour rien. Il existe de nombreux algorithmes logiciels pour implémenter l'élimination des surfaces cachées, mais la carte graphique peut aussi s'en charger.
L'''occlusion culling'' demande de connaitre la distance à la caméra de chaque triangle. La distance à la caméra est appelée la '''profondeur''' du triangle. Elle est déterminée à l'étape de rastérisation et est calculée à chaque sommet. Lors de la rastérisation, chaque sommet se voit attribuer trois coordonnées : deux coordonnées x et y qui indiquent sa position à l'écran, et une coordonnée de profondeur notée z.
===L'algorithme du peintre===
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les triangles du plus lointain au plus proche. L'idée est que si deux triangles se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Quelque chose qui devrait vous rappeler le rendu 2D, où les sprites sont rendus du plus lointain au plus proche. Il ne s'agit ni plus ni moins que de l''''algorithme du peintre'''.
[[File:Polygons cross.svg|vignette|Polygons cross]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
Un autre problème est que l'algorithme demande de trier les triangles d'une scène 3D selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
===Le tampon de profondeur===
[[File:Z-buffer no text.jpg|vignette|Z-buffer correspondant à un rendu]]
Une autre solution utilise ce qu'on appelle un '''tampon de profondeur''', aussi appelé un ''z-buffer''. Il s'agit d'un tableau, stocké en mémoire vidéo, qui mémorise la coordonnée z de l'objet le plus proche pour chaque pixel. Par défaut, ce tampon de profondeur est initialisé avec la valeur de profondeur maximale, celle du ''far plane'' du ''viewfrustum''. Au fur et à mesure que les objets seront calculés, le tampon de profondeur est mis à jour, conservant ainsi la trace de l'objet le plus proche de la caméra.
Si jamais un triangle a une coordonnée z plus grande que celle du tampon de profondeur, cela veut dire qu'il est situé derrière un objet déjà rendu. Il est éliminé (sauf si transparence il y a) et le tampon de profondeur n'a pas à être mis à jour. Dans le cas contraire, l'objet est plus près de la caméra et sa coordonnée z remplace l'ancienne valeur z dans le tampon de profondeur.
[[File:Z-buffer.svg|centre|vignette|upright=2.0|Illustration du processus de mise à jour du Z-buffer.]]
Il existe des techniques alternatives pour coder la coordonnée de profondeur, qui se distinguent par le fait que la coordonnée z n'est pas proportionnelle à la distance entre le fragment et la caméra. Mais il s'agit là de détails assez mathématiques que je me permets de passer sous silence. Dans la suite de ce cours, nous allons juste parler de profondeur pour regrouper toutes ces techniques, conventionnelles ou alternatives.
Toutes les cartes graphiques modernes utilisent un système de ''z-buffer''. C'est la seule solution pour avoir des performances dignes de ce nom. Il faut cependant noter qu'elles utilisent des tampons de profondeur légèrement modifiés, qui ne mémorisent pas la coordonnée de profondeur, mais une valeur dérivée. Pour simplifier, ils ne mémorisent pas la coordonnée de profondeur z, mais son inverse 1/z. Les raisons à cela ne peuvent pas encore être expliquées à ce moment du cours, aussi nous allons simplement dire que c'est une histoire de correction de perspective.
Les coordonnées z et 1/z sont codées sur quelques bits, allant de 16 bits pour les anciennes cartes graphiques, à 24/32 bits pour les cartes plus récentes. De nos jours, les Z-buffer de 16 bits sont abandonnés et toutes les cartes graphiques utilisent des coordonnées z de 24 à 32 bits. La raison est que les Z-buffer de 16 bits ont une précision insuffisante, ce qui fait que des artefacts peuvent survenir. Si deux objets sont suffisamment proches, le tampon de profondeur n'a pas la précision suffisante pour discriminer les deux objets. Pour lui, les deux objets sont à la même place. Conséquence : il faut bien choisir un des deux objets et ce choix se fait pixel par pixel, ce qui fait des artefacts visuels apparaissent. On parle alors de '''''z-fighting'''''. Voici ce que cela donne :
[[File:Z-fighting.png|centre|vignette|Z-fighting]]
Un défaut du tampon de profondeur est qu'il ne gère pas correctement les objets transparents. Dès que de la transparence est présente dans une scène 3D, le tampon de profondeur ne peut pas être utilisé. Une solution pour cela est de rendre une scène 3D en deux phases : une pour les objets opaques, une avec les objets transparents. La où on rend les objets opaques utilise le tampon de profondeur, mais il est désactivé lors de la seconde.
==La rastérisation et les textures==
Dans cette section, nous allons voir ensemble l'étape de rastérisation et l'étape de traitement des pixels. La rastérisation est difficile à expliquer, surtout que son rôle exact dépend de la technique de rendu utilisée. Pour simplifier, elle projette un rendu en 3D sur un écran en 2D. Une autre explication tout aussi vague est qu'elle s'occupe la traduction des triangles en un affichage pixelisé à l'écran. Elle détermine à quoi ressemble la scène visible sur l'écran. C'est par exemple lors de cette étape que sont appliquées certaines techniques de ''culling'', qui éliminent les portions non-visibles de l'image, ainsi qu'une correction de la perspective et diverses opérations d'interpolation dont nous parlerons dans plusieurs chapitres.
La rastérisation et placage de textures sont deux opérations très liées entre elles. Il existe deux manières principales pour lier les textures à la géométrie : la méthode directe et la méthode inverse (''UV Mapping''). Et les deux font que la rastérisation se fait de manière très différente. Précisons cependant que les rendus les plus simples n'utilisent pas de textures du tout. Ils se contentent de colorier les triangles, voire d'un simple rendu en fil de fer basé sur du tracé de lignes. Dans la suite de cette section, nous allons voir les quatre types de rendu principaux : le rendu en fils de fer, le rendu colorié, et deux rendus utilisant des textures.
===Le rendu en fil de fer===
[[File:Obj lineremoval.png|vignette|Rendu en fil de fer d'un objet 3D.]]
Le '''rendu 3D en fils de fer''' est illustré ci-contre. Il s'agit d'un rendu assez ancien, utilisé au tout début de la 3D, sur des machines qu'on aurait du mal à appeler ordinateurs. Il se contente de tracer des lignes à l'écran, lignes qui connectent deux sommets, qui ne sont autres que les arêtes de la géométrie de la scène rendue. Le tout était suffisant pour réaliser quelques jeux vidéos rudimentaires. Les tout premiers jeux vidéos utilisaient ce rendu, l'un d'entre eux étant Maze War, le tout premier FPS.
{|
|[[File:Maze war.jpg|vignette|Maze war]]
|[[File:Maze representation using wireframes 2022-01-10.gif|centre|vignette|Maze representation using wireframes 2022-01-10]]
|}
Le monde est calculé en 3D, il y a toujours un calcul de la géométrie, la scène est rastérisée normalement, les portions invisbles de l'image sont retirées, mais il n'y a pas d'application de textures après rastérisation. À la place, un algorithme de tracé de ligne trace les lignes à l'écran. Quand un triangle passe l'étape de rastérisation, l'étape de rastérisation fournit la position des trois sommets sur l'écran. En clair, elle fournit les coordonnées de trois pixels, un par sommet. À la suite, un algorithme de tracé de ligne trace trois lignes, une par paire de sommet.
L'implémentation demande juste d'avoir une unité de calcul géométrique, une unité de rastérisation, et un VDC qui supporte le tracé de lignes. Elle est donc assez simple et ne demande pas de circuits de gestion des textures ni de ROP. Le VDC écrit directement dans le ''framebuffer'' les lignes à tracer.
Il a existé des proto-cartes graphiques spécialisées dans ce genre de rendu, comme le '''''Line Drawing System-1''''' de l'entreprise Eans & Sutherland. Nous détaillerons son fonctionnement dans quelques chapitres.
===Le rendu à primitives colorées===
[[File:MiniFighter.png|vignette|upright=1|Exemple de rendu pouvant être obtenu avec des sommets colorés.]]
Une amélioration du rendu précédent utilise des triangles/''quads'' coloriés. Chaque triangle ou ''quad'' est associé à une couleur, et cette couleur est dessinée sur le triangle/''quad''après la rastérisation. Le rendu est une amélioration du rendu en fils de fer. L'idée est que chaque triangle/''quad'' est associé à une couleur, qui est dessinée sur le triangle/''quad'' après la rastérisation. La technique est nommée ''colored vertices'' en anglais, nous parlerons de '''rendu à maillage coloré'''.
[[File:Malla irregular de triángulos modelizando una superficie convexa.png|centre|vignette|upright=2|Maillage coloré.]]
La couleur est propagée lors des calculs géométriques et de la rastérisation, sans subir de modifications. Une fois un rendu en fils de fer effectué, la couleur du triangle est récupérée. Le triangle/''quad'' rendu correspond à un triangle/''quad'' à l'écran. Et l'intérieur de ce triangle/''quad'' est colorié avec la couleur transmise. Pour cela, on utilise encore une fois une fonction du VDC : celle du remplissage de figure géométrique. Nous l’avions vu en parlant des VDC à accélération 2D, mais elle est souvent prise en charge par les ''blitters''. Ils peuvent remplir une figure géométrique avec une couleur unique, on réutilise cette fonction pour colorier le triangle/''quad''. L'étape de rastérisation fournit les coordonnées des sommets de la figure géométrique, le ''blitter'' les utilise pour colorier la figure géométrique.
Niveau matériel, quelques bornes d'arcade ont utilisé ce rendu. La toute première borne d'arcade utilisant le rendu à maillage coloré est celle du jeu I Robot, d'Atari, sorti en 1983. Par la suite, dès 1988, les cartes d'arcades Namco System 21 et les bornes d'arcades Sega Model 1 utilisaient ce genre de rendu. On peut s'en rendre compte en regardant les graphismes des jeux tournant sur ces bornes d'arcade. Des jeux comme Virtua Racing, Virtua Fighter ou Virtua Formula sont assez parlants à ce niveau. Leurs graphismes sont assez anguleux et on voit qu'ils sont basés sur des triangles uniformément colorés.
Pour ceux qui veulent en savoir plus sur la toute première borne d'arcade en rendu à maillage colorée, la borne ''I Robot'' d'Atari, voici une vidéo youtube à ce sujet :
* [https://www.youtube.com/watch?v=6miEkPENsT0 I Robot d'Atari, le pionnier de la 3D Flat.]
===Le placage de textures direct===
Les deux rendus précédents sont très simples, mais n'utilisent pas de textures. Et il est temps de voir les deux rendus qui utilisent des textures. Il y en a deux types, appelés rendu avec placage de texture direct et indirect, nous allons voir le '''rendu par placage de texture direct''' en premier. Et nous l'appellerons ''rendu direct'' dans ce qui suit, pour simplifier les explications. Le rendu direct est une sorte d'intermédiaire entre rendu 2D à base de ''sprite'' et rendu 3D moderne. La géométrie est rendue en 3D pour générer des ''quads'', mais ces ''quads'' ne servent à guider la copie des sprites/textures dans le ''framebuffer''.
Le rendu direct peut utiliser aussi bien des triangles que des ''quads'', mais nous allons partir du principe qu'il utilise des '''''quads''''', à savoir que les objets 3D sont composés de quadrilatères. Lorsqu'un ''quad'' est rastérisé, sa forme à l'écran est un rectangle déformé par la perspective. On obtient un rectangle si le ''quad'' est vu de face, un trapèze si on le voit de biais. Pour résumer, un 'quad' correspond à un quadrilatère sur l'écran. Et le placage de texture est effectué quand on sait quelles sont les coordonnées de ce quadrilatère à l'écran.
Le rendu direct associe à chaque ''quad'', une texture rectangulaire, assez semblable à un ''sprite'' si on regarde de très loin. L'idée est de remplir le quadrilatère à l'écran avec ce ''sprite'', cette texture. La seule subtilité est que la texture/''sprite'' doit être déformé pour rentrer dans le quadrilatère, cette déformation dépendant de la perspective, de l'angle entre le ''quad'' et la caméra, et ainsi de suite. Une fois déformée, le ''sprite''/texture est directement recopié au bon endroit dans le ''framebuffer'', pour remplir le quadrilatère.
Le sprite doit être déformé de deux manières : il doit être agrandi/réduit en fonction de la taille de la figure affichée à l'écran, tourné en fonction de l'orientation du ''quad'', déformé pour gérer la perspective. Pour cela, il faut connaitre les coordonnées de profondeur de chaque bord d'un ''quad'', et de faire quelques calculs. N'importe quel VDC incluant un ''blitter'' avec une gestion du zoom/rotation des sprites peut le faire. Au-delà de cette déformation, il vaut mieux appliquer un filtre d'interpolation pour lisser l'image des ''sprite'' déformés, afin d'obtenir une bonne qualité d'image. Le filtre en question et n'est pas très différent du filtrage de texture qui lisse les textures à l'écran.
[[File:TextureMapping.png|centre|vignette|upright=2|Exemple caricatural de placage de texture sur un ''quad''.]]
Un point important est que les ''quads'' doivent être rendus du plus lointain au plus proche. Sans cela, on obtient rapidement des erreurs de rendu. L'idée est que si deux quads se chevauchent, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. L'écriture du sprite du second quad écrasera les données du premier quad, pour les portions recouvertes, lors de l'écriture du sprite dans le ''framebuffer''. Quelque chose qui devrait vous rappeler le rendu 2D avec l'algorithme du peintre, où les sprites sont rendus du plus lointain au plus proche.
Le rendu inverse utilise très souvent des triangles pour la géométrie, alors que le rendu direct a tendance à utiliser des ''quads'', mais il ne s'agit pas d'une différence stricte. L'usage de triangles/''quads'' peut se faire aussi bien avec un rendu direct comme avec un rendu inverse. Cependant, le rendu en ''quad'' se marie très bien au rendu direct, alors que le rendu en triangle colle mieux au rendu inverse.
L'avantage de cette technique est qu'on parcourt les textures dans un ordre bien précis. Par exemple, on peut parcourir la texture ligne par ligne, l'exploiter par blocs de 4*4 pixels, etc. Et accéder à une texture de manière prédictible se marie bien avec l'usage de mémoires caches, ce qui est un avantage en matière de performances. Mais un même pixel du ''framebuffer'' est écrit plusieurs fois quand plusieurs quads se superposent, alors que le rendu inverse gère la situation avec une seule écriture (sauf si usage de la transparence). De plus, la gestion de la transparence était compliquée et les jeux devaient ruser en utilisation des solutions logicielles assez complexes.
Niveau implémentation matérielle, une carte graphique en rendu direct demande juste trois circuits. Le premier est un circuit de calcul géométrique, qui rend la scène 3D. Le tri des quads est souvent réalisé par le processeur principal, et non pas par un circuit séparé. Toutes les étapes au-delà de l'étape de rastérisation étaient prises en charge par un VDC amélioré, qui écrivait des sprites/textures directement dans le ''framebuffer''.
{|class="wikitable"
|-
! Géométrie
| Processeurs dédiés programmé pour émuler le pipeline graphique
|-
! Tri des quads du plus lointain au plus proche
| Processeur principal (implémentation logicielle)
|-
! Application des textures
| ''Blitter'' amélioré, capable de faire tourner et de zoomer sur des ''sprites''.
|}
L'implémentation était très simple et réutilisait des composants déjà existants : des VDC 2D pour l'application des textures, des processeurs dédiés pour la géométrie. Les unités de calcul de la géométrie étaient généralement implémentées avec un ou plusieurs processeurs dédiés. Vu qu'on savait déjà effectuer le rendu géométrique en logiciel, pas besoin de créer un circuit sur mesure. Il suffisait de dédier un processeur spécialisé rien que pour les calculs géométriques et on lui faisait exécuter un code déjà bien connu à la base. En clair, ils utilisaient un code spécifique pour émuler un circuit fixe. C'était clairement la solution la plus adaptée pour l'époque.
Les unités géométriques étaient des processeurs RISC, normalement utilisés dans l'embarqué ou sur des serveurs. Elles utilisaient parfois des DSP. Pour rappel, les DSP des processeurs de traitement de signal assez communs, pas spécialement dédiés aux rendu 3D, mais spécialisé dans le traitement de signal audio, vidéo et autre. Ils avaient un jeu d'instruction assez proche de celui des cartes graphiques actuelles, et supportaient de nombreuses instructions utiles pour le rendu 3D.
Le rendu direct a été utilisé dans la période de transition entre rendu 2D et rendu 3D, car il était très adapté pour faire cette transition. Coupler un VDC à un processeur pour la géométrie était particulièrement simple à l'époque. Le rendu direct a été utilisé sur des bornes d'arcade dès les années 90. Outre les bornes d'arcade, quelques consoles de 5ème génération utilisaient le rendu direct, avec les mêmes solutions matérielles. La géométrie était calculée sur plusieurs processeurs dédiés. Le reste du pipeline était géré par un VDC 2D qui implémentait le placage de textures. Deux consoles étaient dans ce cas : la 3DO, et la Sega Saturn. Le rendu direct est aujourd'hui abandonné.
===Le placage de textures inverse===
Le rendu précédent, le rendu direct, permet d'appliquer des textures directement dans le ''framebuffer''. Mais comme dit plus haut, il existe une seconde technique pour plaquer des textures, appelé le '''placage de texture inverse''', aussi appelé l'''UV Mapping''. Elle associe une texture complète pour un modèle 3D,contrairement au placage de tecture direct qui associe une texture par ''quad''/triangle. L'idée est que l'on attribue un texel à chaque sommet. Plus précisémment, chaque sommet est associé à des '''coordonnées de texture''', qui précisent quelle texture appliquer, mais aussi où se situe le texel à appliquer dans la texture. Par exemple, la coordonnée de texture peut dire : je veux le pixel qui est à ligne 5, colonne 27 dans cette texture. La correspondance entre texture et géométrie est réalisée lorsque les créateurs de jeu vidéo conçoivent le modèle de l'objet.
[[File:Texture Mapping example.png|centre|vignette|upright=2|Exemple de placage de texture.]]
Dans les faits, on n'utilise pas de coordonnées entières de ce type, mais deux nombres flottants compris entre 0 et 1. La coordonnée 0,0 correspond au texel en bas à gauche, celui de coordonnée 1,1 est tout en haut à droite. L'avantage est que ces coordonnées sont indépendantes de la résolution de la texture, ce qui aura des avantages pour certaines techniques de rendu, comme le ''mip-mapping''. Les deux coordonnées de texture sont notées u,v avec DirectX, ou encore s,t dans le cas général : u est la coordonnée horizontale, v la verticale.
[[File:UVMapping.png|centre|vignette|upright=2|UV Mapping]]
Avec le placage de texture inverse, la rastérisation se fait grosso-modo en trois étapes : la rastérisation proprement dite, le placage de textures, et les opérations finales qui écrivent un pixel dans le ''framebuffer''. Au niveau du matériel, ainsi que dans la plupart des API 3D, les trois étapes sont réalisées par des circuits séparés.
[[File:01 3D-Rasterung-a.svg|vignette|Illustration du principe de la rasterization. La surface correspondant à l'écran est subdivisée en pixels carrés, de coordonnées x et y. La caméra est placée au point e. Pour chaque pixel, on trace une droite qui part de la caméra et qui passe par le pixel considéré. L'intersection entre une surface et cette droite se fait en un point, appartenant à un triangle.]]
Lors de la rasterisation, chaque triangle se voit attribuer un ou plusieurs pixels à l'écran. Pour bien comprendre, imaginez une ligne droite qui part de caméra et qui passe par un pixel sur le plan de l'écran. Cette ligne intersecte 0, 1 ou plusieurs objets dans la scène 3D. Les triangles situés ces intersections entre cette ligne et les objets rencontrés seront associés au pixel correspondant. L'étape de rastérisation prend en entrée un triangle et renvoie la coordonnée x,y du pixel associé.
Il s'agit là d'une simplification, car un triangle tend à occuper plusieurs pixels sur l'écran. L'étape de rastérisation fournit la liste de tous les pixels occupés par un triangle, et les traite un par un. Quand un triangle est rastérisé, le rasteriseur détermine la coordonnée x,y du premier pixel, applique une texture dessus, puis passe au suivant, et rebelote jusqu'à ce que tous les pixels occupés par le triangles aient été traités.
L'implémentation matérielle du placage de texture inverse est beaucoup plus complexe que pour les autres techniques. Pour être franc, nous allons passer le reste du cours à parler de l'implémentation matérielle du placage de texture inverse, ce qui prendra plus d'une dizaine de chapitres.
==La transparence, les fragments et les ROPs==
Dans ce qui suit, nous allons parler uniquement de la rastérisation avec placage de textures inverse. Les autres formes de rastérisation ne seront pas abordées. La raison est que tous les GPUs modernes utilisent cette forme de rastérisation, les exceptions étant rares. De même, ils utilisent un tampon de profondeur, pour l'élimination des surfaces cachées.
La rastérisation effectue donc des calculs géométriques, suivis d'une étape de rastérisation, puis de placage des textures. Ces trois étapes sont réalisées par une unité géométrique, une unité de rastérisation, et un circuit de placage de textures. Du moins sur le principe, car les cartes graphiques modernes ont fortement optimisé l'implémentation et n'ont pas hésité à fusionner certains circuits. Mais nous verrons cela en temps voulu, nous n'allons pas résumer plusieurs décennies d'innovation technologique en quelques paragraphes.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Placage de textures
|}
Mais où mettre le tampon de profondeur ? Intuitivement, on se dit qu'il vaut mieux faire l'élimination des surfaces cachées le plus tôt possible, dès que la coordonnée de profondeur est connue. Et elle est connu à l'étape de rastérisation, une fois les sommets transformés.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Tampon de profondeur
| Placage de textures
|}
En réalité, la profondeur des fragments est gérée par un circuit appelé le '''''Raster Operations Pipeline''''' (ROP), situé à la toute fin du pipeline graphique. Dans ce qui suit, nous utiliserons l'abréviation ROP pour simplifier les explications. Le ROP effectue quelques traitements sur les fragments, avant d'enregistrer l'image finale dans la mémoire vidéo. Il est placé à la fin du pipeline pour gérer correctement la transparence. Et nous allons voir pourquoi la transparence est gérée à la fin du pipeline.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Placage de textures
| ''Raster Operations Pipeline''
|}
===Le mélange ''alpha''===
La transparence se manifeste quand plusieurs objets sont l'un derrière l'autre. Histoire de simplifier les explications, nous allons d'abord voir le cas où un objet semi-transparent est devant un objet opaque. La couleur perçue est alors un mélange de la couleur de l'objet opaque et celle de l'objet semi-transparent. Le mélange dépend d'à quel point l'objet semi-transparent est transparent. Avec un objet parfaitement transparent, seul l'objet opaque est visible. Avec un objet à moitié transparent, la couleur finale sera pour moitié celle de l'objet opaque, pour moitié celle de l'objet semi-transparent. Et c'est pareil pour les cas intermédiaires entre un objet totalement transparent et un objet totalement opaque.
La transparence d'un objet/pixel est définie par un nombre, appelé la '''composante ''alpha'''''. Plus la composante alpha est élevée, plus le pixel est opaque. Elle vaut 0 pour un objet opaque et 1 pour un objet transparent. Elle est ajoutée aux composantes RGB, ce qui fait que tout fragment contient une "couleur de transparence" en plus des couleurs RGB. Elle agit comme un coefficient qui dit comment mélanger la couleur d'un objet transparent et d'un objet opaque. Le calcul de la transparence est une moyenne pondérée par la composante alpha. On parle alors d''''''alpha blending'''''.
: <math>\text{Couleur finale} = \alpha \times \text{Couleur de l'objet transparent} + (1 - \alpha) \times \text{Couleur de l'objet opaque}</math>
[[File:Texture splatting.png|centre|vignette|upright=2.0|Calcul de transparence. La première ligne montre le produit pour l'objet transparent, la seconde ligne est celle de l'objet opaque. La troisième ligne est celle de l'addition finale.]]
Maintenant, qu'en est-il du cas où plusieurs objets sont superposés ? Si vous tracez une demi-droite dont l'origine est la caméra et qui passe par le pixel, il arrive qu'elle intersecte la géométrie en plusieurs points, un point par objet sur la ligne du regarde. Sans transparence, l'objet le plus proche cache tous les autres et c'est donc lui qui décide de la couleur du pixel. Mais avec un objet transparent, la couleur finale est un mélange de la couleur de plusieurs points d'intersection. Il faut donc calculer un pseudo-pixel pour chaque point d'intersection, auquel on donne le nom de '''fragment'''. Un fragment possède une position à l'écran, une coordonnée de profondeur, une couleur, ainsi que quelques autres informations potentiellement utiles.
Les fragments attribués à un même pixel, qui sont à la même position sur l'écran, sont combinés pour obtenir la couleur finale de ce pixel. Il est possible d'utiliser le mélange ''alpha'' pour cela. Il suffit de faire le mélange ''alpha'' entre le fragment qui vient d'être calculé, et le pixel dans le ''framebuffer''. Pour cela, le fragment a une composante ''alpha'', qui est ajouté aux trois couleurs RGB. Le pixel déjà dans le ''framebuffer'' est un résultat temporaire, né du mélange ''alpha'' de tous les fragments précédents.
Un défaut de cette méthode est qu'elle fonctionne assez mal avec un tampon de profondeur. Si le tampon de profondeur est activé, le mélange ''alpha'' ne fonctionne que si les objets sont rendus du plus lointain au plus proche. Et procéder dans cet ordre a un défaut : on dessine des objets dans le ''framebuffer'', pour qu'ensuite les objets devant écrasent ce qui a déjà été dessiné. Un même pixel peut donc être dessiné plusieurs fois, dont une seule sera pertinente. Et ces écritures utilisent de la bande passante mémoire, qui est une ressource précieuse sur un GPU moderne. Il s'agit d'un phénomène appelé '''''overdraw''''', ou sur-dessinage en français. Quelques optimisations permettent d'éliminer l'''overdraw'' en rendant les objets du plus proche au plus lointain, d'autres permettent de dessiner des objets dans un ordre arbitraire, mais nous ne pouvons pas en parler ici.
Beaucoup de moteurs 3D rendent séparément les objets opaques et transparents. Une première passe rend les objets opaques, puis les objets transparents sont rendus dans une seconde passe. Les objets opaques sont rendus dans le désordre, ce qui fait qu'on n'a pas à les trier, alors que les objets transparents doivent être triés selon leur distance. un autre avantage est que le mélange ''alpha'' est désactivé lors de la première passe, alors que c'est la mise à jour du tampon de profondeur qui est désactivé lors de la seconde passe, ce qui augmente un peu les performances dans les deux cas.
===Le test ''alpha''===
Le test ''alpha'' est une technique qui permet d'annuler le rendu d'un fragment en fonction de sa transparence. Si la composante alpha est en-dessous ou au-dessus d'un seuil, le fragment est simplement abandonné. Le seuil en question est configurable, de même que la comparaison utilisée : on peut éliminer le fragment si sa transparence est au-dessus d'un certain seuil, en-dessous, égal, différent, etc.
Il s'agit d'une optimisation qui est utile dans certains scénarios spécifiques. Par exemple, si l'objet a une transparence très élevée, du genre 95%, autant le compter comme complétement transparent, afin d'éviter des opérations de mélange ''alpha''. En effet, les opérations de mélange ''alpha'' sont très lentes, car elles demandent de faire des opérations de lecture-écriture en mémoire vidéo : on lit un pixel dans le ''framebuffer'', on applique le mélange ''alpha'' et on écrit le résultat en mémoire vidéo. L'''alpha test'' permet donc de gagner en performance au prix d'une baisse de la qualité d'image.
Il y a cependant des cas où l'usage du test ''alpha'' est primordial, au-delà d'une question de performances. Un exemple classique est celui du rendu du feuillage dans un jeu 3D. Un feuillage est composé en assemblant plusieurs images de feuilles. Chaque feuille est un carré sur lequel on place une texture de feuille, qui est opaque pour la partie verte des feuilles, transparente pour le reste. Les carrés ne sont cependant pas superposés, mais s'intersectent fortement, ce qui fait que le mélange ''alpha'' ne donne pas de bons résultats. L'usage du test ''alpha'' permet d'obtenir un rendu correct. Pour d'informations via ce lien :
* [https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f Anti-aliased Alpha Test: The Esoteric Alpha To Coverage].
===Les effets de brouillard===
Les '''effets de brouillard''' sont nécessaires dans certains jeux vidéo pour l'ambiance (pensez à des jeux d'horreur comme Silent Hill), mais ils ont surtout été utilisés pour économiser des calculs. L'idée est de ne pas calculer les graphismes au-delà d'une certaine distance, sans que cela se voie. Le ''view frustum'' utilise alors un plan limite, au-delà duquel on ne voit pas les objets. Mais ce plan limite donne une cassure inesthétique dans le rendu. Pour masquer cette cassure, les programmeurs ajoutaient un effet de brouillard. Les objets au-delà du plan limite étaient totalement dans le brouillard, puis ce brouillard se réduisait progressivement en se rapprochant de la caméra, avant de s'annuler à partir d'une certaine distance.
Pour calculer le brouillard, on effectue un mélange ''alpha'' entre la couleur du pixel et une ''couleur de brouillard''. La différence est que l'on n'utilise pas la transparence pour faire le mélange, mais un '''coefficient de brouillard''', noté <math>\text{fog}(z)</math>.
: <math>\text{Couleur finale} = \text{fog}(z) \times \text{Couleur de brouillard} + [ 1 - \text{fog}(z) ] \times \text{Couleur du pixel}</math>
Le coefficient de brouillard dépend de la coordonnée de profondeur, de la distance du pixel par rapport à la caméra. Le brouillard démarre à une distance <math>z_{fog-start}</math>, et masque totalement les objets à partir d'une distance <math>z_{fog-end}</math>. Entre les deux, le coefficient de brouillard dépend de la distance. OpenGL autorise trois formules de calcul suivantes :
: <math>\text{fog}(z) = \frac{z_{fog-end} - z}{z_{fog-end} - z_{fog-start}}</math>
: <math>\text{fog}(z) = e^{- k \times z}</math>
: <math>\text{fog}(z) = e^{- (k \times z)^2}</math>
==L'éclairage d'une scène 3D==
L'éclairage d'une scène 3D calcule les ombres, mais aussi la luminosité de chaque pixel, ainsi que bien d'autres effets graphiques. Les algorithmes d'éclairage ont longtemps été implémentés directement en matériel, les cartes graphiques géraient l'éclairage dans des circuits spécialisés. Aussi, il est important de voir ces algorithmes d'éclairage. Il est possible d'implémenter l'éclairage à deux endroits différents du pipeline : juste avant la rastérisation, et après la rastérisation.
===Les sources de lumière et les couleurs associées===
L'éclairage d'une scène 3D provient de sources de lumières, comme des lampes, des torches, le soleil, etc. Il existe de nombreux types de sources de lumière, et nous n'allons parler que des principales. Elles sont au nombre de quatre et elles sont illustrées ci-dessous.
[[File:3udUJ.gif|centre|vignette|upright=2|Types de sources de lumière.]]
[[File:Graphics lightmodel directional.png|vignette|upright=1.0|Source de lumière directionnelle.]]
Les '''sources directionnelles''' servent à modéliser des sources de lumière très éloignées, comme le soleil ou la lune. Elles sont simplement définies par un vecteur qui indique la direction de la lumière, rien de plus.
Les '''sources ponctuelles''' sont des points, qui émettent de la lumière dans toutes les directions. Elles sont définies par une position, et une intensité lumineuse, éventuellement la couleur de la lumière émise. Il existe deyux types de sources de lumière ponctuelles.
* Le premières émettent de manière égale dans toutes les directions. Elles sont appelées des ''point light'' dans le schéma du dessus.
* Les secondes émettent de la lumière dans une '''direction privilégiée'''. L'exemple le plus parlant est celui d'une lampe-torche : elle émet de la lumière "tout droit", dans la direction où la lampe est orientée. Elles sont appelées des ''sport light'' dans le schéma du dessus. La direction privilégiée est un vecteur, notée v dans le schéma du dessous.
[[File:Graphics lightmodel ambient.png|vignette|upright=1.0|Lumière ambiante.]]
En théorie, la lumière rebondit sur les surfaces et a tendance à se disperser un peu partout à force de rebondir. C'est ce qui explique qu'on arrive à voir à l'intérieur d'une pièce si une fenêtre est ouverte. Il en résulte un certain '''éclairage ambiant''', qui est assez difficile à représenter dans un moteur de rendu 3D. Auparavant, l'éclairage ambiant était simulé par une lumière égale en tout point de la scène 3D, appelée simplement la '''lumière ambiante'''. Précisément, on suppose que la lumière ambiante en un point vient de toutes les directions et a une intensité constante, identique dans toutes les directions. Le tout est illustré ci-contre. C'est assez irréaliste, mais ça donne une bonne approximation de la lumière ambiante.
===La lumière incidente : le terme géométrique===
Pour simplifier, nous allons supposer que l'éclairage est calculé pour chaque sommet, pas par triangle. C'est de loin le cas le plus courant, aussi ce n'est pas une simplification abusive. La lumière qui arrive sur un sommet est appelée la '''lumière incidente'''.
La couleur d'un sommet dépend de deux choses : la lumière incidente directe, comment il réfléchit cette lumière. Mathématiquement, il est possible de résumer cela avec le produit de deux termes : l'intensité de la lumière incidente, une fonction qui indique comment la surface réfléchit la lumière incidente. La fonction en question est appelée la '''réflectivité bidirectionnelle'''. Le terme anglais est ''bidirectional reflectance distribution function'', abrévié en BRDF, et nous utiliserons cette abréviation dans ce qui suit.
: <math>\text{Couleur finale} = \text{Lumière incidente} \times BRDF(...)</math>
La lumière incidente vient soit directement des sources de lumière, soit de la lumière qui a rebondit sur d'autres objets proches. La première est appelée la lumière directe, celle qui vient des rebonds s'appelle la lumière indirecte. Pour simplifier, la lumière indirecte est gérée par la lumière ambiante, nous passons sous silence les techniques d'illumination globale. En clair : nous allons nous limiter au cas où la lumière incidente vient directement d'une source de lumière, pas d'un rebond.
Intuitivement, la lumière incidente est simplement égale à l'intensité de la source de lumière. Sauf que ce n'est qu'une approximation, et une assez mauvaise. En réalité, l'approximation est bonne si la lumière arrive proche de la verticale, mais elle est d'autant plus mauvaise que la lumière arrive penchée, voire rasante.
La raison : la lumière incidente sera étalée sur une surface plus grande, si elle arrive penchée. Si vous vous souvenez de vos cours de collège, c'est le même principe qui explique les saisons. La lumière du soleil est proche de la verticale en été, mais est de plus en plus penché quand on s'avance vers l'Hiver. La lumière solaire est donc étalée sur une surface plus grande, ce qui fait qu'un point de la surface recevra moins de lumière, celle-ci étant diluée, étalée.
[[File:Radiación solar.png|centre|vignette|upright=2|Exemple avec la lumière solaire.]]
[[File:Angle of incidence.svg|vignette|upright=1|Angle d'incidence.]]
En clair, tout dépend de l''''angle d'incidence''' de la lumière. Reste à voir comment calculer cet angle. La lumière incidente est définie par un vecteur, qui part de la source de lumière et atterrit sur le sommet considéré. Imaginez simplement que ce vecteur suit un rayon lumineux provenant de la source de lumière. Le vecteur pour la lumière incidente sera noté L. L'angle d'incidence est l'angle que fait ce vecteur avec la verticale de la surface, au niveau du sommet considéré.
[[File:Graphics lightmodel ptsource.png|vignette|Normale de la surface.]]
Pour cela, les calculs d'éclairage ont besoin de connaitre la verticale d'un sommet. Un sommet est donc associé à un vecteur, appelé la '''normale''', qui indique la verticale en ce point. Deux sommets différents peuvent avoir deux normales différentes, même s'ils sont proches. Elles sont d'autant plus différentes que la surface est rugueuse, non-lisse. La normale est prédéterminée lors de la création du modèle 3D, il n'y a pas besoin de le calculer. Par contre, elle est modifiée lors de l'étape de transformation, quand on place le modèle 3D dans la scène 3D. Les deux autres vecteurs sont à calculer à chaque image, car ils changent quand on bouge le sommet.
La lumière qui arrive sur la surface dépend de l'angle entre la normale et le vecteur L. Précisément, elle dépend du cosinus de cet angle. En multipliant ce cosinus avec l'intensité de la lumière, on a la lumière arrivante. La couleur finale d'un pixel est donc :
: <math>\text{Couleur finale} = I \times \cos{(N, L)} \times BRDF(...)</math>
Le terme <math>I \times \cos{N, L}</math> ne dépend pas de la surface considérée. Juste de la position de la source de lumière, de la position du sommet et de son orientation par rapport à la lumière. Aussi, il est parfois appelé le '''terme géométrique''', en opposition aux propriétés de la surface. Les propriétés de la surface sont définies par un '''''material''''', qui indique comment il réfléchit la lumière, ainsi que sa texture.
===Le produit scalaire de deux vecteurs===
Calculer le terme géométrique demande de calculer le cosinus d'un angle. Et il n'est pas le seul : les autres calculs d'éclairage que nous allons voir demandent de calculer des cosinus. Or, les calculs trigonométriques sont très gourmands pour le GPU. Pour éviter le calcul d'un cosinus, les GPU utilisent une opération mathématique appelée le ''produit scalaire''. Le produit scalaire agit sur deux vecteurs, que l'on notera A et B. Un produit scalaire prend : la longueur des deux vecteurs, et l'angle entre les deux vecteurs noté <math>\omega</math>. Le produit scalaire est équivalent à la formule suivante :
: <math>\text{Produit scalaire de deux vecteurs A et B} = \vec{A} \cdot \vec{B} = A \times B \times \cos{(\omega)}</math>, avec A et B la longueur des deux vecteurs A et B.
L'avantage est que le produit scalaire se calcule simplement avec des additions, soustractions et multiplications, des opérations que les cartes graphiques savent faire très facilement. Le produit scalaire de deux vecteurs de coordonnées x,y,z est le suivant :
: <math>\vec{A} \cdot \vec{B} = x_A \times x_B + y_A \times y_B + z_A \times z_B</math>
En clair, on multiplie les coordonnées identiques, et on additionne les résultats. Rien de compliqué.
Un avantage est que tous les vecteurs vus précédemment sont normalisés, à savoir qu'ils ont une longueur qui vaut 1. Ainsi, le calcul du produit scalaire devient équivalent au calcul du produit scalaire.
===La réflexion de la lumière sur la surface===
[[File:Ray Diagram 2.svg|vignette|Reflection de la lumière sur une surface parfaitement lisse.]]
Maintenant que nous venons de voir le terme géométrique, voyons le BRDF, qui définit comment la surface de l'objet 3D réfléchit la lumière. Vos cours de collège vous ont sans doute appris que la lumière est réfléchie avec le même angle d'arrivée. L'angle d'incidence et l'angle de réflexion sont égaux, comme illustré ci-contre. On parle alors de '''réflexion parfaite'''.
Mais cela ne vaut que pour une surface parfaitement lisse, comme un miroir parfait. Dans la réalité, une surface a tendance à renvoyer des rayons dans toutes les directions. La raison est qu'une surface réelle est rugueuse, avec de petites aspérités et des micro-reliefs, qui renvoient la lumière dans des directions "aléatoires". La lumière « rebondit » sur la surface de l'objet et une partie s'éparpille dans un peu toutes les directions. On parle alors de '''réflexion diffuse'''.
{|
|-
|[[File:Dioptre reflexion diffuse speculaire refraction.svg|vignette|upright=1.4|Différence entre réflexion diffuse et spéculaire.]]
|[[File:Diffuse reflection.svg|vignette|upright=1|Réflexion diffuse.]]
|}
Maintenant, imaginons que la surface n'ait qu'une réflexion diffuse, pas d'autres formes de réflexion. Et imaginons aussi que cette réflexion diffuse soit parfaite, à savoir que la lumière réfléchie soit renvoyée à l'identique dans toutes les directions, sans aucune direction privilégiée. On a alors le ''material'' le plus simple qui soit, appelé un '''''diffuse material'''''.
Vu que la lumière est réfléchie à l'identique dans toutes les directions, elle sera identique peu importe où on place la caméra. La lumière finale ne dépend donc que des propriété de la surface, que de sa couleur. En clair, il suffit de donner une '''couleur diffuse''' à chaque sommet. La couleur diffuse est simplement multipliée par le terme géométrique, pour obtenir la lumière réfléchie finale. Rien de plus, rien de moins. Cela donne l'équation suivante, avec les termes suivants :
* L est le vecteur pour la lumière incidente ;
* N est la normale du sommet ;
* I est l'intensité de la source de lumière ;
* <math>C_d</math> est la couleur diffuse.
: <math>\text{Illumination diffuse} = C_d \times \left[ I \times (\vec{N} \cdot \vec{L}) \right]</math>
Rajoutons maintenant l'effet de la lumière ambiante à un ''material'' de ce genre. Pour rappel, la lumière ambiante vient de toutes les directions à part égale, ce qui fait que son angle d'incidence n'a donc pas d'effet. L'intensité de la lumière ambiante est déterminée lors de la création de la scène 3D, c'est une constante qui n'a pas à être calculée. Pour obtenir l'effet de la lumière ambiante sur un objet, il suffit de multiplier sa couleur diffuse par l'intensité de la lumière ambiante. Cependant, de nombreux moteurs de jeux ajoutent une '''couleur ambiante''', différente de la couleur diffuse.
: <math>\text{Illumination ambiante} = C_a \times I_a</math> avec <math>C_a</math> la couleur ambiante du point de surface et <math>I_a</math> l'intensité de la lumière ambiante.
En plus de la réflexion diffuse parfaite, de nombreux matériaux ajoutent une '''réflexion spéculaire''', qui n'est pas exactement la réflexion parfaite, en est très proche. Les rayons réfléchis sont très proches de la direction de réflexion parfaite, et s'atténuent très vite en s'en éloignant. Le résultat ressemble à une sorte de petit "point blanc", très lumineux, orienté vers la source de lumière, appelé le '''''specular highlight'''''. La réflexion diffuse est prédominante pour les matériaux rugueux, alors que la réflexion spéculaire est dominante sur les matériaux métalliques ou très lisses.
[[File:Phong components version 4.png|centre|vignette|upright=3.0|Couleurs utilisées dans l'algorithme de Phong.]]
[[File:Phong Vectors.svg|vignette|Vecteurs utilisés dans l'algorithme de Phong (et dans le calcul de l'éclairage, de manière générale).]]
Pour calculer la réflexion spéculaire, il faut d'abord connaitre le vecteur pour la réflexion parfaite, que nous noterons R dans ce qui suit. Le vecteur R peut se calculer avec la formule ci-dessous :
: <math>\vec{R} = 2 (\vec{L} \cdot \vec{N}) \times \vec{N} - \vec{L} </math>
La réflexion spéculaire dépend de l'angle entre la direction du regard et la normale : plus celui-ci est proche de l'angle de réflexion parfaite, plus la réflexion spéculaire sera intense. Le vecteur pour la direction du regard sera noté V, pour vue ou vision. La réflexion spéculaire est une fonction qui dépend de l'angle entre les vecteurs R et V. Le calcul de la réflexion spéculaire utilise une '''couleur spéculaire''', qui est l'équivalent de la couleur diffuse pour la réflexion spéculaire.
: <math>\text{BRDF spéculaire} = C_s \times f(\vec{R} \cdot \vec{V}) </math>
La fonction varie grandement d'un modèle de calcul spéculaire à l'autre. Aussi, je ne rentre pas dans le détail. L'essentiel est que vous compreniez que le calcul de l'éclairage utilise de nombreux calculs géométriques, réalisés avec des produits scalaires. Les calculs géométriques utilisent la couleur d'un sommet, la normale du sommet, et le vecteur de la lumière incidente. Les autres informations sont calculées à l'exécution.
===Les algorithmes d'éclairage basiques : par triangle, par sommet et par pixel===
Dans tout ce qui a été dit précédemment, l'éclairage est calculé pour chaque sommet, ce qui fait qu'on parle d''''éclairage par sommet''', ou ''vertex lighting''. Il attribue une illumination/couleur à chaque sommet de la scène 3D. Mais une fois qu'on a obtenu la couleur des sommets, reste à colorier les triangles.
Pour obtenir la couleur finale d'un pixel, l'éclairage par sommet fait une moyenne de la couleur de chaque sommet. La moyenne est une moyenne pondérée, qui tient compte de la distance du sommet avec le pixel. Plus le pixel est loin d'un sommet, plus son coefficient est petit. Typiquement, le coefficient varie entre 0 et 1 : de 1 si le pixel est sur le sommet, à 0 si le pixel est sur un des sommets adjacents. La moyenne effectuée est généralement une interpolation bilinéaire, qui est prise en charge par l'étape de rastérisation.
L'éclairage par sommet a eu son heure de gloire, mais il est maintenant remplacé par l''''éclairage par pixel''' (''per-pixel lighting''), qui calcule l'éclairage pixel par pixel. En clair, l’éclairage est finalisé après l'étape de rastérisation, il ne se fait pas qu'au niveau de la géométrie. Il existe plusieurs types d'éclairage par pixel, mais on peut les classer en deux grands types : l'éclairage de Phong et le ''bump/normal mapping''.
L''''éclairage de Phong''' calcule l'éclairage pixel par pixel. Avec cet algorithme, la géométrie n'est pas éclairée : les couleurs des sommets ne sont pas calculées. À la place, les normales sont envoyées à l'étape de rastérisation, qui effectue une opération d'interpolation, qui renvoie une normale pour chaque pixel. Les calculs d'éclairage utilisent alors ces normales pour faire les calculs d'éclairage pour chaque pixel.
{|
|-
|[[File:Per vertex lighting.png|vignette|upright=1|Eclairage par sommet.]]
|[[File:Per fragment lighting.png|vignette|upright=1|Eclairage de Phong.]]
|-
|[[File:Per vertex lighting example.png|vignette|upright=1|Eclairage par sommet.]]
|[[File:Per fragment lighting example.png|vignette|upright=1|Eclairage de Phong.]]
|}
La technique du '''''normal mapping''''' est assez simple à expliquer, sans compter que plusieurs cartes graphiques l'ont implémentée directement dans leurs circuits. Là où l'éclairage de Phong interpole les normales pour chaque pixel, le ''normal-mapping'' précalcule les normales d'une surface dans une texture, appelée la ''normal-map''. Lors du rendu 3D, la carte graphique lit les normales adéquates directement depuis cette texture, puis fait les calculs d'éclairage avec. Le ''normal mapping'' permettent d'ajouter du relief et des détails sur des surfaces planes en jouant sur l'éclairage. Il permet de simplifier grandement la géométrie rendue, tout en utilisant l'éclairage pour compenser.
[[File:WallSimpleAndNormalMapping.png|centre|vignette|upright=2|Différence sans et avec ''normal-mapping''.]]
===Les ''shaders'' : des programmes exécutés sur le GPU===
Maintenant que nous venons de voir les algorithmes d'éclairages, il est temps de voir comment les réaliser sur une carte graphique. Nous venons de voir qu'il y a une différence entre l'éclairage par pixel et par sommet. Intuitivement, l'éclairage par sommet devrait se faire avec les calculs géométriques, alors que l'éclairage par pixel devrait se faire après avoir appliqué les textures.
Les toutes premières cartes graphiques ne géraient ni l'éclairage par sommet, ni l'éclairage par pixel. Elles laissaient les calculs géométriques au CPU. Par la suite, la Geforce 256 a intégré '''circuit de ''Transform & Lightning''''', qui s'occupait de tous les calculs géométriques, éclairage par sommet inclus (d'où le L de T&L). Elle gérait alors l'éclairage par sommet, mais un algorithme particulier, qui n'était pas très flexible. Il ne gérait que des ''material'' bien précis (des ''Phong materials''), rien de plus.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Unité de T&L : géométrie
| Rastérisation
| Placage de textures
| ''Raster Operations Pipeline''
|}
L'amélioration suivante est venue sur la Geforce 3 : l'unité de T&L est devenue programmable. Au vu le grand nombre d'algorithmes d'éclairages possibles et le grand nombre de ''materials'' possibles, c'était la seule voie possibles. Les programmeurs pouvaient programmer leurs propres algorithmes d'éclairage par sommet, même s'ils devaient aussi programmer les étapes de transformation et de projection. Mais nous détaillerons cela dans un chapitre dédié sur l'historique des GPUs.
Ce qui est important est que la Geforce 3 a introduit une fonctionnalité absolument cruciale pour le rendu 3D moderne : les '''''shaders'''''. Il s'agit de programmes informatiques exécutés par la carte graphique, qui servaient initialement à coder des algorithmes d'éclairage. D'où leur nom : ''shader'' pour ''shading'' (éclairage en anglais). Cependant, l'usage modernes des ''shaders'' dépasse le cadre des algorithmes d'éclairage.
L'avantage est que cela simplifie grandement l'implémentation des algorithmes d'éclairage. Pas besoin de les intégrer dans la carte graphique pour les utiliser, pas besoin d'un circuit distinct pour chaque algorithme. Sans ''shaders'', si la carte graphique ne gère pas un algorithme d'éclairage, on ne peut pas l'utiliser. À la rigueur, il est parfois possible de l'émuler avec des contournements logiciels, mais au prix de performances souvent désastreuses. Avec des ''shaders'', il est possible de programmer l'algorithme d'éclairage de notre choix, pour l'exécuter sur la carte graphique, avec des performances plus que convenables.
[[File:Implémentation de l'éclairage sur les cartes graphiques.png|vignette|Implémentation de l'éclairage sur les cartes graphiques]]
Il existe plusieurs types de ''shaders'', mais les deux principaux sont les '''''vertex shaders''''' et les '''''pixel shaders'''''. Les pixels ''shaders'' s'occupent de l'éclairage par pixel, leur nom est assez parlent. Les vertex ''shaders'' s'occupent de l'éclairage par sommet, mais aussi des étapes de transformation/projection. Je parle bien des trois étapes de transformation vues plus haut, qui effectuent des calculs de transformation de coordonnées avec des matrices. La raison à cela est que les calculs de transformation ressemblent beaucoup aux calculs d'éclairage par sommet. Ils impliquent tous deux des calculs vectoriels, comme des produits scalaires et des produits vectoriels, qui agissent sur des sommets/triangles. Si la carte graphique incorpore un processeur de ''shader'' capable de faire de tels calculs, alors il peut servir pour les deux.
Pour implémenter les ''shaders'', il a fallu ajouter des processeurs à la carte graphique. Les processeurs en question exécutent les ''shaders'', ils peuvent lire ou écrire dans des textures, mais ne font rien d'autres. Les ''vertex shaders'' font tout ce qui a trait à la géométrie, ils remplacent l'unité de T&L. Les pixels ''shaders'' sont entre la rastérisation et les ROPs, ils sont très liés à l'unité de texture.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| rowspan="2" class="f_rouge" | ''Vertex shader''
| rowspan="2" | Rastérisation
| Placage de textures
| rowspan="2" |''Raster Operations Pipeline''
|-
| class="f_rouge" | ''Pixel shader''
|}
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=Les cartes d'affichage des anciens PC
| prevText=Les cartes d'affichage des anciens PC
| next=Avant les GPUs : les cartes accélératrices 3D
| nextText=Avant les GPUs : les cartes accélératrices 3D
}}{{autocat}}
</noinclude>
s3n8hmu4t8x9f2kj4hdom2smn1n9564
774459
774458
2026-10-08T14:01:35Z
Mewtow
31375
/* Le mélange alpha */
774459
wikitext
text/x-wiki
Le premier jeu à utiliser de la "vraie 3D" texturée fut le jeu Quake, premier du nom. Et depuis sa sortie, la grande majorité des jeux vidéo utilisent de la 3D, même s'il existe encore quelques jeux en 2D. Face à la prolifération des jeux vidéo en 3D, les fabricants de cartes graphiques ont inventé les cartes accélératrices 3D, des cartes vidéo capables d'accélérer le rendu en 3D. Dans ce chapitre, nous allons voir comment elles fonctionnent et comment elles ont évolué dans le temps. Pour comprendre comment celles-ci fonctionnent, il faut faire quelques rapides rappels sur les bases du rendu 3D.
==Les bases du rendu 3D==
[[File:Rectangular parallelepiped labeled el.svg|vignette|Parallélépipède.]]
Une '''scène 3D''' est composée d'un espace en trois dimensions, dans laquelle le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple parallélépipède. Un des coins de ce parallélogramme sert d’origine à un système de coordonnées : il est à la position (0, 0, 0), et les axes partent de ce point en suivant les arêtes. Les objets seront placés à des coordonnées bien précises dans ce parallélogramme.
===Les objets 3D et leur géométrie===
<noinclude>[[File:Dolphin triangle mesh.png|vignette|Illustration d'un dauphin, représenté avec des triangles.]]</noinclude>
Dans la quasi-totalité des jeux vidéo actuels, les objets et la scène 3D sont modélisés par un assemblage de triangles collés les uns aux autres, ce qui porte le nom de '''maillage''', (''mesh'' en anglais). Il a été tenté dans le passé d'utiliser des quadrilatères (rendu dit en ''quad'') ou d'autres polygones, mais les contraintes techniques ont fait que ces solutions n'ont pas été retenues.
[[File:CG WIKI.jpg|centre|vignette|upright=2|Exemple de modèle 3D.]]
Les modèles 3D sont définis par leurs sommets, aussi appelés '''vertices''' dans le domaine du rendu 3D. Chaque sommet possède trois coordonnées, qui indiquent sa position dans la scène 3D : abscisse, ordonnée, profondeur. Les sommets sont regroupés en triangles, qui sont formés en combinant trois sommets entre eux. Les anciennes cartes graphiques géraient aussi d'autres formes géométriques, comme des points, des lignes, ou des quadrilatères. Les quadrilatères étaient appelés des ''quads'', et ce terme reviendra occasionnellement dans ce cours. De telles formes basiques, gérées nativement, sont appelées des '''primitives'''.
La représentation exacte d'un objet est donc une liste plus ou moins structurée de sommets. La liste doit préciser les coordonnées de chaque sommet, ainsi que comment les relier pour former des triangles. Pour cela, l'objet est représenté par une structure qui contient la liste des sommets, mais aussi de quoi savoir quels sont les sommets reliés entre eux par un segment. Nous en dirons plus dans le chapitre sur le rendu de la géométrie.
===La caméra : le point de vue depuis l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par :
* une position ;
* par la direction du regard (un vecteur).
À la caméra, il faut ajouter tout ce qui permet de déterminer le '''champ de vision'''. Le champ de vision contient tout ce qui est visible à l'écran. Et sa forme dépend de la perspective utilisée. Dans le cas le plus courant dans les jeux vidéos en 3D, il correspond à une '''pyramide de vision''' dont la pointe est la caméra, et dont les faces sont délimitées par les bords de l'écran. À l'intérieur de la pyramide, il y a un rectangle qui représente l'écran du joueur, appelé le '''''viewport'''''.
<noinclude>[[File:ViewFrustum.jpg|centre|vignette|upright=2|Caméra.]]</noinclude>
[[File:ViewFrustum.svg|vignette|upright=1|Volume délimité par la caméra (''view frustum'').]]
La majorité des jeux vidéos ajoutent deux plans :
* un ''near plane'' en-deça duquel les objets ne sont pas affichés. Il élimine du champ de vision les objets trop proches.
* Un ''far plane'', un '''plan limite''' au-delà duquel on ne voit plus les objets. Il élimine les objets trop lointains.
Avec ces deux plans, le champ de vision de la caméra est donc un volume en forme de pyramide tronquée, appelé le '''''view frustum'''''. Le tout est parfois appelée, bien que par abus de langage, la pyramide de vision. Avec d'autres perspectives moins utilisées, le ''view frustum'' est un pavé, mais nous n'en parlerons pas plus dans le cadre de ce cours car elles ne sont presque pas utilisés dans les jeux vidéos actuels.
===Les textures===
Tout objet à rendre en 3D est donc composé d'un assemblage de triangles, et ceux-ci sont éclairés et coloriés par divers algorithmes. Pour rajouter de la couleur, les objets sont recouverts par des '''textures''', des images qui servent de papier peint à un objet. Un objet géométrique est donc recouvert par une ou plusieurs textures qui permettent de le colorier ou de lui appliquer du relief.
[[File:Texture+Mapping.jpg|centre|vignette|upright=2|Texture Mapping]]
Notons que les textures sont des images comme les autres, codées pixel par pixel. Pour faire la différence entre les pixels de l'écran et les pixels d'une texture, on appelle ces derniers des '''texels'''. Ce terme est assez important, aussi profitez-en pour le mémoriser, nous le réutiliserons dans quelques chapitres.
Un autre point lié au fait que les textures sont des images est leur compression, leur format. N'allez pas croire que les textures sont stockées dans un fichier .jpg, .png ou tout autre format de ce genre. Les textures utilisent des formats spécialisés, comme le DXTC1, le S3TC ou d'autres, plus adaptés à leur rôle de texture. Mais qu'il s'agisse d'images normales (.jpg, .png ou autres) ou de textures, toutes sont compressées. Les textures sont compressées pour prendre moins de mémoire. Songez que la compression de texture est terriblement efficace, souvent capable de diviser par 6 la mémoire occupée par une texture. S'en est au point où les textures restent compressées sur le disque dur, mais aussi dans la mémoire vidéo ! Nous en reparlerons dans le chapitre sur la mémoire d'une carte graphique.
Plaquer une texture sur un objet peut se faire de deux manières, qui portent les noms de placage de texture inverse et direct. Le placage de texture direct a été utilisé au tout début de la 3D, sur des bornes d'arcade et les consoles de jeu 3DO, PS1, Sega Saturn. De nos jours, on utilise uniquement la technique de placage de texture inverse. Les deux seront décrites dans le détail plus bas.
===La différence entre rastérisation et lancer de rayons===
Les techniques de rendu 3D sont nombreuses, mais on peut les classer en deux grands types : le ''lancer de rayons'' et la ''rasterization''. Sans décrire les deux techniques, sachez cependant que le lancer de rayon n'est pas beaucoup utilisé pour les jeux vidéo. Il est surtout utilisé dans la production de films d'animation, d'effets spéciaux, ou d'autres rendu spéciaux. Dans les jeux vidéos, il est surtout utilisé pour quelques effets graphiques, la rasterization restant le mode de rendu principal.
La raison principale est que le lancer de rayons demande beaucoup de puissance de calcul. Une autre raison est que créer des cartes accélératrices pour le lancer de rayons n'est pas simple. Il a existé des cartes accélératrices permettant d'accélérer le rendu en lancer de rayons, mais elles sont restées confidentielles. Les cartes graphiques modernes incorporent quelques circuits pour accélérer le lancer de rayons, mais ils restent d'un usage marginal et servent de compléments au rendu par rastérization. Un chapitre entier sera dédié aux cartes accélératrices de lancer de rayons et nous verrons pourquoi le lancer de rayons est difficile à implémenter avec des performances convenables, ce qui explique que les jeux vidéo utilisent la ''rasterization''.
La rastérisation est structurée autour de trois étapes principales :
* Une étape purement logicielle, effectuée par le processeur, où le moteur physique calcule la géométrie de la scène 3D.
* Une étape de '''traitement de la géométrie''', qui gère tout ce qui a trait aux sommets et triangles.
* Une étape de '''rastérisation''' qui détermine sur quels pixels de l'écran est affiché le triangle.
* Une étape de '''traitement des pixels''', qui colorie les pixels et gère les textures.
Il existe plusieurs rendus différents et la rastérisation ne se fait pas de la même manière selon le rendu utilisé. Il existe des rendus sans textures, d'autres avec, d'autres avec éclairage, d'autres sans, etc. Par contre, l'étape de calcul de la géométrie est la même quel que soit le rendu ! Mieux : le calcul de la géométrie se fait de la même manière entre rastérisation et lancer de rayons, il est le même quelle que soit la technique de rendu 3D utilisée.
Les trois étapes précédentes sont réalisées dans des circuits ou processeurs séparés, comme on le verra plus tard. Et cela permet d'utiliser la technique dite du '''pipeline'''. Concrètement, supposons que la carte graphique traite les données par paquets de triangles (en réalité, c'est des paquets de sommets, mais passons). L'étape de traitement de la géométrie peut travailler sur un paquet de triangle, pendant que le paquet précédent est dans l'étape de rastérisation, et que le paquet encore précédent est en train de traiter ses pixels. Cela permet de traiter trois paquets de triangles en même temps, mais à des états d'avancements différents.
Mieux que cela : le traitement de la géométrie est lui-même composé d'une succession de sous-étapes, la rasterisation est elle-même découpée en plusieurs sous-étapes, et ainsi de suite. Le nombre d'étapes pour une carte graphique moderne dépasse la dizaine. La rastérisation calcule un rendu 3D avec une suite d'étapes consécutives qui doivent s'enchainer dans un ordre bien précis. L'ensemble de ces étapes est appelé le '''pipeline graphique''', qui sera détaillé dans ce qui suit.
==Le calcul de la géométrie==
Le calcul de la géométrie regroupe plusieurs manipulations différentes. La principale demande juste de placer les modèles 3D dans la scène, de placer les objets dans le monde. Puis, il faut centrer la scène 3D sur la caméra. Les deux changements ont pour point commun de demander des changements de repères. Par changement de repères, on veut dire que l'on passe d'un système de coordonnées à un autre. En tout, il existe trois changements de repères distincts qui sont regroupés dans l''''étape de transformation''' : un premier qui place chaque objet 3D dans la scène 3D, un autre qui centre la scène du point de vue de la caméra, et un autre qui corrige la perspective.
===Les trois étapes de transformation===
La première étape place les objets 3D dans la scène 3D. Un modèle 3D est représentée par un ensemble de sommets, qui sont reliés pour former sa surface. Les données du modèle 3D indiquent, pour chaque sommet, sa position par rapport au centre de l'objet qui a les coordonnées (0, 0, 0). La première étape place l'objet 3D à une position dans la scène 3D, déterminée par le moteur physique, qui a des coordonnées (X, Y, Z). Une fois placé dans la scène 3D, le centre de l'objet passe donc des coordonnées (0, 0, 0) aux coordonnées (X, Y, Z) et tous les sommets de l'objet doivent être mis à jour.
De plus, l'objet a une certaine orientation : il faut aussi le faire tourner. Enfin, l'objet peut aussi subir une mise à l'échelle : on peut le gonfler ou le faire rapetisser, du moment que cela ne modifie pas sa forme, mais simplement sa taille. En clair, le modèle 3D subit une translation, une rotation et une mise à l'échelle, les trois impliquant une modification des coordonnées des sommets..
[[File:Similarity and congruence transformations.svg|centre|vignette|upright=1.5|Transformations géométriques possibles pour chaque triangle.]]
Une fois le placement des différents objets effectué, la carte graphique effectue un changement de coordonnées pour centrer le monde sur la caméra. Au lieu de considérer un des bords de la scène 3D comme étant le point de coordonnées (0, 0, 0), il va passer dans le référentiel de la caméra. Après cette transformation, le point de coordonnées (0, 0, 0) sera la caméra. La direction de la vue du joueur sera alignée avec l'axe de la profondeur (l'axe Z).
[[File:View transform.svg|centre|vignette|upright=2|Étape de transformation dans un environnement en deux dimensions : avant et après. On voit que l'on centre le monde sur la position de la caméra et dans sa direction.]]
Enfin, il faut aussi corriger la perspective, ce qui est le fait de l'étape de projection, qui modifie la forme du ''view frustum'' sans en modifier le contenu. Différents types de perspective existent et celles-ci ont un impact différent les unes des autres sur le ''view frustum''. Dans le cas qui nous intéresse, le ''view frustum'' passe d’une forme de trapèze tridimensionnel à une forme de pavé dont l'écran est une des faces.
===Les changements de coordonnées se font via des multiplications de matrices===
Les trois étapes précédentes demande de faire des changements de coordonnées, chaque sommet voyant ses coordonnées remplacées par de nouvelles. Or, un changement de coordonnée s'effectue assez simplement, avec des matrices, à savoir des tableaux organisés en lignes et en colonnes avec un nombre dans chaque case. Un changement de coordonnées se fait simplement en multipliant le vecteur (X, Y, Z) des coordonnées d'un sommet par une matrice adéquate. Il existe des matrices pour la translation, la mise à l'échelle, d'autres pour la rotation, une autre pour la transformation de la caméra, une autre pour l'étape de projection, etc.
Un changement de coordonnée s'effectue assez simplement en multipliant le vecteur-coordonnées (X, Y, Z) d'un sommet par une matrice adéquate. Un petit problème est que les matrices qui le permettent sont des matrices avec 4 lignes et 4 colonnes. Or, la multiplication demande que le nombre de coordonnées du vecteur soit égal au nombre de colonnes. Pour résoudre ce petit problème, on ajoute une 4éme coordonnée aux sommets, la coordonnée homogène, qui ne sert à rien pour le moment. Elle est souvent mise à 1, par défaut. Mais oublions ce détail.
Il se trouve que multiplier des matrices amène certaines simplifications. Au lieu de faire plusieurs multiplications de matrices, il est possible de fusionner les matrices en une seule, ce qui permet de simplifier les calculs. Ce qui fait que le placement des objets, changement de repère pour centrer la caméra, et d'autres traitements forts différents sont regroupés ensemble.
Le traitement de la géométrie implique, sans surprise, des calculs de géométrie dans l'espace. Et cela implique des opérations mathématiques aux noms barbares : produits scalaires, produits vectoriels, et autres calculs impliquant des vecteurs et/ou des matrices. Et les calculs vectoriels/matriciels impliquent beaucoup d'additions, de soustractions, de multiplications, de division, mais aussi des opérations plus complexes : calculs trigonométriques, racines carrées, inverse d'une racine carrée, etc.
Au final, un simple processeur peut faire ce genre de calculs, si on lui fournit le programme adéquat, l'implémentation est assez aisée. Mais on peut aussi implémenter le tout avec un circuit spécialisé, non-programmable. Les deux solutions sont possibles, tant que le circuit dispose d'assez de puissance de calcul. Les cartes graphiques anciennes contenaient un ou plusieurs circuits de multiplication de matrices spécialisés dans l'étape de transformation. Chacun de ces circuits prend un sommet et renvoie le sommet transformé. Ils sont composés d'un gros paquet de multiplieurs et d'additionneurs flottants. Pour plus d'efficacité, les cartes graphiques comportent plusieurs de ces circuits, afin de pouvoir traiter plusieurs sommets en même temps.
==L'élimination des surfaces cachées==
Un point important du rendu 3D est que ce que certaines portions de la scène 3D ne sont pas visibles depuis la caméra. Et idéalement, les portions de la scène 3D qui ne sont pas visibles à l'écran ne doivent pas être calculées. A quoi bon calculer des choses qui ne seront pas affichées ? Ce serait gâcher de la puissance de calcul. Et pour cela, de nombreuses optimisations visent à éliminer les calculs inutiles. Elles sont regroupées sous les termes de '''''clipping''''' ou de '''''culling'''''. La différence entre ''culling'' et ''clipping'' n'est pas fixée et la terminologie n'est pas claire. Dans ce qui va suivre, nous n'utiliserons que le terme ''culling''.
Les cartes graphiques modernes embarquent diverses méthodes de ''culling'' pour abandonner les calculs quand elles s’aperçoivent que ceux-ci portent sur une partie non-affichée de l'image. Cela fait des économies de puissance de calcul assez appréciables et un gain en performance assez important. Précisons que le ''culling'' peut être plus ou moins précoce suivant le type de rendu 3D utilisé, mais nous verrons cela dans la suite du chapitre.
===Les différentes formes de ''culling''/''clipping''===
La première forme de ''culling'' est le '''''view frustum culling''''', dont le nom indique qu'il s'agit de l'élimination de tout ce qui est situé en-dehors du ''view frustum''. Ce qui est en-dehors du champ de vision de la caméra n'est pas affiché à l'écran n'est pas calculé ou rendu, dans une certaine mesure. Le ''view frustum culling'' est assez trivial : il suffit d'éliminer ce qui n'est pas dans le ''view frustum'' avec quelques calculs de coordonnées assez simples. Quelques subtilités surviennent quand un triangle est partiellement dans le ''view frustrum'', ce qui arrive parfois si le triangle est sur un bord de l'écran. Mais rien d'insurmontable.
[[File:View frustum culling.svg|centre|vignette|upright=1|''View frustum culling'' : les parties potentiellement visibles sont en vert, celles invisibles en rouge et celles partiellement visibles en bleu.]]
Les autres formes de ''culling'' visent à éliminer ce qui est dans le ''view frustum'', mais qui n'est pas visible depuis la caméra. Pensez à des objets cachés par un autre objet plus proche, par exemple. Ou encore, pensez aux faces à l'arrière d'un objet opaque qui sont cachées par l'avant. Ces deux cas correspondent à deux types de ''culling''. L'élimination des objets masqués par d'autres est appelé l'''occlusion culling''. L'élimination des parties arrières d'un objet est appelé le ''back-face culling''. Dans les deux cas, nous parlerons d''''élimination des surfaces cachées'''.
[[File:Occlusion culling example PL.svg|centre|vignette|''Occlusion culling'' : les objets en bleu sont visibles, ceux en rouge sont masqués par les objets en bleu.]]
Le lancer de rayons n'a pas besoin d'éliminer les surfaces cachées, il ne calcule que les surfaces visibles. Par contre, la rastérisation demande d'éliminer les surfaces cachées. Sans cela, le rendu est incorrect dans le pire des cas, ou alors le rendu calcule des surfaces invisibles pour rien. Il existe de nombreux algorithmes logiciels pour implémenter l'élimination des surfaces cachées, mais la carte graphique peut aussi s'en charger.
L'''occlusion culling'' demande de connaitre la distance à la caméra de chaque triangle. La distance à la caméra est appelée la '''profondeur''' du triangle. Elle est déterminée à l'étape de rastérisation et est calculée à chaque sommet. Lors de la rastérisation, chaque sommet se voit attribuer trois coordonnées : deux coordonnées x et y qui indiquent sa position à l'écran, et une coordonnée de profondeur notée z.
===L'algorithme du peintre===
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les triangles du plus lointain au plus proche. L'idée est que si deux triangles se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Quelque chose qui devrait vous rappeler le rendu 2D, où les sprites sont rendus du plus lointain au plus proche. Il ne s'agit ni plus ni moins que de l''''algorithme du peintre'''.
[[File:Polygons cross.svg|vignette|Polygons cross]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
Un autre problème est que l'algorithme demande de trier les triangles d'une scène 3D selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
===Le tampon de profondeur===
[[File:Z-buffer no text.jpg|vignette|Z-buffer correspondant à un rendu]]
Une autre solution utilise ce qu'on appelle un '''tampon de profondeur''', aussi appelé un ''z-buffer''. Il s'agit d'un tableau, stocké en mémoire vidéo, qui mémorise la coordonnée z de l'objet le plus proche pour chaque pixel. Par défaut, ce tampon de profondeur est initialisé avec la valeur de profondeur maximale, celle du ''far plane'' du ''viewfrustum''. Au fur et à mesure que les objets seront calculés, le tampon de profondeur est mis à jour, conservant ainsi la trace de l'objet le plus proche de la caméra.
Si jamais un triangle a une coordonnée z plus grande que celle du tampon de profondeur, cela veut dire qu'il est situé derrière un objet déjà rendu. Il est éliminé (sauf si transparence il y a) et le tampon de profondeur n'a pas à être mis à jour. Dans le cas contraire, l'objet est plus près de la caméra et sa coordonnée z remplace l'ancienne valeur z dans le tampon de profondeur.
[[File:Z-buffer.svg|centre|vignette|upright=2.0|Illustration du processus de mise à jour du Z-buffer.]]
Il existe des techniques alternatives pour coder la coordonnée de profondeur, qui se distinguent par le fait que la coordonnée z n'est pas proportionnelle à la distance entre le fragment et la caméra. Mais il s'agit là de détails assez mathématiques que je me permets de passer sous silence. Dans la suite de ce cours, nous allons juste parler de profondeur pour regrouper toutes ces techniques, conventionnelles ou alternatives.
Toutes les cartes graphiques modernes utilisent un système de ''z-buffer''. C'est la seule solution pour avoir des performances dignes de ce nom. Il faut cependant noter qu'elles utilisent des tampons de profondeur légèrement modifiés, qui ne mémorisent pas la coordonnée de profondeur, mais une valeur dérivée. Pour simplifier, ils ne mémorisent pas la coordonnée de profondeur z, mais son inverse 1/z. Les raisons à cela ne peuvent pas encore être expliquées à ce moment du cours, aussi nous allons simplement dire que c'est une histoire de correction de perspective.
Les coordonnées z et 1/z sont codées sur quelques bits, allant de 16 bits pour les anciennes cartes graphiques, à 24/32 bits pour les cartes plus récentes. De nos jours, les Z-buffer de 16 bits sont abandonnés et toutes les cartes graphiques utilisent des coordonnées z de 24 à 32 bits. La raison est que les Z-buffer de 16 bits ont une précision insuffisante, ce qui fait que des artefacts peuvent survenir. Si deux objets sont suffisamment proches, le tampon de profondeur n'a pas la précision suffisante pour discriminer les deux objets. Pour lui, les deux objets sont à la même place. Conséquence : il faut bien choisir un des deux objets et ce choix se fait pixel par pixel, ce qui fait des artefacts visuels apparaissent. On parle alors de '''''z-fighting'''''. Voici ce que cela donne :
[[File:Z-fighting.png|centre|vignette|Z-fighting]]
Un défaut du tampon de profondeur est qu'il ne gère pas correctement les objets transparents. Dès que de la transparence est présente dans une scène 3D, le tampon de profondeur ne peut pas être utilisé. Une solution pour cela est de rendre une scène 3D en deux phases : une pour les objets opaques, une avec les objets transparents. La où on rend les objets opaques utilise le tampon de profondeur, mais il est désactivé lors de la seconde.
==La rastérisation et les textures==
Dans cette section, nous allons voir ensemble l'étape de rastérisation et l'étape de traitement des pixels. La rastérisation est difficile à expliquer, surtout que son rôle exact dépend de la technique de rendu utilisée. Pour simplifier, elle projette un rendu en 3D sur un écran en 2D. Une autre explication tout aussi vague est qu'elle s'occupe la traduction des triangles en un affichage pixelisé à l'écran. Elle détermine à quoi ressemble la scène visible sur l'écran. C'est par exemple lors de cette étape que sont appliquées certaines techniques de ''culling'', qui éliminent les portions non-visibles de l'image, ainsi qu'une correction de la perspective et diverses opérations d'interpolation dont nous parlerons dans plusieurs chapitres.
La rastérisation et placage de textures sont deux opérations très liées entre elles. Il existe deux manières principales pour lier les textures à la géométrie : la méthode directe et la méthode inverse (''UV Mapping''). Et les deux font que la rastérisation se fait de manière très différente. Précisons cependant que les rendus les plus simples n'utilisent pas de textures du tout. Ils se contentent de colorier les triangles, voire d'un simple rendu en fil de fer basé sur du tracé de lignes. Dans la suite de cette section, nous allons voir les quatre types de rendu principaux : le rendu en fils de fer, le rendu colorié, et deux rendus utilisant des textures.
===Le rendu en fil de fer===
[[File:Obj lineremoval.png|vignette|Rendu en fil de fer d'un objet 3D.]]
Le '''rendu 3D en fils de fer''' est illustré ci-contre. Il s'agit d'un rendu assez ancien, utilisé au tout début de la 3D, sur des machines qu'on aurait du mal à appeler ordinateurs. Il se contente de tracer des lignes à l'écran, lignes qui connectent deux sommets, qui ne sont autres que les arêtes de la géométrie de la scène rendue. Le tout était suffisant pour réaliser quelques jeux vidéos rudimentaires. Les tout premiers jeux vidéos utilisaient ce rendu, l'un d'entre eux étant Maze War, le tout premier FPS.
{|
|[[File:Maze war.jpg|vignette|Maze war]]
|[[File:Maze representation using wireframes 2022-01-10.gif|centre|vignette|Maze representation using wireframes 2022-01-10]]
|}
Le monde est calculé en 3D, il y a toujours un calcul de la géométrie, la scène est rastérisée normalement, les portions invisbles de l'image sont retirées, mais il n'y a pas d'application de textures après rastérisation. À la place, un algorithme de tracé de ligne trace les lignes à l'écran. Quand un triangle passe l'étape de rastérisation, l'étape de rastérisation fournit la position des trois sommets sur l'écran. En clair, elle fournit les coordonnées de trois pixels, un par sommet. À la suite, un algorithme de tracé de ligne trace trois lignes, une par paire de sommet.
L'implémentation demande juste d'avoir une unité de calcul géométrique, une unité de rastérisation, et un VDC qui supporte le tracé de lignes. Elle est donc assez simple et ne demande pas de circuits de gestion des textures ni de ROP. Le VDC écrit directement dans le ''framebuffer'' les lignes à tracer.
Il a existé des proto-cartes graphiques spécialisées dans ce genre de rendu, comme le '''''Line Drawing System-1''''' de l'entreprise Eans & Sutherland. Nous détaillerons son fonctionnement dans quelques chapitres.
===Le rendu à primitives colorées===
[[File:MiniFighter.png|vignette|upright=1|Exemple de rendu pouvant être obtenu avec des sommets colorés.]]
Une amélioration du rendu précédent utilise des triangles/''quads'' coloriés. Chaque triangle ou ''quad'' est associé à une couleur, et cette couleur est dessinée sur le triangle/''quad''après la rastérisation. Le rendu est une amélioration du rendu en fils de fer. L'idée est que chaque triangle/''quad'' est associé à une couleur, qui est dessinée sur le triangle/''quad'' après la rastérisation. La technique est nommée ''colored vertices'' en anglais, nous parlerons de '''rendu à maillage coloré'''.
[[File:Malla irregular de triángulos modelizando una superficie convexa.png|centre|vignette|upright=2|Maillage coloré.]]
La couleur est propagée lors des calculs géométriques et de la rastérisation, sans subir de modifications. Une fois un rendu en fils de fer effectué, la couleur du triangle est récupérée. Le triangle/''quad'' rendu correspond à un triangle/''quad'' à l'écran. Et l'intérieur de ce triangle/''quad'' est colorié avec la couleur transmise. Pour cela, on utilise encore une fois une fonction du VDC : celle du remplissage de figure géométrique. Nous l’avions vu en parlant des VDC à accélération 2D, mais elle est souvent prise en charge par les ''blitters''. Ils peuvent remplir une figure géométrique avec une couleur unique, on réutilise cette fonction pour colorier le triangle/''quad''. L'étape de rastérisation fournit les coordonnées des sommets de la figure géométrique, le ''blitter'' les utilise pour colorier la figure géométrique.
Niveau matériel, quelques bornes d'arcade ont utilisé ce rendu. La toute première borne d'arcade utilisant le rendu à maillage coloré est celle du jeu I Robot, d'Atari, sorti en 1983. Par la suite, dès 1988, les cartes d'arcades Namco System 21 et les bornes d'arcades Sega Model 1 utilisaient ce genre de rendu. On peut s'en rendre compte en regardant les graphismes des jeux tournant sur ces bornes d'arcade. Des jeux comme Virtua Racing, Virtua Fighter ou Virtua Formula sont assez parlants à ce niveau. Leurs graphismes sont assez anguleux et on voit qu'ils sont basés sur des triangles uniformément colorés.
Pour ceux qui veulent en savoir plus sur la toute première borne d'arcade en rendu à maillage colorée, la borne ''I Robot'' d'Atari, voici une vidéo youtube à ce sujet :
* [https://www.youtube.com/watch?v=6miEkPENsT0 I Robot d'Atari, le pionnier de la 3D Flat.]
===Le placage de textures direct===
Les deux rendus précédents sont très simples, mais n'utilisent pas de textures. Et il est temps de voir les deux rendus qui utilisent des textures. Il y en a deux types, appelés rendu avec placage de texture direct et indirect, nous allons voir le '''rendu par placage de texture direct''' en premier. Et nous l'appellerons ''rendu direct'' dans ce qui suit, pour simplifier les explications. Le rendu direct est une sorte d'intermédiaire entre rendu 2D à base de ''sprite'' et rendu 3D moderne. La géométrie est rendue en 3D pour générer des ''quads'', mais ces ''quads'' ne servent à guider la copie des sprites/textures dans le ''framebuffer''.
Le rendu direct peut utiliser aussi bien des triangles que des ''quads'', mais nous allons partir du principe qu'il utilise des '''''quads''''', à savoir que les objets 3D sont composés de quadrilatères. Lorsqu'un ''quad'' est rastérisé, sa forme à l'écran est un rectangle déformé par la perspective. On obtient un rectangle si le ''quad'' est vu de face, un trapèze si on le voit de biais. Pour résumer, un 'quad' correspond à un quadrilatère sur l'écran. Et le placage de texture est effectué quand on sait quelles sont les coordonnées de ce quadrilatère à l'écran.
Le rendu direct associe à chaque ''quad'', une texture rectangulaire, assez semblable à un ''sprite'' si on regarde de très loin. L'idée est de remplir le quadrilatère à l'écran avec ce ''sprite'', cette texture. La seule subtilité est que la texture/''sprite'' doit être déformé pour rentrer dans le quadrilatère, cette déformation dépendant de la perspective, de l'angle entre le ''quad'' et la caméra, et ainsi de suite. Une fois déformée, le ''sprite''/texture est directement recopié au bon endroit dans le ''framebuffer'', pour remplir le quadrilatère.
Le sprite doit être déformé de deux manières : il doit être agrandi/réduit en fonction de la taille de la figure affichée à l'écran, tourné en fonction de l'orientation du ''quad'', déformé pour gérer la perspective. Pour cela, il faut connaitre les coordonnées de profondeur de chaque bord d'un ''quad'', et de faire quelques calculs. N'importe quel VDC incluant un ''blitter'' avec une gestion du zoom/rotation des sprites peut le faire. Au-delà de cette déformation, il vaut mieux appliquer un filtre d'interpolation pour lisser l'image des ''sprite'' déformés, afin d'obtenir une bonne qualité d'image. Le filtre en question et n'est pas très différent du filtrage de texture qui lisse les textures à l'écran.
[[File:TextureMapping.png|centre|vignette|upright=2|Exemple caricatural de placage de texture sur un ''quad''.]]
Un point important est que les ''quads'' doivent être rendus du plus lointain au plus proche. Sans cela, on obtient rapidement des erreurs de rendu. L'idée est que si deux quads se chevauchent, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. L'écriture du sprite du second quad écrasera les données du premier quad, pour les portions recouvertes, lors de l'écriture du sprite dans le ''framebuffer''. Quelque chose qui devrait vous rappeler le rendu 2D avec l'algorithme du peintre, où les sprites sont rendus du plus lointain au plus proche.
Le rendu inverse utilise très souvent des triangles pour la géométrie, alors que le rendu direct a tendance à utiliser des ''quads'', mais il ne s'agit pas d'une différence stricte. L'usage de triangles/''quads'' peut se faire aussi bien avec un rendu direct comme avec un rendu inverse. Cependant, le rendu en ''quad'' se marie très bien au rendu direct, alors que le rendu en triangle colle mieux au rendu inverse.
L'avantage de cette technique est qu'on parcourt les textures dans un ordre bien précis. Par exemple, on peut parcourir la texture ligne par ligne, l'exploiter par blocs de 4*4 pixels, etc. Et accéder à une texture de manière prédictible se marie bien avec l'usage de mémoires caches, ce qui est un avantage en matière de performances. Mais un même pixel du ''framebuffer'' est écrit plusieurs fois quand plusieurs quads se superposent, alors que le rendu inverse gère la situation avec une seule écriture (sauf si usage de la transparence). De plus, la gestion de la transparence était compliquée et les jeux devaient ruser en utilisation des solutions logicielles assez complexes.
Niveau implémentation matérielle, une carte graphique en rendu direct demande juste trois circuits. Le premier est un circuit de calcul géométrique, qui rend la scène 3D. Le tri des quads est souvent réalisé par le processeur principal, et non pas par un circuit séparé. Toutes les étapes au-delà de l'étape de rastérisation étaient prises en charge par un VDC amélioré, qui écrivait des sprites/textures directement dans le ''framebuffer''.
{|class="wikitable"
|-
! Géométrie
| Processeurs dédiés programmé pour émuler le pipeline graphique
|-
! Tri des quads du plus lointain au plus proche
| Processeur principal (implémentation logicielle)
|-
! Application des textures
| ''Blitter'' amélioré, capable de faire tourner et de zoomer sur des ''sprites''.
|}
L'implémentation était très simple et réutilisait des composants déjà existants : des VDC 2D pour l'application des textures, des processeurs dédiés pour la géométrie. Les unités de calcul de la géométrie étaient généralement implémentées avec un ou plusieurs processeurs dédiés. Vu qu'on savait déjà effectuer le rendu géométrique en logiciel, pas besoin de créer un circuit sur mesure. Il suffisait de dédier un processeur spécialisé rien que pour les calculs géométriques et on lui faisait exécuter un code déjà bien connu à la base. En clair, ils utilisaient un code spécifique pour émuler un circuit fixe. C'était clairement la solution la plus adaptée pour l'époque.
Les unités géométriques étaient des processeurs RISC, normalement utilisés dans l'embarqué ou sur des serveurs. Elles utilisaient parfois des DSP. Pour rappel, les DSP des processeurs de traitement de signal assez communs, pas spécialement dédiés aux rendu 3D, mais spécialisé dans le traitement de signal audio, vidéo et autre. Ils avaient un jeu d'instruction assez proche de celui des cartes graphiques actuelles, et supportaient de nombreuses instructions utiles pour le rendu 3D.
Le rendu direct a été utilisé dans la période de transition entre rendu 2D et rendu 3D, car il était très adapté pour faire cette transition. Coupler un VDC à un processeur pour la géométrie était particulièrement simple à l'époque. Le rendu direct a été utilisé sur des bornes d'arcade dès les années 90. Outre les bornes d'arcade, quelques consoles de 5ème génération utilisaient le rendu direct, avec les mêmes solutions matérielles. La géométrie était calculée sur plusieurs processeurs dédiés. Le reste du pipeline était géré par un VDC 2D qui implémentait le placage de textures. Deux consoles étaient dans ce cas : la 3DO, et la Sega Saturn. Le rendu direct est aujourd'hui abandonné.
===Le placage de textures inverse===
Le rendu précédent, le rendu direct, permet d'appliquer des textures directement dans le ''framebuffer''. Mais comme dit plus haut, il existe une seconde technique pour plaquer des textures, appelé le '''placage de texture inverse''', aussi appelé l'''UV Mapping''. Elle associe une texture complète pour un modèle 3D,contrairement au placage de tecture direct qui associe une texture par ''quad''/triangle. L'idée est que l'on attribue un texel à chaque sommet. Plus précisémment, chaque sommet est associé à des '''coordonnées de texture''', qui précisent quelle texture appliquer, mais aussi où se situe le texel à appliquer dans la texture. Par exemple, la coordonnée de texture peut dire : je veux le pixel qui est à ligne 5, colonne 27 dans cette texture. La correspondance entre texture et géométrie est réalisée lorsque les créateurs de jeu vidéo conçoivent le modèle de l'objet.
[[File:Texture Mapping example.png|centre|vignette|upright=2|Exemple de placage de texture.]]
Dans les faits, on n'utilise pas de coordonnées entières de ce type, mais deux nombres flottants compris entre 0 et 1. La coordonnée 0,0 correspond au texel en bas à gauche, celui de coordonnée 1,1 est tout en haut à droite. L'avantage est que ces coordonnées sont indépendantes de la résolution de la texture, ce qui aura des avantages pour certaines techniques de rendu, comme le ''mip-mapping''. Les deux coordonnées de texture sont notées u,v avec DirectX, ou encore s,t dans le cas général : u est la coordonnée horizontale, v la verticale.
[[File:UVMapping.png|centre|vignette|upright=2|UV Mapping]]
Avec le placage de texture inverse, la rastérisation se fait grosso-modo en trois étapes : la rastérisation proprement dite, le placage de textures, et les opérations finales qui écrivent un pixel dans le ''framebuffer''. Au niveau du matériel, ainsi que dans la plupart des API 3D, les trois étapes sont réalisées par des circuits séparés.
[[File:01 3D-Rasterung-a.svg|vignette|Illustration du principe de la rasterization. La surface correspondant à l'écran est subdivisée en pixels carrés, de coordonnées x et y. La caméra est placée au point e. Pour chaque pixel, on trace une droite qui part de la caméra et qui passe par le pixel considéré. L'intersection entre une surface et cette droite se fait en un point, appartenant à un triangle.]]
Lors de la rasterisation, chaque triangle se voit attribuer un ou plusieurs pixels à l'écran. Pour bien comprendre, imaginez une ligne droite qui part de caméra et qui passe par un pixel sur le plan de l'écran. Cette ligne intersecte 0, 1 ou plusieurs objets dans la scène 3D. Les triangles situés ces intersections entre cette ligne et les objets rencontrés seront associés au pixel correspondant. L'étape de rastérisation prend en entrée un triangle et renvoie la coordonnée x,y du pixel associé.
Il s'agit là d'une simplification, car un triangle tend à occuper plusieurs pixels sur l'écran. L'étape de rastérisation fournit la liste de tous les pixels occupés par un triangle, et les traite un par un. Quand un triangle est rastérisé, le rasteriseur détermine la coordonnée x,y du premier pixel, applique une texture dessus, puis passe au suivant, et rebelote jusqu'à ce que tous les pixels occupés par le triangles aient été traités.
L'implémentation matérielle du placage de texture inverse est beaucoup plus complexe que pour les autres techniques. Pour être franc, nous allons passer le reste du cours à parler de l'implémentation matérielle du placage de texture inverse, ce qui prendra plus d'une dizaine de chapitres.
==La transparence, les fragments et les ROPs==
Dans ce qui suit, nous allons parler uniquement de la rastérisation avec placage de textures inverse. Les autres formes de rastérisation ne seront pas abordées. La raison est que tous les GPUs modernes utilisent cette forme de rastérisation, les exceptions étant rares. De même, ils utilisent un tampon de profondeur, pour l'élimination des surfaces cachées.
La rastérisation effectue donc des calculs géométriques, suivis d'une étape de rastérisation, puis de placage des textures. Ces trois étapes sont réalisées par une unité géométrique, une unité de rastérisation, et un circuit de placage de textures. Du moins sur le principe, car les cartes graphiques modernes ont fortement optimisé l'implémentation et n'ont pas hésité à fusionner certains circuits. Mais nous verrons cela en temps voulu, nous n'allons pas résumer plusieurs décennies d'innovation technologique en quelques paragraphes.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Placage de textures
|}
Mais où mettre le tampon de profondeur ? Intuitivement, on se dit qu'il vaut mieux faire l'élimination des surfaces cachées le plus tôt possible, dès que la coordonnée de profondeur est connue. Et elle est connu à l'étape de rastérisation, une fois les sommets transformés.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Tampon de profondeur
| Placage de textures
|}
En réalité, la profondeur des fragments est gérée par un circuit appelé le '''''Raster Operations Pipeline''''' (ROP), situé à la toute fin du pipeline graphique. Dans ce qui suit, nous utiliserons l'abréviation ROP pour simplifier les explications. Le ROP effectue quelques traitements sur les fragments, avant d'enregistrer l'image finale dans la mémoire vidéo. Il est placé à la fin du pipeline pour gérer correctement la transparence. Et nous allons voir pourquoi la transparence est gérée à la fin du pipeline.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Géométrie
| Rastérisation
| Placage de textures
| ''Raster Operations Pipeline''
|}
===Le mélange ''alpha''===
La transparence se manifeste quand plusieurs objets sont l'un derrière l'autre. Histoire de simplifier les explications, nous allons d'abord voir le cas où un objet semi-transparent est devant un objet opaque. La couleur perçue est alors un mélange de la couleur de l'objet opaque et celle de l'objet semi-transparent. Le mélange dépend d'à quel point l'objet semi-transparent est transparent. Avec un objet parfaitement transparent, seul l'objet opaque est visible. Avec un objet à moitié transparent, la couleur finale sera pour moitié celle de l'objet opaque, pour moitié celle de l'objet semi-transparent. Et c'est pareil pour les cas intermédiaires entre un objet totalement transparent et un objet totalement opaque.
La transparence d'un objet/pixel est définie par un nombre, appelé la '''composante ''alpha'''''. Plus la composante alpha est élevée, plus le pixel est opaque. Elle vaut 1 pour un objet opaque et 0 pour un objet transparent. Elle est ajoutée aux composantes RGB, ce qui fait que tout fragment contient une "couleur de transparence" en plus des couleurs RGB. Elle agit comme un coefficient qui dit comment mélanger la couleur d'un objet transparent et d'un objet opaque. Le calcul de la transparence est une moyenne pondérée par la composante alpha. On parle alors d''''''alpha blending'''''.
: <math>\text{Couleur finale} = \alpha \times \text{Couleur de l'objet transparent} + (1 - \alpha) \times \text{Couleur de l'objet opaque}</math>
[[File:Texture splatting.png|centre|vignette|upright=2.0|Calcul de transparence. La première ligne montre le produit pour l'objet transparent, la seconde ligne est celle de l'objet opaque. La troisième ligne est celle de l'addition finale.]]
Maintenant, qu'en est-il du cas où plusieurs objets sont superposés ? Si vous tracez une demi-droite dont l'origine est la caméra et qui passe par le pixel, il arrive qu'elle intersecte la géométrie en plusieurs points, un point par objet sur la ligne du regarde. Sans transparence, l'objet le plus proche cache tous les autres et c'est donc lui qui décide de la couleur du pixel. Mais avec un objet transparent, la couleur finale est un mélange de la couleur de plusieurs points d'intersection. Il faut donc calculer un pseudo-pixel pour chaque point d'intersection, auquel on donne le nom de '''fragment'''. Un fragment possède une position à l'écran, une coordonnée de profondeur, une couleur, ainsi que quelques autres informations potentiellement utiles.
Les fragments attribués à un même pixel, qui sont à la même position sur l'écran, sont combinés pour obtenir la couleur finale de ce pixel. Il est possible d'utiliser le mélange ''alpha'' pour cela. Il suffit de faire le mélange ''alpha'' entre le fragment qui vient d'être calculé, et le pixel dans le ''framebuffer''. Pour cela, le fragment a une composante ''alpha'', qui est ajouté aux trois couleurs RGB. Le pixel déjà dans le ''framebuffer'' est un résultat temporaire, né du mélange ''alpha'' de tous les fragments précédents.
Un défaut de cette méthode est qu'elle fonctionne assez mal avec un tampon de profondeur. Si le tampon de profondeur est activé, le mélange ''alpha'' ne fonctionne que si les objets sont rendus du plus lointain au plus proche. Et procéder dans cet ordre a un défaut : on dessine des objets dans le ''framebuffer'', pour qu'ensuite les objets devant écrasent ce qui a déjà été dessiné. Un même pixel peut donc être dessiné plusieurs fois, dont une seule sera pertinente. Et ces écritures utilisent de la bande passante mémoire, qui est une ressource précieuse sur un GPU moderne. Il s'agit d'un phénomène appelé '''''overdraw''''', ou sur-dessinage en français. Quelques optimisations permettent d'éliminer l'''overdraw'' en rendant les objets du plus proche au plus lointain, d'autres permettent de dessiner des objets dans un ordre arbitraire, mais nous ne pouvons pas en parler ici.
Beaucoup de moteurs 3D rendent séparément les objets opaques et transparents. Une première passe rend les objets opaques, puis les objets transparents sont rendus dans une seconde passe. Les objets opaques sont rendus dans le désordre, ce qui fait qu'on n'a pas à les trier, alors que les objets transparents doivent être triés selon leur distance. un autre avantage est que le mélange ''alpha'' est désactivé lors de la première passe, alors que c'est la mise à jour du tampon de profondeur qui est désactivé lors de la seconde passe, ce qui augmente un peu les performances dans les deux cas.
===Le test ''alpha''===
Le test ''alpha'' est une technique qui permet d'annuler le rendu d'un fragment en fonction de sa transparence. Si la composante alpha est en-dessous ou au-dessus d'un seuil, le fragment est simplement abandonné. Le seuil en question est configurable, de même que la comparaison utilisée : on peut éliminer le fragment si sa transparence est au-dessus d'un certain seuil, en-dessous, égal, différent, etc.
Il s'agit d'une optimisation qui est utile dans certains scénarios spécifiques. Par exemple, si l'objet a une transparence très élevée, du genre 95%, autant le compter comme complétement transparent, afin d'éviter des opérations de mélange ''alpha''. En effet, les opérations de mélange ''alpha'' sont très lentes, car elles demandent de faire des opérations de lecture-écriture en mémoire vidéo : on lit un pixel dans le ''framebuffer'', on applique le mélange ''alpha'' et on écrit le résultat en mémoire vidéo. L'''alpha test'' permet donc de gagner en performance au prix d'une baisse de la qualité d'image.
Il y a cependant des cas où l'usage du test ''alpha'' est primordial, au-delà d'une question de performances. Un exemple classique est celui du rendu du feuillage dans un jeu 3D. Un feuillage est composé en assemblant plusieurs images de feuilles. Chaque feuille est un carré sur lequel on place une texture de feuille, qui est opaque pour la partie verte des feuilles, transparente pour le reste. Les carrés ne sont cependant pas superposés, mais s'intersectent fortement, ce qui fait que le mélange ''alpha'' ne donne pas de bons résultats. L'usage du test ''alpha'' permet d'obtenir un rendu correct. Pour d'informations via ce lien :
* [https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f Anti-aliased Alpha Test: The Esoteric Alpha To Coverage].
===Les effets de brouillard===
Les '''effets de brouillard''' sont nécessaires dans certains jeux vidéo pour l'ambiance (pensez à des jeux d'horreur comme Silent Hill), mais ils ont surtout été utilisés pour économiser des calculs. L'idée est de ne pas calculer les graphismes au-delà d'une certaine distance, sans que cela se voie. Le ''view frustum'' utilise alors un plan limite, au-delà duquel on ne voit pas les objets. Mais ce plan limite donne une cassure inesthétique dans le rendu. Pour masquer cette cassure, les programmeurs ajoutaient un effet de brouillard. Les objets au-delà du plan limite étaient totalement dans le brouillard, puis ce brouillard se réduisait progressivement en se rapprochant de la caméra, avant de s'annuler à partir d'une certaine distance.
Pour calculer le brouillard, on effectue un mélange ''alpha'' entre la couleur du pixel et une ''couleur de brouillard''. La différence est que l'on n'utilise pas la transparence pour faire le mélange, mais un '''coefficient de brouillard''', noté <math>\text{fog}(z)</math>.
: <math>\text{Couleur finale} = \text{fog}(z) \times \text{Couleur de brouillard} + [ 1 - \text{fog}(z) ] \times \text{Couleur du pixel}</math>
Le coefficient de brouillard dépend de la coordonnée de profondeur, de la distance du pixel par rapport à la caméra. Le brouillard démarre à une distance <math>z_{fog-start}</math>, et masque totalement les objets à partir d'une distance <math>z_{fog-end}</math>. Entre les deux, le coefficient de brouillard dépend de la distance. OpenGL autorise trois formules de calcul suivantes :
: <math>\text{fog}(z) = \frac{z_{fog-end} - z}{z_{fog-end} - z_{fog-start}}</math>
: <math>\text{fog}(z) = e^{- k \times z}</math>
: <math>\text{fog}(z) = e^{- (k \times z)^2}</math>
==L'éclairage d'une scène 3D==
L'éclairage d'une scène 3D calcule les ombres, mais aussi la luminosité de chaque pixel, ainsi que bien d'autres effets graphiques. Les algorithmes d'éclairage ont longtemps été implémentés directement en matériel, les cartes graphiques géraient l'éclairage dans des circuits spécialisés. Aussi, il est important de voir ces algorithmes d'éclairage. Il est possible d'implémenter l'éclairage à deux endroits différents du pipeline : juste avant la rastérisation, et après la rastérisation.
===Les sources de lumière et les couleurs associées===
L'éclairage d'une scène 3D provient de sources de lumières, comme des lampes, des torches, le soleil, etc. Il existe de nombreux types de sources de lumière, et nous n'allons parler que des principales. Elles sont au nombre de quatre et elles sont illustrées ci-dessous.
[[File:3udUJ.gif|centre|vignette|upright=2|Types de sources de lumière.]]
[[File:Graphics lightmodel directional.png|vignette|upright=1.0|Source de lumière directionnelle.]]
Les '''sources directionnelles''' servent à modéliser des sources de lumière très éloignées, comme le soleil ou la lune. Elles sont simplement définies par un vecteur qui indique la direction de la lumière, rien de plus.
Les '''sources ponctuelles''' sont des points, qui émettent de la lumière dans toutes les directions. Elles sont définies par une position, et une intensité lumineuse, éventuellement la couleur de la lumière émise. Il existe deyux types de sources de lumière ponctuelles.
* Le premières émettent de manière égale dans toutes les directions. Elles sont appelées des ''point light'' dans le schéma du dessus.
* Les secondes émettent de la lumière dans une '''direction privilégiée'''. L'exemple le plus parlant est celui d'une lampe-torche : elle émet de la lumière "tout droit", dans la direction où la lampe est orientée. Elles sont appelées des ''sport light'' dans le schéma du dessus. La direction privilégiée est un vecteur, notée v dans le schéma du dessous.
[[File:Graphics lightmodel ambient.png|vignette|upright=1.0|Lumière ambiante.]]
En théorie, la lumière rebondit sur les surfaces et a tendance à se disperser un peu partout à force de rebondir. C'est ce qui explique qu'on arrive à voir à l'intérieur d'une pièce si une fenêtre est ouverte. Il en résulte un certain '''éclairage ambiant''', qui est assez difficile à représenter dans un moteur de rendu 3D. Auparavant, l'éclairage ambiant était simulé par une lumière égale en tout point de la scène 3D, appelée simplement la '''lumière ambiante'''. Précisément, on suppose que la lumière ambiante en un point vient de toutes les directions et a une intensité constante, identique dans toutes les directions. Le tout est illustré ci-contre. C'est assez irréaliste, mais ça donne une bonne approximation de la lumière ambiante.
===La lumière incidente : le terme géométrique===
Pour simplifier, nous allons supposer que l'éclairage est calculé pour chaque sommet, pas par triangle. C'est de loin le cas le plus courant, aussi ce n'est pas une simplification abusive. La lumière qui arrive sur un sommet est appelée la '''lumière incidente'''.
La couleur d'un sommet dépend de deux choses : la lumière incidente directe, comment il réfléchit cette lumière. Mathématiquement, il est possible de résumer cela avec le produit de deux termes : l'intensité de la lumière incidente, une fonction qui indique comment la surface réfléchit la lumière incidente. La fonction en question est appelée la '''réflectivité bidirectionnelle'''. Le terme anglais est ''bidirectional reflectance distribution function'', abrévié en BRDF, et nous utiliserons cette abréviation dans ce qui suit.
: <math>\text{Couleur finale} = \text{Lumière incidente} \times BRDF(...)</math>
La lumière incidente vient soit directement des sources de lumière, soit de la lumière qui a rebondit sur d'autres objets proches. La première est appelée la lumière directe, celle qui vient des rebonds s'appelle la lumière indirecte. Pour simplifier, la lumière indirecte est gérée par la lumière ambiante, nous passons sous silence les techniques d'illumination globale. En clair : nous allons nous limiter au cas où la lumière incidente vient directement d'une source de lumière, pas d'un rebond.
Intuitivement, la lumière incidente est simplement égale à l'intensité de la source de lumière. Sauf que ce n'est qu'une approximation, et une assez mauvaise. En réalité, l'approximation est bonne si la lumière arrive proche de la verticale, mais elle est d'autant plus mauvaise que la lumière arrive penchée, voire rasante.
La raison : la lumière incidente sera étalée sur une surface plus grande, si elle arrive penchée. Si vous vous souvenez de vos cours de collège, c'est le même principe qui explique les saisons. La lumière du soleil est proche de la verticale en été, mais est de plus en plus penché quand on s'avance vers l'Hiver. La lumière solaire est donc étalée sur une surface plus grande, ce qui fait qu'un point de la surface recevra moins de lumière, celle-ci étant diluée, étalée.
[[File:Radiación solar.png|centre|vignette|upright=2|Exemple avec la lumière solaire.]]
[[File:Angle of incidence.svg|vignette|upright=1|Angle d'incidence.]]
En clair, tout dépend de l''''angle d'incidence''' de la lumière. Reste à voir comment calculer cet angle. La lumière incidente est définie par un vecteur, qui part de la source de lumière et atterrit sur le sommet considéré. Imaginez simplement que ce vecteur suit un rayon lumineux provenant de la source de lumière. Le vecteur pour la lumière incidente sera noté L. L'angle d'incidence est l'angle que fait ce vecteur avec la verticale de la surface, au niveau du sommet considéré.
[[File:Graphics lightmodel ptsource.png|vignette|Normale de la surface.]]
Pour cela, les calculs d'éclairage ont besoin de connaitre la verticale d'un sommet. Un sommet est donc associé à un vecteur, appelé la '''normale''', qui indique la verticale en ce point. Deux sommets différents peuvent avoir deux normales différentes, même s'ils sont proches. Elles sont d'autant plus différentes que la surface est rugueuse, non-lisse. La normale est prédéterminée lors de la création du modèle 3D, il n'y a pas besoin de le calculer. Par contre, elle est modifiée lors de l'étape de transformation, quand on place le modèle 3D dans la scène 3D. Les deux autres vecteurs sont à calculer à chaque image, car ils changent quand on bouge le sommet.
La lumière qui arrive sur la surface dépend de l'angle entre la normale et le vecteur L. Précisément, elle dépend du cosinus de cet angle. En multipliant ce cosinus avec l'intensité de la lumière, on a la lumière arrivante. La couleur finale d'un pixel est donc :
: <math>\text{Couleur finale} = I \times \cos{(N, L)} \times BRDF(...)</math>
Le terme <math>I \times \cos{N, L}</math> ne dépend pas de la surface considérée. Juste de la position de la source de lumière, de la position du sommet et de son orientation par rapport à la lumière. Aussi, il est parfois appelé le '''terme géométrique''', en opposition aux propriétés de la surface. Les propriétés de la surface sont définies par un '''''material''''', qui indique comment il réfléchit la lumière, ainsi que sa texture.
===Le produit scalaire de deux vecteurs===
Calculer le terme géométrique demande de calculer le cosinus d'un angle. Et il n'est pas le seul : les autres calculs d'éclairage que nous allons voir demandent de calculer des cosinus. Or, les calculs trigonométriques sont très gourmands pour le GPU. Pour éviter le calcul d'un cosinus, les GPU utilisent une opération mathématique appelée le ''produit scalaire''. Le produit scalaire agit sur deux vecteurs, que l'on notera A et B. Un produit scalaire prend : la longueur des deux vecteurs, et l'angle entre les deux vecteurs noté <math>\omega</math>. Le produit scalaire est équivalent à la formule suivante :
: <math>\text{Produit scalaire de deux vecteurs A et B} = \vec{A} \cdot \vec{B} = A \times B \times \cos{(\omega)}</math>, avec A et B la longueur des deux vecteurs A et B.
L'avantage est que le produit scalaire se calcule simplement avec des additions, soustractions et multiplications, des opérations que les cartes graphiques savent faire très facilement. Le produit scalaire de deux vecteurs de coordonnées x,y,z est le suivant :
: <math>\vec{A} \cdot \vec{B} = x_A \times x_B + y_A \times y_B + z_A \times z_B</math>
En clair, on multiplie les coordonnées identiques, et on additionne les résultats. Rien de compliqué.
Un avantage est que tous les vecteurs vus précédemment sont normalisés, à savoir qu'ils ont une longueur qui vaut 1. Ainsi, le calcul du produit scalaire devient équivalent au calcul du produit scalaire.
===La réflexion de la lumière sur la surface===
[[File:Ray Diagram 2.svg|vignette|Reflection de la lumière sur une surface parfaitement lisse.]]
Maintenant que nous venons de voir le terme géométrique, voyons le BRDF, qui définit comment la surface de l'objet 3D réfléchit la lumière. Vos cours de collège vous ont sans doute appris que la lumière est réfléchie avec le même angle d'arrivée. L'angle d'incidence et l'angle de réflexion sont égaux, comme illustré ci-contre. On parle alors de '''réflexion parfaite'''.
Mais cela ne vaut que pour une surface parfaitement lisse, comme un miroir parfait. Dans la réalité, une surface a tendance à renvoyer des rayons dans toutes les directions. La raison est qu'une surface réelle est rugueuse, avec de petites aspérités et des micro-reliefs, qui renvoient la lumière dans des directions "aléatoires". La lumière « rebondit » sur la surface de l'objet et une partie s'éparpille dans un peu toutes les directions. On parle alors de '''réflexion diffuse'''.
{|
|-
|[[File:Dioptre reflexion diffuse speculaire refraction.svg|vignette|upright=1.4|Différence entre réflexion diffuse et spéculaire.]]
|[[File:Diffuse reflection.svg|vignette|upright=1|Réflexion diffuse.]]
|}
Maintenant, imaginons que la surface n'ait qu'une réflexion diffuse, pas d'autres formes de réflexion. Et imaginons aussi que cette réflexion diffuse soit parfaite, à savoir que la lumière réfléchie soit renvoyée à l'identique dans toutes les directions, sans aucune direction privilégiée. On a alors le ''material'' le plus simple qui soit, appelé un '''''diffuse material'''''.
Vu que la lumière est réfléchie à l'identique dans toutes les directions, elle sera identique peu importe où on place la caméra. La lumière finale ne dépend donc que des propriété de la surface, que de sa couleur. En clair, il suffit de donner une '''couleur diffuse''' à chaque sommet. La couleur diffuse est simplement multipliée par le terme géométrique, pour obtenir la lumière réfléchie finale. Rien de plus, rien de moins. Cela donne l'équation suivante, avec les termes suivants :
* L est le vecteur pour la lumière incidente ;
* N est la normale du sommet ;
* I est l'intensité de la source de lumière ;
* <math>C_d</math> est la couleur diffuse.
: <math>\text{Illumination diffuse} = C_d \times \left[ I \times (\vec{N} \cdot \vec{L}) \right]</math>
Rajoutons maintenant l'effet de la lumière ambiante à un ''material'' de ce genre. Pour rappel, la lumière ambiante vient de toutes les directions à part égale, ce qui fait que son angle d'incidence n'a donc pas d'effet. L'intensité de la lumière ambiante est déterminée lors de la création de la scène 3D, c'est une constante qui n'a pas à être calculée. Pour obtenir l'effet de la lumière ambiante sur un objet, il suffit de multiplier sa couleur diffuse par l'intensité de la lumière ambiante. Cependant, de nombreux moteurs de jeux ajoutent une '''couleur ambiante''', différente de la couleur diffuse.
: <math>\text{Illumination ambiante} = C_a \times I_a</math> avec <math>C_a</math> la couleur ambiante du point de surface et <math>I_a</math> l'intensité de la lumière ambiante.
En plus de la réflexion diffuse parfaite, de nombreux matériaux ajoutent une '''réflexion spéculaire''', qui n'est pas exactement la réflexion parfaite, en est très proche. Les rayons réfléchis sont très proches de la direction de réflexion parfaite, et s'atténuent très vite en s'en éloignant. Le résultat ressemble à une sorte de petit "point blanc", très lumineux, orienté vers la source de lumière, appelé le '''''specular highlight'''''. La réflexion diffuse est prédominante pour les matériaux rugueux, alors que la réflexion spéculaire est dominante sur les matériaux métalliques ou très lisses.
[[File:Phong components version 4.png|centre|vignette|upright=3.0|Couleurs utilisées dans l'algorithme de Phong.]]
[[File:Phong Vectors.svg|vignette|Vecteurs utilisés dans l'algorithme de Phong (et dans le calcul de l'éclairage, de manière générale).]]
Pour calculer la réflexion spéculaire, il faut d'abord connaitre le vecteur pour la réflexion parfaite, que nous noterons R dans ce qui suit. Le vecteur R peut se calculer avec la formule ci-dessous :
: <math>\vec{R} = 2 (\vec{L} \cdot \vec{N}) \times \vec{N} - \vec{L} </math>
La réflexion spéculaire dépend de l'angle entre la direction du regard et la normale : plus celui-ci est proche de l'angle de réflexion parfaite, plus la réflexion spéculaire sera intense. Le vecteur pour la direction du regard sera noté V, pour vue ou vision. La réflexion spéculaire est une fonction qui dépend de l'angle entre les vecteurs R et V. Le calcul de la réflexion spéculaire utilise une '''couleur spéculaire''', qui est l'équivalent de la couleur diffuse pour la réflexion spéculaire.
: <math>\text{BRDF spéculaire} = C_s \times f(\vec{R} \cdot \vec{V}) </math>
La fonction varie grandement d'un modèle de calcul spéculaire à l'autre. Aussi, je ne rentre pas dans le détail. L'essentiel est que vous compreniez que le calcul de l'éclairage utilise de nombreux calculs géométriques, réalisés avec des produits scalaires. Les calculs géométriques utilisent la couleur d'un sommet, la normale du sommet, et le vecteur de la lumière incidente. Les autres informations sont calculées à l'exécution.
===Les algorithmes d'éclairage basiques : par triangle, par sommet et par pixel===
Dans tout ce qui a été dit précédemment, l'éclairage est calculé pour chaque sommet, ce qui fait qu'on parle d''''éclairage par sommet''', ou ''vertex lighting''. Il attribue une illumination/couleur à chaque sommet de la scène 3D. Mais une fois qu'on a obtenu la couleur des sommets, reste à colorier les triangles.
Pour obtenir la couleur finale d'un pixel, l'éclairage par sommet fait une moyenne de la couleur de chaque sommet. La moyenne est une moyenne pondérée, qui tient compte de la distance du sommet avec le pixel. Plus le pixel est loin d'un sommet, plus son coefficient est petit. Typiquement, le coefficient varie entre 0 et 1 : de 1 si le pixel est sur le sommet, à 0 si le pixel est sur un des sommets adjacents. La moyenne effectuée est généralement une interpolation bilinéaire, qui est prise en charge par l'étape de rastérisation.
L'éclairage par sommet a eu son heure de gloire, mais il est maintenant remplacé par l''''éclairage par pixel''' (''per-pixel lighting''), qui calcule l'éclairage pixel par pixel. En clair, l’éclairage est finalisé après l'étape de rastérisation, il ne se fait pas qu'au niveau de la géométrie. Il existe plusieurs types d'éclairage par pixel, mais on peut les classer en deux grands types : l'éclairage de Phong et le ''bump/normal mapping''.
L''''éclairage de Phong''' calcule l'éclairage pixel par pixel. Avec cet algorithme, la géométrie n'est pas éclairée : les couleurs des sommets ne sont pas calculées. À la place, les normales sont envoyées à l'étape de rastérisation, qui effectue une opération d'interpolation, qui renvoie une normale pour chaque pixel. Les calculs d'éclairage utilisent alors ces normales pour faire les calculs d'éclairage pour chaque pixel.
{|
|-
|[[File:Per vertex lighting.png|vignette|upright=1|Eclairage par sommet.]]
|[[File:Per fragment lighting.png|vignette|upright=1|Eclairage de Phong.]]
|-
|[[File:Per vertex lighting example.png|vignette|upright=1|Eclairage par sommet.]]
|[[File:Per fragment lighting example.png|vignette|upright=1|Eclairage de Phong.]]
|}
La technique du '''''normal mapping''''' est assez simple à expliquer, sans compter que plusieurs cartes graphiques l'ont implémentée directement dans leurs circuits. Là où l'éclairage de Phong interpole les normales pour chaque pixel, le ''normal-mapping'' précalcule les normales d'une surface dans une texture, appelée la ''normal-map''. Lors du rendu 3D, la carte graphique lit les normales adéquates directement depuis cette texture, puis fait les calculs d'éclairage avec. Le ''normal mapping'' permettent d'ajouter du relief et des détails sur des surfaces planes en jouant sur l'éclairage. Il permet de simplifier grandement la géométrie rendue, tout en utilisant l'éclairage pour compenser.
[[File:WallSimpleAndNormalMapping.png|centre|vignette|upright=2|Différence sans et avec ''normal-mapping''.]]
===Les ''shaders'' : des programmes exécutés sur le GPU===
Maintenant que nous venons de voir les algorithmes d'éclairages, il est temps de voir comment les réaliser sur une carte graphique. Nous venons de voir qu'il y a une différence entre l'éclairage par pixel et par sommet. Intuitivement, l'éclairage par sommet devrait se faire avec les calculs géométriques, alors que l'éclairage par pixel devrait se faire après avoir appliqué les textures.
Les toutes premières cartes graphiques ne géraient ni l'éclairage par sommet, ni l'éclairage par pixel. Elles laissaient les calculs géométriques au CPU. Par la suite, la Geforce 256 a intégré '''circuit de ''Transform & Lightning''''', qui s'occupait de tous les calculs géométriques, éclairage par sommet inclus (d'où le L de T&L). Elle gérait alors l'éclairage par sommet, mais un algorithme particulier, qui n'était pas très flexible. Il ne gérait que des ''material'' bien précis (des ''Phong materials''), rien de plus.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| Unité de T&L : géométrie
| Rastérisation
| Placage de textures
| ''Raster Operations Pipeline''
|}
L'amélioration suivante est venue sur la Geforce 3 : l'unité de T&L est devenue programmable. Au vu le grand nombre d'algorithmes d'éclairages possibles et le grand nombre de ''materials'' possibles, c'était la seule voie possibles. Les programmeurs pouvaient programmer leurs propres algorithmes d'éclairage par sommet, même s'ils devaient aussi programmer les étapes de transformation et de projection. Mais nous détaillerons cela dans un chapitre dédié sur l'historique des GPUs.
Ce qui est important est que la Geforce 3 a introduit une fonctionnalité absolument cruciale pour le rendu 3D moderne : les '''''shaders'''''. Il s'agit de programmes informatiques exécutés par la carte graphique, qui servaient initialement à coder des algorithmes d'éclairage. D'où leur nom : ''shader'' pour ''shading'' (éclairage en anglais). Cependant, l'usage modernes des ''shaders'' dépasse le cadre des algorithmes d'éclairage.
L'avantage est que cela simplifie grandement l'implémentation des algorithmes d'éclairage. Pas besoin de les intégrer dans la carte graphique pour les utiliser, pas besoin d'un circuit distinct pour chaque algorithme. Sans ''shaders'', si la carte graphique ne gère pas un algorithme d'éclairage, on ne peut pas l'utiliser. À la rigueur, il est parfois possible de l'émuler avec des contournements logiciels, mais au prix de performances souvent désastreuses. Avec des ''shaders'', il est possible de programmer l'algorithme d'éclairage de notre choix, pour l'exécuter sur la carte graphique, avec des performances plus que convenables.
[[File:Implémentation de l'éclairage sur les cartes graphiques.png|vignette|Implémentation de l'éclairage sur les cartes graphiques]]
Il existe plusieurs types de ''shaders'', mais les deux principaux sont les '''''vertex shaders''''' et les '''''pixel shaders'''''. Les pixels ''shaders'' s'occupent de l'éclairage par pixel, leur nom est assez parlent. Les vertex ''shaders'' s'occupent de l'éclairage par sommet, mais aussi des étapes de transformation/projection. Je parle bien des trois étapes de transformation vues plus haut, qui effectuent des calculs de transformation de coordonnées avec des matrices. La raison à cela est que les calculs de transformation ressemblent beaucoup aux calculs d'éclairage par sommet. Ils impliquent tous deux des calculs vectoriels, comme des produits scalaires et des produits vectoriels, qui agissent sur des sommets/triangles. Si la carte graphique incorpore un processeur de ''shader'' capable de faire de tels calculs, alors il peut servir pour les deux.
Pour implémenter les ''shaders'', il a fallu ajouter des processeurs à la carte graphique. Les processeurs en question exécutent les ''shaders'', ils peuvent lire ou écrire dans des textures, mais ne font rien d'autres. Les ''vertex shaders'' font tout ce qui a trait à la géométrie, ils remplacent l'unité de T&L. Les pixels ''shaders'' sont entre la rastérisation et les ROPs, ils sont très liés à l'unité de texture.
{|class="wikitable"
|-
! colspan="4" | Cartes accélératrices PC, avant l'arrivée des ''shaders''
|-
| rowspan="2" class="f_rouge" | ''Vertex shader''
| rowspan="2" | Rastérisation
| Placage de textures
| rowspan="2" |''Raster Operations Pipeline''
|-
| class="f_rouge" | ''Pixel shader''
|}
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=Les cartes d'affichage des anciens PC
| prevText=Les cartes d'affichage des anciens PC
| next=Avant les GPUs : les cartes accélératrices 3D
| nextText=Avant les GPUs : les cartes accélératrices 3D
}}{{autocat}}
</noinclude>
g73kxqzl0bpuidvkc3pv1l6m56mo09v
Les cartes graphiques/La mémoire unifiée et la mémoire vidéo dédiée
0
80571
774461
764885
2026-10-08T14:28:18Z
Mewtow
31375
/* Les GPU intégrés */
774461
wikitext
text/x-wiki
Pour rappel, il existe deux types de cartes graphiques : les cartes dédiées et les cartes intégrées. Les '''cartes graphiques dédiées''' sont des cartes graphiques branchées sur des connecteurs/ports de la carte mère. A l'opposé, tous les processeurs modernes intègrent une carte graphique, appelée '''carte graphique intégrée''', ou encore '''IGP''' (''Integrated Graphic Processor''). En somme, les cartes dédiées sont opposées à celles intégrées dans les processeurs modernes.
Les cartes graphiques dédiées ont de la mémoire vidéo intégrée à la carte graphique, sauf pour quelques exceptions dont on parlera plus tard. Par contre, les IGP n'ont pas de mémoire vidéo dédiée, vu qu'on ne peut pas intégrer beaucoup de mémoire dans un processeur. Et cela permet de classer les cartes graphiques en deux types :
* Les cartes graphiques à '''mémoire vidéo dédiée''', à savoir que la carte graphique dispose de sa propre mémoire rien qu'à elle, séparée de la mémoire RAM de l'ordinateur. On fait alors la distinction entre ''RAM système'' et ''RAM vidéo''.
* Les cartes graphiques à '''mémoire unifiée''', où la mémoire RAM est partagée entre le processeur et la carte graphique. Le terme "unifiée" sous-entend que l'on a unifié la mémoire vidéo et la mémoire système (la RAM).
[[File:Répartition de la mémoire entre RAM système et carte graphique.png|centre|vignette|upright=2.5|Répartition de la mémoire entre RAM système et carte graphique]]
La distinction "mémoire dédiée versus unifiée" correspond de très près à la distinction "GPU dédié versus IGP". Dans la grosse majorité des cas, les cartes vidéos dédiées ont une mémoire dédiée, alors que les IGP utilisent la mémoire unifiée. Mais il existe de rares exceptions où une carte vidéo dédiée utilise la mémoire unifiée. Par exemple, la toute première carte graphique AGP, l'Intel 740, ne possédait pas de mémoire vidéo proprement dite, juste un simple ''framebuffer''. Tout le reste, texture comme géométrie, était placé en mémoire système ! Les performances étaient ridicules, mais les cartes étaient peu chères du fait de l’absence de VRAM, ce qui explique que l'Intel 740 a eu un petit succès sur les ordinateurs d'entrée de gamme.
Une autre exception est celle des GPU soudés sur la carte mère, utilisées sur certaines consoles de jeu, ainsi que certains PC portables puissants destinés aux ''gamers''. Pour ces dernières, il est possible d'utiliser aussi bien de la mémoire dédiée que de la mémoire unifiée. Si la plupart des consoles récents utilisent de la VRAM dédiées, d'anciennes consoles de jeu avec un GPU soudé utilisaient une mémoire unifiée, comme la Nintendo 64, pour ne citer qu'elle.
{|class="wikitable"
|-
!
! Mémoire unifiée
! Mémoire dédiée
|-
! GPU dédié
| Quelques rares GPU d'entrée de gamme || Tous les GPU, sauf de rares exceptions
|-
! GPU intégré
| Systématique ||
|-
! GPU soudé
| Anciennes consoles de jeu || Ordinateurs portables modernes
|}
==La mémoire vidéo dédiée==
La mémoire dédiée est nécessaire pour stocker l'image à afficher à l'écran, mais aussi pour mémoriser temporairement des informations importantes. Dans le cas le plus simple, elle sert simplement de ''Framebuffer'' : elle stocke l'image à afficher à l'écran. Au fil du temps, elle s'est vu ajouter d'autres fonctions, comme stocker les textures et les sommets de l'image à calculer, ainsi que divers résultats temporaires.
: Dans ce qui suit, la mémoire vidéo des cartes graphiques dédiées sera appelée la VRAM.
===Les mémoires GDDR et autres VRAM===
La VRAM ressemble aux barrettes de RAM qu'on trouve dans nos PC, à quelques différences près. Le point le plus important est qu'elle n'est pas présente sous la forme de barrettes de mémoire. À la place, les puces de mémoire sont soudées sur la carte graphique, sur son PCB vert. La conséquence est que l'on ne peut pas upgrader la VRAM d'une carte vidéo. Le fait que la VRAM est soudée simplifie la conception de la carte graphique, mais a aussi des avantages au niveau électrique et donc en termes de performance.
Les cartes graphiques des années 90-2000, utilisaient la même mémoire que celle des barrettes de RAM, à savoir des DRAM de type FPM, EDO, ou SDRAM (''Synchronous DRAM''). De nos jours, les barrettes de RAM utilisent de la mémoire dite DDR (''Double Data Rate''), alors que les VRAM sont des RAM dites GDDR (''Graphic Double Data Rate''). Les deux se ressemblent beaucoup, mais il y a de petites différences techniques qui sont trop complexes pour être expliquées ici.
: Quelques cartes graphiques ont utilisé de la mémoire non-DDR, comme les GPU ''Laguna'' de ''Cirrus Logic'', qui utilisaient de la RDRAM de feu Rambus, une mémoire RAM qui a utilisée comme RAM système sur certains PC et sur la Nintendo 64. Citons aussi les cartes graphiques ''Matrox Millennium'' et ''ATI 3D Rage Pro'', qui utilisaient de la ''Window DRAM'' (WRAM). Idem avec
Niveau performance, la GDDR se distingue de la DDR simple par une bande passante élevée, proche de la centaine de gigaoctets par secondes sur les GPU modernes. Mais il y a une contrepartie : un temps d'accès très long, de plusieurs centaines de cycles d'horloge. Concrètement, si je veux lire une texture, entre le moment où j'envoie une demande de lecture à la mémoire vidéo, et le moment celle-ci me renvoie les premiers texels, il va se passer entre 200 à 1000 cycles d'horloge GPU. Par contre, une fois les premiers texels reçus, les texels suivants sont disponibles au cycle suivant, et ainsi de suite. En clair, les données lues mettent du temps avant d'arriver, mais elles arrivent par gros paquets.
Les GPU récents sont des monstres de bande passante et ce n'est pas qu'à cause de la GDDR. Une puce mémoire de DDR/GDDR peut lire/écrire 64 bits par cycle d'horloge. Mais les GPU connectent la GDDR de manière à additionner la bande passante de plusieurs puces. Cela implique que chaque puce de RAM a sa propre connexion au GPU, ce qui permet d’accéder à plusieurs puces en parallèle, en même temps. Les GPU actuels sont capables de lire/écrire 192, 256, 384, voire 512 bits par cycle d'horloge. Les techniques de ''dual channel'' permettent de faire la même chose avec la RAM système, mais n’atteignent que 128 bits par cycle - 192/256 bits avec des techniques de ''triple/quad channel''. En clair, le bus mémoire de la GDDR permet de lire/écrire plus de données par cycle d'horloge qu'une RAM système, de 2 à 8 fois plus.
[[File:Puces mémories d'un GPU et d'une barrette de mémoire.png|centre|vignette|upright=2|Puces mémoires d'un GPU et d'une barrette de mémoire.]]
La différence entre débit et temps d'accès est primordiale sur les GPU modernes comme anciens. Toute l'architecture de la carte graphique est conçue de manière à prendre en compte ce temps d'attente. Les techniques employées sont multiples, et ne sont pas inconnues à ceux qui ont déjà lu un cours d'architecture des ordinateurs : mémoire caches, hiérarchie de caches, ''multithreading'' matériel au niveau du processeur, optimisations des accès mémoire comme des ''Load-Store Queues'' larges, des ''coalesing write buffers'', etc. Mais toutes ces techniques sont techniquement incorporées dans les processeurs de ''shaders'' et dans les circuits fixes. Aussi nous ne pouvons pas en parler dans ce chapitre. À une exception près : l'usage de caches et de ''local stores''.
===Les transferts ''Direct Memory Access''===
Pour échanger des données entre la RAM et la mémoire vidéo, les GPU utilisent la technologie ''Direct Memory Access'', aussi appelée DMA. Elle permet à un périphérique de lire/écrire un bloc de mémoire RAM, sans intervention du processeur, par l'intermédiaire du bus PCI Express.
Pour cela, le GPU intègre un circuit dédié à la gestion des transferts DMA, appelé le '''contrôleur DMA''', qui lit des données en RAM système pour les copier en RAM vidéo (ou inversement, mais c'est plus rare). Précisément, le contrôleur DMA copie un bloc de mémoire, de données consécutives en mémoire, par exemple un bloc de 128 mégaoctets, un bloc de 64 kiloctets, ou autre. Le processeur configure le contrôleur DMA en lui indiquant l'adresse de départ du bloc de mémoire, sa taille, et quelques informations annexes. Le contrôleur DMA lit alors les données une par une, et les écrit dans la mémoire vidéo.
===La mémoire vidéo est mappée dans l'espace d'adressage du CPU===
Les GPU doivent souvent échanger des données avec le processeur. Des données doivent être copiées de la mémoire RAM vers la VRAM. De plus, le CPU peut aussi adresser directement la VRAM. Pour cela, une partie de l'espace d'adressage peut être détourné pour communiquer avec les périphériques, grâce à la technique des '''entrées-sorties mappées en mémoire'''. Et c'est ce qui est fait pour le GPU : une partie des adresses est détournée vers le GPU. Typiquement un bloc d'adresse de la même taille que la mémoire vidéo est détournée : elles n'adressent plus de la RAM, mais la directement la mémoire vidéo de la carte graphique. En clair, le processeur voit la mémoire vidéo et peut lire ou écrire dedans directement.
[[File:Espace d'adressage classique avec entrées-sorties mappées en mémoire.png|centre|vignette|upright=2|Espace d'adressage classique avec entrées-sorties mappées en mémoire]]
Intuitivement, on se dit que toute la mémoire vidéo est visible par le CPU, mais le bus PCI, AGP ou PCI Express ont leur mot à dire. Le bus PCI permettait au CPU d'adresser une fenêtre de 256 mégaoctets de VRAM maximum, en raison d’une sombre histoire de configuration des ''Base Address Registers'' (BARs). Les registres BAR étaient utilisés pour gérer les transferts DMA, mais aussi pour l'adressage direct.
Le PCI Express était aussi dans ce cas avant 2008. La gestion de la mémoire vidéo était alors difficile, mais on pouvait adresser plus de 256 mégaoctets, en déplaçant la fenêtre de 256 mégaoctets dans la mémoire vidéo. Après 2008, la spécification du PCI-Express ajouta un support de la technologie ''resizable bar'', qui permet au processeur d’accéder directement à plus de 256 mégaoctets de mémoire vidéo, voire à la totalité de la mémoire vidéo.
==La mémoire unifiée==
Avec la mémoire unifiée, une partie de la RAM système est détournée pour servir de mémoire vidéo. La quantité d'adresses détournées est généralement réglable avec un réglage dans le BIOS. On peut ainsi choisir d'allouer 64, 128 ou 256 mégaoctets de mémoire système pour la carte vidéo, sur un ordinateur avec 4 gigaoctets de RAM. Les GPU modernes sont plus souples et fournissent deux réglages : une quantité de RAM vidéo minimale et une quantité de RAM maximale que le GPU ne peut pas dépasser. Par exemple, il est possible de régler le GPU de manière à ce qu'il ait 64 mégaoctets rien que pour lui, mais qu'il puisse avoir accès à maximum 1 gigaoctet s'il en a besoin. Cela fait au total 960 mégaoctets (1024-64) qui peut être alloués au choix à la carte graphique ou au reste des programmes en cours d’exécution, selon les besoins.
[[File:Partage de la mémoire unifiée entre CPU et GPU.png|centre|vignette|upright=2|Répartition de la mémoire entre RAM système et carte graphique]]
===Les GPU dédiés avec mémoire unifiée===
Il existe des cartes dédiées qui utilisent pourtant la mémoire unifiée, par exemple l'Intel 740. Pour lire en mémoire RAM, elles doivent passer par l'intermédiaire du bus AGP, PCI ou PCI-Express. Et ce bus est très lent, bien plus que ne le serait une mémoire vidéo normale. Aussi, les performances sont exécrables. J'insiste sur le fait que l'on parle des cartes graphiques dédiées, mais pas des cartes graphiques soudées des consoles de jeu.
D'ailleurs, de telles cartes dédiées incorporent un ''framebuffer'' directement dans la carte graphique. Il n'y a pas le choix, le VDC de la carte graphique doit accéder à une mémoire suffisamment rapide pour alimenter l'écran. Ils ne peuvent pas prendre le risque d'aller lire la RAM, dont le temps de latence est élevé, et qui peut potentiellement être réservée par le processeur pendant l’affichage d'une image à l'écran.
===Les GPU intégrés===
Les GPU intégrés ou soudés accèdent à la RAM par les mêmes voies que le CPU. Les GPU et le processeur sont reliés à la RAM par un bus dédié appelé le '''bus mémoire'''. Il existe même sans GPU intégré : tous les processeurs sont reliés à la RAM par un tel bus. Si le CPU intègre un IGPU, il greffe l'IGPU sur ce bus mémoire existant.
Un défaut de cette approche est que le débit du bus système est partagé entre le GPU et le processeur. En clair, si le bus mémoire peut transférer 20 gigas de données par secondes, il faudra partager ces 20 gigas/seconde entre CPU et GPU. Par exemple, le CPU aura droit à 15 gigas par secondes, le GPU seulement 5 gigas. Divers circuits d'arbitrage s'occupent de répartir équitablement le bus système, selon les besoins, mais ça reste un compromis imparfait.
[[File:Echanges de données entre CPU et GPU avec une mémoire unifiée.png|centre|vignette|upright=2|Connexion du bus mémoire au CPU et à un GPU soudé sur la carte mère.]]
N'allez cependant pas croire que les GPU intégrés n'ont que des désavantages. Ils disposent d'un avantage bien spécifique : la '''''zero-overhead copy''''', terme barbare mais qui cache une réalité très simple. Avec une mémoire dédiée, le processeur doit copier des données de la RAM système dans la RAM vidéo. Les copies en question se font souvent avant de démarrer le rendu 3D, par exemple lors du chargement d'un niveau dans un jeu vidéo. Elles peuvent aussi se faire lors du rendu, bien que ce soit plus rare. Et ces copies ont un cout en performance. Par contre, avec la mémoire unifié, pas besoin de faire de copie d'une mémoire à l'autre. Le processeur envoie juste une commande du GPU, qui indique l'adresse des données en RAM, leur position dans la RAM.
Le processeur et le GPU intègrent des mémoires caches, afin de réduire l'usage du bus mémoire. Et l'intégration des caches avec un GPU intégré est assez intéressante. Le CPU et l'IGPU peuvent partager certains caches. Par exemple, les processeurs Skylake et Sandy Bridge d'Intel, le CPU et GPU avaient leurs propres caches L2 et L1, mais ils partageaient le cache L3. En général, le partage ne touche que le cache de dernier niveau, le cache le plus gros et le plus proche de la mémoire, à savoir le cache L3 ou L4.
Sur les processeurs modernes, le CPU et le GPU ont leur propre cache L3/L4, afin avoir des caches plus spécialisés. Les caches L3/L4 sont plus gros pour le processeur que pour le GPU, ils vont à des fréquences différentes, sont alimentés par des tensions différentes, etc. De plus, le cache du CPU est optimisé pour un temps d'accès faible, alors que celui du GPU est optimisé pour un fort débit mémoire. Utiliser des caches séparés entraine des problèmes de cohérence des caches, qui ont été vus dans le chapitre sur les caches des processeurs de shaders.
[[File:Caches sur un iGPU.png|centre|vignette|upright=2.5|Caches sur un iGPU]]
===Les GPU des consoles===
Les consoles de jeu utilisent souvent la mémoire unifiée, même les consoles récentes. Pour les consoles de salon, les consoles Nintendo ont été les pionnières de la mémoire unifiée, XBOX l'a adopté dès le début, et les Playstation ont été les dernières à l'utiliser.
* Pour les consoles de 5ème génération, Nintendo 64 utilisait déjà la mémoire unifiée, alors que les autres consoles de l'époque faisaient avec une mémoire vidéo dédiée.
* Pour la 6ème génération, la Gamecube et la Xbox utilisaient la mémoire unifiée, pas la Playstation 2. A partir de la génération suivante, la mémoire unifiée est devenue la solution dominante.
* Pour la 7ème génération, seule la Playstation 3 n'utilisait pas la mémoire unifiée, mais XBOX et la Wii le faisait.
* A partir de la 8ème génération, toutes les consoles utilisent la mémoire unifiée.
Il faut noter que la mémoire utilisée sur ces consoles est une mémoire GDDR, soit le type de mémoire utilisé pour les mémoires vidéo dédiées ! Ce ne sont pas des mémoires DDR normales, optimisées pour fonctionner avec des CPU. Faire ainsi pose plus de problèmes pour le CPU que le GPU, mais ce n'est pas un problème sur une console. Les jeux vidéos sont plus gourmands pour le GFPU que pour le CPU, ce qui fait qu'il vaut mieux privilégier le GPU niveau performance. Ainsi, il vaut mieux utiliser de la mémoire GDDR, qui a une meilleure bande passante et des latences pas terribles. Le GPU sera donc avec une mémoire adaptée, quitte à détériorer les performances du CPU qui préfère les faibles latences.
Les GPU des consoles doivent s'adapter à cette mémoire unifiée, histoire de conserver de bonnes performances. Les limitations de bande passante se manifestent les GPU des consoles sont adaptés pour limiter la casse. Aussi, les GPU des consoles ne sont pas exactement les mêmes que ceux des PC, et qu'il existe des toutes petites différences. Par exemple, le GPU de la XBOX est une Geforce 3 légèrement modifiée, en partie pour s'adapter à la mémoire unifiée.
Une adaptation possible est simplement d'ajouter des mémoires caches, ou d'agrandir les mémoires caches existantes. Par exemple, il est possible de doubler le cache de textures ou le cache de sommets. Les GPU des consoles ne s'en sont pas privé, leurs caches sont généralement plus importants que les GPU dédiés équivalents.
Une seconde adaptation a été utilisée sur la Xbox 360, la Xbox One, la Gamecube, la Wii, la WiiU. L'idée était d'utiliser une mémoire dédiée pour le ''framebuffer'' et le tampon de profondeur (ainsi que le tampon de stencil, que nous n'avons pas encore abordé). Mettre à jour le ''framebuffer'' pendant le rendu est responsable d'une grande quantité d'accès mémoire, et c'est encore pire pour le tampon de profondeur. Si on met les deux dans une mémoire dédiée, on économise beaucoup de bande passante.
: Vous aurez peut-être fait le rapprochement avec les GPU à rendu en ''tile'', qui suivent une logique similaire, sauf qu'ils rendent l'image finale morceau par morceau. Ici, pas de système de ''tile''. Les GPU de ce type ont bien une SRAM, mais elle est plus petite et mémorise juste une ''tile'' de petite taille, qui correspond à un morceau de l'image finale, idem pour le ''frambuffer''.
La Xbox 360 utilisait une mémoire EDRAM de 10 Mégaoctets, la Xbox One utilisait 32 mégaoctets de mémoire ESRAM. La Gamecube et la Wii utilisaient 3 mégaoctets de mémoire EDRAM, la WiiU utilisait 32 mégaoctets d'EDRAM. Et mine de rien, ce n'est pas beaucoup. Une image en résolution 1920 par 1080 utilise déjà 8 mégaoctets, ce qui rentre tout juste dans l'EDRAM de la Xbox 360, à condition de ne pas utiliser d'antialiasing. Pour des résolutions usuelles à l'époque, comme le 1280 par 720, ca ne rentre pas si on utilise de l'antialiasing.
Et c'est sans doute pour cette raison que la technique a été abandonnée par la suite. La mémoire RAM nécessaire pour que la technique fonctionne demanderait une mémoire plus grosse, qui utiliserait plus de transistors. Et ce budget en transistor peut être utilisé pour autre chose, ce qui améliorerait les performances. A quoi bon placer une EDRAM dédiée aux ''render target'', alors qu'on peut avoir du cache en remplacement qui améliorera les performances générales ? Pas étonnant que les GPU modernes préfèrent utiliser des GDDR avec une bande passante énorme, ainsi que des mémoires caches de très grande capacité.
==La mémoire virtuelle des GPUs==
Pour commencer, parlons d'un point important, à savoir l'espace d'adressage du GPU. L''''espace d'adressage''' d'un processeur est l'ensemble des adresses utilisables par le processeur. Par exemple, un processeur 16 bits peut adresser 2^16 = 65536 adresses, l'ensemble de ces adresses forme son espace d'adressage. L'espace d'adressage n'est pas toujours égal à la mémoire réellement installée. S'il n'y a pas assez de RAM installée, des adresses seront inoccupées.
L'espace d'adressage du CPU est quelque peu particulier. Depuis les années 80-90, les logiciels n'adressent pas la mémoire RAM directement. Tous les processeurs gèrent un système de mémoire virtuelle, qui donne l'illusion aux programmes d'avoir accès à toute la mémoire adressable, tout l'espace d'adressage. Par exemple, sur les systèmes 32 bits, chaque programme a accès à tout l'espace d'adressage de 4 gigaoctets, et il ne voit pas les autres programmes dedans. C'est une illusion qui a de nombreux avantages : les programmes sont isolés les uns des autres, un programme ne peut pas lire dans la mémoire d'un autre, les programmes n'ont pas à se soucier de la quantité de RAM installée dans l'ordinateur, etc.
Par contre, cela implique que les adresses manipulées par les programmes sont des adresses fictives, qui doivent être traduites en adresses physiques. Les données situées à une adresse dans l'espace d'adressage ne sont pas à la même adresse en RAM. On fait ainsi la distinction entre adresse physique et logique : les adresses logiques sont les fausses adresses, les adresses physiques sont les vraies adresses en RAM.
Rien de tout cela ne devrait vous surprendre si vous avez déjà lu un cours d'architecture des ordinateurs. Maintenant, passons à quelque chose auquel vous n'avez peut-être pas pensé : qu'en est-il du GPU ? La réponse est que non seulement cela dépend de si le GPU est dédié ou intégré, mais que cela dépend aussi de si le GPU est ancien ou non. Les anciens GPU ne géraient pas la mémoire virtuelle, les nouveaux ont leur propre système de mémoire virtuelle séparé du CPU !
===L'abstraction mémoire des GPU===
Cependant, cela pose un léger problème : la communication avec les programmes et les API graphiques est perturbée. Le pilote de périphérique reçoit des adresses virtuelles de la part des applications, mais il doit gérer la mémoire vidéo en utilisant des adresses physiques. Et cela posait de nombreux problèmes pour les pilotes de GPU.
Le cas le plus facile à expliquer est celui des GPU dédiés. Le pilote de GPU alimente la mémoire vidéo avec des transferts DMA et le contrôleur DMA doit être configuré avec des adresses physiques pour faire son travail. Or, les API 3D leur donnaient des adresses virtuelles, pour localiser les textures, ''mesh'', shaders compilés et autres. Et c'est à eux et au système d'exploitation de traduire ces adresses en adresses physiques. Des cas similaires apparaissent sur des GPU intégrés.
Sous Windows, du temps de l'ancien ''Windows Display Driver Model'' (WDDM), c'était le système d’exploitation qui gérait cela. Le WDDM déportait une partie de la gestion du GPU dans le noyau de l'OS, dont la gestion de la mémoire vidéo, vu que seul lui pouvait faire la traduction d'adresse (il a accès au tables des pages). Le pilote de GPU envoyait les commandes matérielles au noyau de l'OS, qui les patchait, en remplaçant les adresses virtuelles par des adresses physiques. Le pilote envoyait alors les commandes traduites au GPU.
Avec le WDDM 2.0, les GPU supportent maintenant la mémoire virtuelle. Cependant, il faut noter que les GPU utilisent une mémoire virtuelle séparée de celle du CPU. Le processeur et le GPU ont deux espaces d'adressage différents. Concrètement, une adresse virtuelle n'adresse pas la même adresse physique selon qu'elle est utilisée par le GPU. De même, la même adresse physique correspondra à des adresses virtuelles différentes entre CPU et GPU. Aussi, on parle de '''mémoire virtuelle GPU''' pour la distinguer de la mémoire virtuelle du processeur.
[[File:Desktop computer bus bandwidths.svg|centre|vignette|upright=2|Séparation de la mémoire virtuelle du CPU et du GPU.]]
Même si la mémoire virtuelle du GPU n'est pas celle du CPU, cela simplifie grandement la gestion de la mémoire vidéo. Le pilote de GPU peut travailler directement avec des adresses virtuelles du GPU, et gérer la mémoire vidéo de lui-même, dans avoir à déléguer une partie du travail au noyau de l'OS. Plus besoin de traduire les adresses en adresses physiques, c'est le GPU qui s'en charge de lui-même ! D'ailleurs, cet avantage fait que la mémoire virtuelle GPU est aussi utilisée avec des GPU intégré, qui n'ont pas besoin de faire de copies entre RAM système et RAM vidéo.
[[File:Integrated graphics with distinct memory allocation.svg|centre|vignette|upright=2|Séparation de la mémoire virtuelle CPU et GPU, avec un GPU intégré.]]
Pour gérer la mémoire virtuelle, les GPU intègrent un circuit appelé la '''''Graphics address remapping table''''', abrévié en GART. La GART est techniquement une une ''Memory Management Unit'' (MMU), à savoir un circuit spécialisé qui s'occupe de traduire les adresses virtuelles en adresses physiques, elle prend en charge la mémoire virtuelle. La dite MMU étant intégrée dans un périphérique d'entrée-sortie (IO), ici la carte graphique, elle est appelée une IO-MMU (''Input Output-MMU''). Toutes les cartes graphiques utilisant les bus AGP ou PCI-Express intégrent une GART/IO-MMU.
La GART est configurée par le pilote de GPU, afin de gérer la mémoire virtuelle au mieux. Et cela explique que la mémoire virtuelle du GPU et du CPU soient séparées. Le pilote de GPU coopére avec le système d'exploitation pour configurer la GART, mais cette coopération ne peut pas être parfaite. On ne peut pas permettre au pilote de GPU d'avoir accès à la table des pages ou d'autres structures impliquées dans la traduction d'adresse : le risque de sécurité est trop grand. Avoir deux mémoires virtuelles séparées aide grandement le travail du pilote de GPU.
Cependant, quelques technologies permette au CPU et au GPU d'utiliser le même espace d'adressage, de fusionner la mémoire virtuelle du CPU et du GPU. Par exemple, la technologie ''Heterogeneous System Architecture'' permet cela, entre autres fonctionnalités. Elles permettent de synchroniser la MMU du processeur et l'IO-MMU du GPU.
{|
|[[File:HSA-enabled virtual memory with distinct graphics card.svg|vignette|upright=2|HSA-enabled virtual memory with distinct graphics card]]
|[[File:HSA-enabled integrated graphics.svg|vignette|upright=2|HSA-enabled integrated graphics]]
|}
===La mémoire virtuelle des GPU dédiés===
Pour rappel, la mémoire virtuelle permet à un CPU d'utiliser plus de RAM qu'il n'y en a d'installée dans l'ordinateur. Par exemple, elle permet au CPU de gérer 4 gigas de RAM sur un ordinateur qui n'en contient que trois, le gigaoctet de trop étant en réalité simulé par un fichier sur le disque dur. La technique est utilisée par tous les processeurs modernes.
La mémoire virtuelle des GPUs dédiés fait la même chose, sauf que le surplus d'adresses n'est pas stockés sur le disque dur dans un fichier pagefile, mais est dans la RAM système. Pour le dire autrement, ces cartes dédiées peuvent utiliser la mémoire système si jamais la mémoire vidéo est pleine. Cela permet au GPU d'adresser plus de RAM qu'en a la mémoire vidéo. Par exemple, si la carte vidéo a 2 giga-octets de RAM, la carte graphique peut être capable d'en adresser 8 : 2 gigas en RAM vidéo, et 6 autres gigas en RAM système. Le GPU peut lire directement des données en RAM, sans forcément les copier en mémoire vidéo.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
La technologie s'est démocratisée avec le bus AGP, dont la fonctionnalité dite d'''AGP texturing'' permettait de lire ou écrire directement dans la mémoire RAM, sans passer par le processeur. D'ailleurs, la carte graphique Intel i740 n'avait pas de mémoire vidéo et se débrouillait uniquement avec la mémoire système. C'est l'AGP qui a introduit le GART, la fameuse IO-MMU mentionnée plus haut.
L'arrivée du bus PCI-Express ne changea pas la donne, si ce n'est que le bus était plus rapide, ce qui améliorait les performances. Au début, seules les cartes graphiques PCI-Express d'entrée de gamme pouvaient accéder à certaines portions de la mémoire RAM grâce à des technologies adaptées, comme le TurboCache de NVIDIA ou l'HyperMemory d'AMD. Mais la technologie s'est aujourd'hui étendue. De nos jours, toutes les cartes vidéos modernes utilisent la RAM système en plus de la mémoire vidéo, mais seulement en dernier recours, soit quand la mémoire vidéo est quasiment pleine, soit pour faciliter les échanges de données avec le processeur. C'est typiquement le pilote de la carte graphique qui décide ce qui va dans la mémoire vidéo et la mémoire système, et il fait au mieux de manière à avoir les performances optimales.
===Les GPU utilisent la pagination===
Le GPU utilise la technique dite de la pagination, à savoir que la RAM système et la RAM du GPU sont découpées en pages de taille fixe, généralement 4 kilo-octets. Un GPU dédié échange des pages entre RAM système et mémoire vidéo. Lors d'un transfert DMA, la page en RAM système est copiée en mémoire vidéo. S'il n'y a pas assez de place en mémoire vidéo, le GPU rapatrie une page de mémoire vidéo vers la RAM système. La page rapatriée en RAM système est choisie par un algorithme spécialisé. Un GPU intégré n'a pas besoin de copie, il lit directement la page voulue en RAM système.
La traduction des adresses virtuelles en adresses physique se fait au niveau de la page. Une adresse est coupée en deux parts : un numéro de page, et la position de la donnée dans la page. La position dans la page ne change pas lors de la traduction d'adresse, mais le numéro de page est lui traduit. Le numéro de page virtuel est remplacé par un numéro de page physique lors de la traduction.
Pour remplacer le numéro de page virtuel en numéro physique, il faut utiliser une table de translation, appelée la '''table des pages''', qui associe un numéro de page logique à un numéro de page physique. Le système d'exploitation dispose de sa table des pages, qui n'est pas accesible au GPU. Par contre, le GPU dispose d'une sorte de mini-table des pages, qui contient les associations page virtuelle-physique utiles pour traiter les commandes GPU, et rien d'autre. En clair, une sorte de sous-ensemble de la table des pages de l'OS, mais spécifique au GPU. La mini-table des pages est gérée par le pilote de périphérique, qui remplit la mini-table des pages. La mini-table des pages est mémorisée dans une mémoire intégrée au GPU, et précisément dans la MMU.
===Le contrôleur DMA et l'IO-MMU des GPU dédiés===
Sur les GPU dédiés, le contrôleur DMA est souvent fusionné avec l'IO-MMU. En effet, il s'occupe de la copie des pages entre mémoire vidéo et RAM système. Le contrôleur DMA reçoit des adresses provenant du pilote de périphérique, qui sont des adresses virtuelles. Mais vu qu'il accède à la RAM système, il doit faire la traduction entre adresses virtuelles qu'il reçoit et adresses physiques qu'il émet, ce qui implique qu'il sert d'IO-MMU.
Même si la mémoire virtuelle GPU a été intégrée dans Windows assez tard, du temps de l'AGP et du PCI-Express, la technologie existait vraisemblablement sur certaines cartes graphiques au format PCI, même la documentation est assez rare. Seule certitude : la carte graphique NV1 de NVIDIA, leur toute première carte graphique, disposait d'un contrôleur DMA avec une IO-MMU intégrée. Le fonctionnement de cette IOMMU est décrite dans le brevet "US5758182A : DMA controller translates virtual I/O device address received directly from application program command to physical i/o device address of I/O device on device bus", des inventeurs David S. H. Rosenthal et Curtis Priem.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture du GPU NV1 de NVIDIA]]
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=Les caches d'un processeur de shader
| prevText=Les caches d'un processeur de shader
| next=Le rendu d'une scène 3D : l'API graphique
| netxText=Le rendu d'une scène 3D : l'API graphique
}}{{autocat}}
</noinclude>
gv6ll0ghje7wf4t0unisgwqh14qr6bf
Mathc initiation/a594
0
81090
774462
774440
2026-10-08T14:34:56Z
Xhungab
23827
774462
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec les séries de Fourier|Sommaire]]
[[File:Fourier serie for my wikibook.png|thumb|Fourier serie f(x) = x with x[0..Pi]]]
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPOQGZMpyjDEHjnf1wsQbDJG&si=mhV8SOb34ohQKvKm Playlist]].'''
En analyse mathématique, les séries de Fourier sont un outil fondamental dans l'étude des fonctions périodiques. C'est à partir de ce concept que s'est développée la branche des mathématiques connue sous le nom d'analyse harmonique. [https://fr.wikipedia.org/wiki/S%C3%A9rie_de_Fourier wikipedia]
.
'''Pour les définitions de a0, b0, an et bn j'utilise la [https://fr.wikipedia.org/wiki/S%C3%A9rie_de_Fourier#Coefficients_réels Première convention]'''
* <math> \mathbf{a_0(f)=\frac1T\int_Tf(t)\,\mathrm{d}t}</math>
* <math>\mathbf{b_0(f)=0}</math>
* '''pour''' <math>\mathbf{n>0}</math> : <math>\mathbf{a_n(f)=\frac2T\int_Tf(t)\cos\left(nt\frac{2\pi}T\right)\,\mathrm dt}</math>
* '''pour''' <math>\mathbf{n>0}</math> : <math>\mathbf{b_n(f)=\frac2T\int_Tf(t)\sin\left(nt\frac{2\pi}T\right)\,\mathrm dt}</math>
.
----
==Se familiariser avec les series de Fourier :==
Quelques exemples :
* [[Mathc initiation/a593|La fonction : f(x) = x ....................... avec x = [0..PI]]]
* [[Mathc initiation/a602|La fonction : f(x) = (x(x+1)) ........... avec x = [-1..1]]
==Calculons les amplitudes An et les phases φn==
* [[Mathc initiation/a9b|Présentation]]
{{AutoCat}}
hjw60gkjsvvpa85e8dzg9m7tiw1fzkt
774471
774462
2026-10-08T23:20:20Z
Xhungab
23827
774471
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec les séries de Fourier|Sommaire]]
[[File:Fourier serie for my wikibook.png|thumb|Fourier serie f(x) = x with x[0..Pi]]]
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPOQGZMpyjDEHjnf1wsQbDJG&si=mhV8SOb34ohQKvKm Playlist]].'''
En analyse mathématique, les séries de Fourier sont un outil fondamental dans l'étude des fonctions périodiques. C'est à partir de ce concept que s'est développée la branche des mathématiques connue sous le nom d'analyse harmonique. [https://fr.wikipedia.org/wiki/S%C3%A9rie_de_Fourier wikipedia]
.
'''Pour les définitions de a0, b0, an et bn j'utilise la [https://fr.wikipedia.org/wiki/S%C3%A9rie_de_Fourier#Coefficients_réels Première convention]'''
* <math> \mathbf{a_0(f)=\frac1T\int_Tf(t)\,\mathrm{d}t}</math>
* <math>\mathbf{b_0(f)=0}</math>
* '''pour''' <math>\mathbf{n>0}</math> : <math>\mathbf{a_n(f)=\frac2T\int_Tf(t)\cos\left(nt\frac{2\pi}T\right)\,\mathrm dt}</math>
* '''pour''' <math>\mathbf{n>0}</math> : <math>\mathbf{b_n(f)=\frac2T\int_Tf(t)\sin\left(nt\frac{2\pi}T\right)\,\mathrm dt}</math>
.
----
==Se familiariser avec les series de Fourier :==
Quelques exemples :
* [[Mathc initiation/a593|La fonction : f(x) = x ....................... avec x = [0..PI]]]
* [[Mathc initiation/a602|La fonction : f(x) = (x(x+1)) ........... avec x = [-1..1]]]
==Calculons les amplitudes An et les phases φn==
* [[Mathc initiation/a9b|Présentation]]
{{AutoCat}}
ephykba5or6rd78hdmy2uggdmgrrcnv
Mathc initiation/a605
0
81102
774473
774443
2026-10-09T11:03:54Z
Xhungab
23827
774473
wikitext
text/x-wiki
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a602| Sommaire]]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c1an.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ---------------------------------- */
/* save as c1an.c */
/* ---------------------------------- */
#include "x_hfile.h"
/* ---------------------------------- */
#include "f1.h"
/* ---------------------------------- */
int main(void)
{
double T = 2.;
double omega = (2.*PI/T);
double a = -1;
double b = +1;
double n = 1.; /* You can change the value of n */
double an = 2./T * fourier_an(f, a,b, omega, n);
clrscrn();
printf(" _+oo_ \n"
" \\ \n"
" S(t) = a0 + | an cos(omega* n*t) + bn sin(omega *n*t)\n"
" /_____ \n"
" n = 1 \n\n\n\n");
printf(" (a + T\n"
" a_%.0f = 2/T int( (%s) cos(%.0f omega t) dt = %.6f\n"
" (a \n\n\n\n",n, feq, n, an);
stop();
return 0;
}
/* ---------------------------------- */
/* ---------------------------------- */
</syntaxhighlight>
'''Exemple de sortie écran :'''
<syntaxhighlight lang="C">
_+oo_
\
S(t) = a0 + | an cos(omega* n*t) + bn sin(omega *n*t)
/_____
n = 1
(a + T
a_1 = 2/T int( ((x(x+1))) cos(1 omega t) dt = -0.405285
(a
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
qv7j6pon8g2myg6o9grdhfuea603wc9
Mathc initiation/a606
0
81103
774474
774445
2026-10-09T11:05:47Z
Xhungab
23827
774474
wikitext
text/x-wiki
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a602| Sommaire]]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c1bn.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ---------------------------------- */
/* save as c1bn.c */
/* ---------------------------------- */
#include "x_hfile.h"
/* ---------------------------------- */
#include "f1.h"
/* ---------------------------------- */
int main(void)
{
double T = 2.;
double omega = (2.*PI/T);
double a = -1;
double b = +1;
double n = 3.; /* You can change the value of n */
double bn = 2./T * fourier_bn(f, a,b, omega, n);
clrscrn();
printf(" _+oo_ \n"
" \\ \n"
" S(t) = a0 + | an cos(omega* n*t) + bn sin(omega *n*t)\n"
" /_____ \n"
" n = 1 \n\n\n\n");
printf(" (a + T\n"
" b_%.0f = 2/T int( (%s) sin(%.0f omega t) dt = %.6f\n"
" (a \n\n\n\n",n, feq, n, bn);
stop();
return 0;
}
/* ---------------------------------- */
/* ---------------------------------- */
</syntaxhighlight>
'''Exemple de sortie écran :'''
<syntaxhighlight lang="C">
_+oo_
\
S(t) = a0 + | an cos(omega* n*t) + bn sin(omega *n*t)
/_____
n = 1
(a + T
b_3 = 2/T int( ((x(x+1))) sin(3 omega t) dt = 0.212207
(a
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
49hl1frybo9nfi0p16f3krce8tpd599
Les cartes graphiques/Les systèmes à framebuffer
0
81123
774449
764901
2026-10-08T13:10:56Z
Mewtow
31375
/* Le codage des pixels dans le framebuffer */
774449
wikitext
text/x-wiki
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''==
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 ne compresse pas l'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===
À 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.
{|class="wikitable"
|-
! 1 bit
! 2 bit
! 4 bit
! 8 bit
|-
| [[File:Bilevel 1bit palette sample image.png]]
| [[File:Grayscale 2bit palette sample image.png]]
| [[File:Grayscale 4bit palette sample image.png]]
| [[File:Grayscale 8bits palette sample image.png]]
|}
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===
[[File:Indexed palette.svg|vignette|upright=0.5|Palette indicée. En haut, on a le ''framebuffer'', qui contient les couleurs codées par des nombres. La table de correspondance est donnée au milieu, et l'image finale en bas.]]
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''.
[[File:IBM16 Palette-en.svg|centre|vignette|upright=1|Palette de l'IBM16.]]
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'''.
[[File:Color-lookup-table.png|centre|vignette|upright=2|Color-lookup-table]]
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'''''.
<noinclude>
{|class="wikitable"
|+ Exemples d'animations obtenues avec du color Cycling
|-
! [[File:Mandelbrot Set Color Cycling Animation 400px.gif|150px|]]
! [[File:Color square cm.gif|150px|]]
! [[File:Mtree-spiral-11 nevit 25.gif|150px|]]
! [[File:-PLASMA-ColorCycling.Gif|150px|]]
|}
</noinclude>
===Le standard RGB et ses dérivés===
[[File:Barn grand tetons rgb separation.jpg|vignette|upright=0.5|Image codée en RGB : l'image est un mélange de trois images : une ne contenant que des nuances de rouge, une des nuances de vert, et la dernière uniquement des nuances de bleu.]]
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.
{|class="wikitable"
|-
! RGB 16 bits
! RG 16 bits
|-
|[[File:RGB 24bits palette sample image.jpg]]
|[[File:RG 16bits palette sample image.png]]
|}
==L'organisation du ''framebuffer''==
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===
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===
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.
[[File:Vector-06c-video-memory.svg|centre|vignette|upright=2|Exemple de ''framebuffer'' planaire, provenant de l'ordinateur soviétique Vector-06c.]]
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==
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''===
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''.
[[File:Double buffering.png|centre|vignette|upright=2|Double buffering]]
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===
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é.
[[File:Tearing (simulated).jpg|centre|vignette|upright=2|''Tearing'' (simulé)]]
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===
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.
[[File:Triple buffering.png|centre|vignette|upright=2|Triple buffering]]
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===
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==
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.
[[File:Architecture globale d'une carte d'affichage, avec CRTC.png|centre|vignette|upright=2|Architecture globale d'une carte d'affichage, avec CRTC.]]
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.
[[File:Architecture interne d'une carte d'affichage en mode graphique.png|centre|vignette|upright=2|Architecture interne d'une carte d'affichage en mode graphique]]
===Le pointeur de ''framebuffer''===
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.
[[File:Arraystatic.svg|vignette|Tableau bidimensionnel.]]
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.
[[File:CRTC et calcul d'adresse.png|centre|vignette|upright=2|CRTC et calcul d'adresse.]]
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.
[[File:CRTC et double buffering.png|centre|vignette|upright=2|Circuit de contrôle et double buffering]]
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'')==
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.
[[File:Speed Dreams splitscreen feature.png|centre|vignette|upright=2|Speed Dreams splitscreen feature]]
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===
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.
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=Le Video Display Controler
| prevText=Le Video Display Controler
| next=Les cartes accélératrices 2D
| nextText=Les cartes accélératrices 2D
}}{{autocat}}
</noinclude>
6fbvm8x1ghukxmxifhpd7gkxly49n0e
Les cartes graphiques/Les Video Display Controler atypiques
0
81131
774453
764865
2026-10-08T13:40:08Z
Mewtow
31375
/* Le rendu des sprites sur la borne d'arcade CPS-1 */
774453
wikitext
text/x-wiki
Nous avions vu dans le chapitre précédent qu'il existe plusieurs types de VDC. Dans les chapitres précédents, nous avons abordé les CRTC des systèmes à ''framebuffer'', ainsi que des VDC à tile et ceux à tampon de ligne. Dans ce chapitre, nous allons voir des VDC ou des systèmes graphiques originaux.
==Le rendu des ''sprites'' sur la borne d'arcade CPS-1==
La borne d'arcade CPS-1 avait une méthode rendu assez particulière. Contrairement aux VDC normaux, sa carte graphique rendait les ''sprites'' séparément. Elle avait un ''framebuffer'' dédié au rendu des ''sprite'', qui était combiné avec l'arrière-plan pour donner l'image finale. La carte graphique lisait les ''sprites'' depuis la cartouche de jeu et les plaçait dans ce second ''framebuffer'', pour donner une image sans l'arrière-plan. La combinaison se faisait au moment d'afficher l'image, lors du rendu d'une ligne à l'écran.
Un inconvénient de cette méthode est que le rendu du ''framebuffer'' pour les ''sprites'' se faisait pendant le rendu de l'image précédente. En clair, pendant que le GPU affichait une image à l'écran, le VDC accédait à la mémoire vidéo pour lire les ''sprites'' et calculer ce second ''framebuffer''. Le résultat était qu'il y avait une sorte de retard d'une ''frame'' pour les ''sprites'', donc un léger ''input lag''. Un second inconvénient est le cout en hardware. Le second ''framebuffer'' avait sa propre mémoire RAM séparée du reste, d'une capacité mémoire de 384 kilo-octets. Cela coutait cher, mais les bornes d'arcade de l'époque se moquaient un peu de ces considérations, vu leur prix élevé.
L'avantage est que les limitations en matière de ''sprite'' par ligne n'existent pas sur ce système. Les autres systèmes de l'époque devaient faire avec une limite de ''sprite'' par ligne, que les développeurs contournaient en éteignant certains ''sprites'' une image sur deux, donnant un effet de clignotement. De son côté, le CPS-1 avait juste une limite par image, et une limite par image assez élevée qui plus est. Les jeux sur CPS-1 n'avaient donc pas d'effets de clignotement. Par contre, les développeurs devaient faire avec la limitation de ''sprite'' par image. Autant cette limite n'était pas pertinente, grâce au ''raster effects'', autant elle limitait les graphismes sur la CPS-1 car le ''swap'' des ''sprites'' à la volée lors de l'affichage n'était pas possible. Pour résumer, la CPS-1 échangeait une limitation de ''sprite'' par ligne pour une limitation de ''sprite'' par image.
==Le double VDC de la SuperGrafx==
La console de jeu SuperGrafx est le successeur spirituel de la console PC-Engine, aussi appelée Turbo Graphx en occident. Elle a le même CPU, la même quantité de mémoire RAM, le même VDC 2D. Voici l'architecture de la Turbo Graphx, qui est assez simple. Un CPU, une puce sonore, un VDC, un circuit pour la palette et l'interface écran séparé du VDC, une RAM pour le CPU et une mémoire vidéo reliée au VDC. La ROM du système est dans la cartouche de jeu.
[[File:Turbo graphx.png|centre|vignette|upright=2.5|Turbo graphx]]
La Super Graphx a une grosse différence : elle a deux VDC identiques ! L'objectif des concepteurs de la console était de doubler la puissance graphique et la solution la plus simple était évidemment de dupliquer le VDC... La console avait donc deux VDC identique à ceux de la PC-Engine, à savoir deux HuC6270. La mémoire vidéo est elle aussi dupliquée, à savoir que chaque VDC dispose de sa propre mémoire vidéo de 64 kilo-octets.
Les deux VDC calculent une image séparée chacun de leur côté, puis les deux images sont fusionnées pour donner l'image finale. L'intérêt est que cela permet d'avoir deux fois plus d'arrière-plan et de ''sprite'', si la fusion est gérée correctement. La fusion est réalisée par un circuit séparé, appelé le VPC (''Video Priority Controler''). À chaque pixel de l'écran, il choisit si ce pixel provient du premier VDC ou du second. Le tout est géré par un système qui dit quel écran a la priorité, l'écran prioritaire étant codé sur un 1 bit par pixel. Le VPC gère aussi la palette indicée et le RAMDAC, il n'y a pas de VCE séparé comme sur la PC-Engine.
[[File:Super Graphx 01.png|centre|vignette|upright=2.5|Super Graphx]]
==Les ''Video shifters'' : des VDC sans mémoire vidéo associée==
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.
Les VDP les plus simples se passent de mémoire vidéo. Ils n'effectuent pas le rendu ligne par ligne, mais réellement pixel par pixel. Nous les appellerons des '''''Video shifters''''', un nom qui trahit le fait qu'ils reçoivent des données pixel par pixel, et les transforment en un flux de bit, voire un flux analogique (ils intègrent alors un RAMDAC). Ils ne peuvent pas envoyer de commandes/adresses à la mémoire vidéo, ils ne parcourent pas le ''framebuffer'' et ne lisent pas les pixels à envoyer à l'écran dedans, c'est le processeur qui le fait à leur place. Leur avantage est qu'ils se passent de mémoire vidéo dédiée et d'utiliser une mémoire unique pour le processeur et le système vidéo. Ils sont très rares, les seuls à avoir été utilisé sur un microordinateur sont le RCA CDP1861 du micro-ordinateur soviétique COSMAC VIP, et le système vidéo du Sinclair ZX-81 (bien qu'il soit fabriqué avec des portes logiques et non avec un VDP).
Pour résumer, voici les fonctions possibles de ces circuits :
* RAMDAC et transformation octet/pixel en flux de bits ;
* génération des signaux de commande pour l'écran ;
* génération des ''raster interrupts''.
Prenons l'exemple du RCA CDP1861. Il générait les signaux HSYNC et VSYNC de synchronisation verticale et horizontale pour l'écran, et transformait un octet reçu en flux de bits envoyés à l'écran (monochrome). L'intérieur de ce circuit était très simple : un registre à décalage pour la transformation parallèle-série, des compteurs pour la ligne et la colonne, quelques circuits de contrôle associés.
Le RCA CDP1861 était relié au processeur et à la mémoire comme indiqué ci-dessous. Le rendu d'une image se faisait en utilisant les ''raster interrupt''. Le RCA CDP1861 générait une interruption à chaque fin de ligne, grâce à un compteur de ligne interne. Le processeur affichait une image grâce à une routine d'interruption, aidé par un contrôleur DMA intégré dans le processeur. La routine d'interruption configurait le contrôleur DMA, qui s'occupait d'envoyer l'image au RCA CDP1861, octet par octet. Il y avait un signal de synchronisation entre RCA CDP1861 et processeur/DMA : un bit émis par le RCA CDP1861 à destination du processeur indiquait qu'il était prêt à recevoir un nouvel octet.
[[File:VDP shifter.png|centre|vignette|upright=2|VDP shifter RCA CDP1861]]
==Les fonctionnalités supplémentaires==
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.
Les VDC peuvent gérer des fonctionnalités qui dépassent de loin le rendu 2D. Ils peuvent gérer des fonctionnalités très diverses, allant de la gestion de la mémoire vidéo, l'intégration d'un co-processeur graphique, la gestion du clavier et de la souris, une carte son rudimentaire, la gestion des signaux d'horloge du processeur, des ''timers'' et bien d'autres. Faisons un petit tour d'horizon de ces fonctionnalités.
===Le contrôleur mémoire pour la RAM 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.
Un point important est que la mémoire vidéo est quasi-systématiquement 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 VDP les plus complexe peuvent incorporer des circuits pour effectuer ce rafraichissement automatiquement. Mais d'autres VDC plus simples font sans et ajoutent des circuits de rafraichissement mémoire séparés du VDC.
Et outre le rafraichissement, l'accès à une mémoire DRAM est plus complexe que l'accès aux autres mémoires RAM. Les timings des accès mémoire sont complexes, sans compter que l'adresse doit être envoyée en deux fois. Pour cela, un circuit appelé le contrôleur mémoire est nécessaire pour communiquer avec une DRAM. Le contrôleur traduit les adresses mémoires et signaux de commandes en commandes compréhensibles par la mémoire DRAM. Notamment, il reçoit une adresse mémoire et l'envoie en deux fois. Le contrôleur mémoire peut être intégré au VDC, ou être un circuit séparé, tout dépend du VDC en question.
===Les fonctionnalités liées aux périphériques===
Il existe des VIC qui 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).
[[File:Engineer working on CAD workstation with light pen Hughes Aircraft Company.jpg|vignette|Ingénieur de ''Hughes Aircraft Company'' travaillant sur un ordinateur de CAO avec un crayon optique.]]
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.
===Un exemple : le NEC μPD7220===
[[File:NECuPD7220BlockDiagram.svg|vignette|NECuPD7220BlockDiagram]]
Un 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''.
===L'exemple des anciens micro-ordinateurs Amiga===
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. Il est intéressant de donner quelques exemples réels de consoles de jeux qui utilisaient de l'accélération 2D. Nous allons voir l'exemple des anciens micro-ordinateurs Amiga.
[[File:Amiga Original Chipset diagram.svg|vignette|Amiga Original Chipset diagram]]
Pour finir ce chapitre, nous allons voir les 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.
Agnus contient un ''blitter'', un CRTC, et un co-processeur spécialisé qu'on détaillera plus bas. 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 temporels.
Agnus incorpore un circuit de gestion de la mémoire assez complexe. Il intégre notamment un contrôleur mémoire de DRAM, pour communiquer avec la mémoire, qui lui-même incorpore un circuit de rafraichissement mémoire. Tous les contrôleurs DRAM de l'époque ne géraient pas le rafraichissement mémoire, mais c'est le cas d'Agnus. Il n'a donc pas besoin de déléguer cette fonction au processeur. De plus, le circuit de gestion mémoire 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. Agnus alloue un cycle sur deux au ''CRTC'', le reste est alloué suivant les besoins, soit au CPU, soit au ''blitter''.
Le ''blitter'' des anciennes consoles Amiga fonctionne suivant trois modes de fonctionnement : copie mémoire, remplissage de polygone, et tracé de ligne. Leurs noms sont assez parlants. Le mode de tracé de ligne permet de tracer une ligne en utilisant l'algorithme de Bresenham. Les deux autres modes se ressemblent, dans le sens où ils font des écritures en mémoire, pour remplir un bloc entier de forme rectangulaire.
Le premier mode copie un bloc de mémoire dans un autre, mais peut aussi effectuer une opération logique entre plusieurs blocs. 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. Les quatre blocs peuvent se recouvrir partiellement. Les blocs en question sont rectangulaires et sont définis par les informations suivantes : une longueur mesurée en multiples de 16 bits, une largeur exprimée en nombre de lignes, et un ''stride'' qui indique la distance entre deux lignes dans le ''framebuffer''.
Pour ce qui est du remplissage de polygone, il s'agit du remplissage de figures géométriques simples avec une couleur uniforme. Les figures géométriques sont soit en forme de rectangles, soit des trapèzes dessinés à l'horizontale. 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. Il peut aussi remplir un polygone d'une couleur uniforme.
Enfin, Agnus contient un co-processeur spécialisé appelé '''Copper'''. Il s'agit plus d'une machine à état que d'un co-processeur, mais passons ce détail sous silence. Il exécute une ''display list'', à savoir une liste d'instructions, un programme. Les instructions en question sont de trois type : MOV, WAIT et SKIP. L'instruction MOV écrit dans les registres de configuration du VDC, ce qui lui permet d'initialiser des transferts DMA, de modifier les ''sprites'', etc. L'instruction WAIT attend que le l'écran en soit arrivé au rendu du pixel de coordonnée X,Y, ce qui peut remplacer partiellement les ''raster interrupts''. L'instruction SKIP est techniquement un branchement qui passe outre certaines instructions de la ''display list'' si l'écran a dépassé le pixel de coordonnée X,Y.
Le co-processeur Copper peut modifier les registres du ''blitter'', ce qui lui permet de démarrer des copies mémoire sans que le CPU ne soit impliqué. Sans lui, le CPU devrait configurer les registres du ''blitter'' pour initier une copie. Ici, le CPU n'a rien à faire : tout est précisé dans la ''display list''. Copper peut modifier les registres de ''sprites'' à la volée, il suffit de coder la ''display list'' adéquate. Copper peut aussi générer des ''raster interrupts'', encore que leur utilité soit ici moindre, vu que ''blitter'' et registres de ''sprites'' sont gérés par la ''display list''. Mais elles sont supportées pour gérer la synchronisation CPU-mémoire vidéo.
{{NavChapitre | book=Les cartes graphiques
| prev=Les accélérateurs de scanline
| prevText=Les accélérateurs de scanline
| next=Les cartes d'affichage des anciens PC
| nextText=Les cartes d'affichage des anciens PC
}}
{{autocat}}
fjox26aqes5i2ruyillgdsxajlltb3d
774454
774453
2026-10-08T13:40:43Z
Mewtow
31375
/* Le rendu des sprites sur la borne d'arcade CPS-1 */
774454
wikitext
text/x-wiki
Nous avions vu dans le chapitre précédent qu'il existe plusieurs types de VDC. Dans les chapitres précédents, nous avons abordé les CRTC des systèmes à ''framebuffer'', ainsi que des VDC à tile et ceux à tampon de ligne. Dans ce chapitre, nous allons voir des VDC ou des systèmes graphiques originaux.
==Le rendu des ''sprites'' sur la borne d'arcade CPS-1==
La borne d'arcade CPS-1 avait une méthode rendu assez particulière. Contrairement aux VDC normaux, sa carte graphique rendait les ''sprites'' séparément. Elle avait un ''framebuffer'' dédié au rendu des ''sprite'', qui était combiné avec l'arrière-plan pour donner l'image finale. La carte graphique lisait les ''sprites'' depuis la cartouche de jeu et les plaçait dans ce second ''framebuffer'', pour donner une image sans l'arrière-plan. La combinaison se faisait au moment d'afficher l'image, lors du rendu d'une ligne à l'écran.
Un inconvénient de cette méthode est que le rendu du ''framebuffer'' pour les ''sprites'' se faisait pendant le rendu de l'image précédente. En clair, pendant que le GPU affichait une image à l'écran, le VDC accédait à la mémoire vidéo pour lire les ''sprites'' et calculer ce second ''framebuffer''. Le résultat était qu'il y avait une sorte de retard d'une ''frame'' pour les ''sprites'', donc un léger ''input lag''. Un second inconvénient est le cout en hardware. Le ''framebuffer'' pour les ''sprites'' avait sa propre mémoire RAM séparée du reste, d'une capacité mémoire de 384 kilo-octets. Cela coutait cher, mais les bornes d'arcade de l'époque se moquaient un peu de ces considérations, vu leur prix élevé.
L'avantage est que les limitations en matière de ''sprite'' par ligne n'existent pas sur ce système. Les autres systèmes de l'époque devaient faire avec une limite de ''sprite'' par ligne, que les développeurs contournaient en éteignant certains ''sprites'' une image sur deux, donnant un effet de clignotement. De son côté, le CPS-1 avait juste une limite par image, et une limite par image assez élevée qui plus est. Les jeux sur CPS-1 n'avaient donc pas d'effets de clignotement. Par contre, les développeurs devaient faire avec la limitation de ''sprite'' par image. Autant cette limite n'était pas pertinente, grâce au ''raster effects'', autant elle limitait les graphismes sur la CPS-1 car le ''swap'' des ''sprites'' à la volée lors de l'affichage n'était pas possible. Pour résumer, la CPS-1 échangeait une limitation de ''sprite'' par ligne pour une limitation de ''sprite'' par image.
==Le double VDC de la SuperGrafx==
La console de jeu SuperGrafx est le successeur spirituel de la console PC-Engine, aussi appelée Turbo Graphx en occident. Elle a le même CPU, la même quantité de mémoire RAM, le même VDC 2D. Voici l'architecture de la Turbo Graphx, qui est assez simple. Un CPU, une puce sonore, un VDC, un circuit pour la palette et l'interface écran séparé du VDC, une RAM pour le CPU et une mémoire vidéo reliée au VDC. La ROM du système est dans la cartouche de jeu.
[[File:Turbo graphx.png|centre|vignette|upright=2.5|Turbo graphx]]
La Super Graphx a une grosse différence : elle a deux VDC identiques ! L'objectif des concepteurs de la console était de doubler la puissance graphique et la solution la plus simple était évidemment de dupliquer le VDC... La console avait donc deux VDC identique à ceux de la PC-Engine, à savoir deux HuC6270. La mémoire vidéo est elle aussi dupliquée, à savoir que chaque VDC dispose de sa propre mémoire vidéo de 64 kilo-octets.
Les deux VDC calculent une image séparée chacun de leur côté, puis les deux images sont fusionnées pour donner l'image finale. L'intérêt est que cela permet d'avoir deux fois plus d'arrière-plan et de ''sprite'', si la fusion est gérée correctement. La fusion est réalisée par un circuit séparé, appelé le VPC (''Video Priority Controler''). À chaque pixel de l'écran, il choisit si ce pixel provient du premier VDC ou du second. Le tout est géré par un système qui dit quel écran a la priorité, l'écran prioritaire étant codé sur un 1 bit par pixel. Le VPC gère aussi la palette indicée et le RAMDAC, il n'y a pas de VCE séparé comme sur la PC-Engine.
[[File:Super Graphx 01.png|centre|vignette|upright=2.5|Super Graphx]]
==Les ''Video shifters'' : des VDC sans mémoire vidéo associée==
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.
Les VDP les plus simples se passent de mémoire vidéo. Ils n'effectuent pas le rendu ligne par ligne, mais réellement pixel par pixel. Nous les appellerons des '''''Video shifters''''', un nom qui trahit le fait qu'ils reçoivent des données pixel par pixel, et les transforment en un flux de bit, voire un flux analogique (ils intègrent alors un RAMDAC). Ils ne peuvent pas envoyer de commandes/adresses à la mémoire vidéo, ils ne parcourent pas le ''framebuffer'' et ne lisent pas les pixels à envoyer à l'écran dedans, c'est le processeur qui le fait à leur place. Leur avantage est qu'ils se passent de mémoire vidéo dédiée et d'utiliser une mémoire unique pour le processeur et le système vidéo. Ils sont très rares, les seuls à avoir été utilisé sur un microordinateur sont le RCA CDP1861 du micro-ordinateur soviétique COSMAC VIP, et le système vidéo du Sinclair ZX-81 (bien qu'il soit fabriqué avec des portes logiques et non avec un VDP).
Pour résumer, voici les fonctions possibles de ces circuits :
* RAMDAC et transformation octet/pixel en flux de bits ;
* génération des signaux de commande pour l'écran ;
* génération des ''raster interrupts''.
Prenons l'exemple du RCA CDP1861. Il générait les signaux HSYNC et VSYNC de synchronisation verticale et horizontale pour l'écran, et transformait un octet reçu en flux de bits envoyés à l'écran (monochrome). L'intérieur de ce circuit était très simple : un registre à décalage pour la transformation parallèle-série, des compteurs pour la ligne et la colonne, quelques circuits de contrôle associés.
Le RCA CDP1861 était relié au processeur et à la mémoire comme indiqué ci-dessous. Le rendu d'une image se faisait en utilisant les ''raster interrupt''. Le RCA CDP1861 générait une interruption à chaque fin de ligne, grâce à un compteur de ligne interne. Le processeur affichait une image grâce à une routine d'interruption, aidé par un contrôleur DMA intégré dans le processeur. La routine d'interruption configurait le contrôleur DMA, qui s'occupait d'envoyer l'image au RCA CDP1861, octet par octet. Il y avait un signal de synchronisation entre RCA CDP1861 et processeur/DMA : un bit émis par le RCA CDP1861 à destination du processeur indiquait qu'il était prêt à recevoir un nouvel octet.
[[File:VDP shifter.png|centre|vignette|upright=2|VDP shifter RCA CDP1861]]
==Les fonctionnalités supplémentaires==
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.
Les VDC peuvent gérer des fonctionnalités qui dépassent de loin le rendu 2D. Ils peuvent gérer des fonctionnalités très diverses, allant de la gestion de la mémoire vidéo, l'intégration d'un co-processeur graphique, la gestion du clavier et de la souris, une carte son rudimentaire, la gestion des signaux d'horloge du processeur, des ''timers'' et bien d'autres. Faisons un petit tour d'horizon de ces fonctionnalités.
===Le contrôleur mémoire pour la RAM 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.
Un point important est que la mémoire vidéo est quasi-systématiquement 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 VDP les plus complexe peuvent incorporer des circuits pour effectuer ce rafraichissement automatiquement. Mais d'autres VDC plus simples font sans et ajoutent des circuits de rafraichissement mémoire séparés du VDC.
Et outre le rafraichissement, l'accès à une mémoire DRAM est plus complexe que l'accès aux autres mémoires RAM. Les timings des accès mémoire sont complexes, sans compter que l'adresse doit être envoyée en deux fois. Pour cela, un circuit appelé le contrôleur mémoire est nécessaire pour communiquer avec une DRAM. Le contrôleur traduit les adresses mémoires et signaux de commandes en commandes compréhensibles par la mémoire DRAM. Notamment, il reçoit une adresse mémoire et l'envoie en deux fois. Le contrôleur mémoire peut être intégré au VDC, ou être un circuit séparé, tout dépend du VDC en question.
===Les fonctionnalités liées aux périphériques===
Il existe des VIC qui 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).
[[File:Engineer working on CAD workstation with light pen Hughes Aircraft Company.jpg|vignette|Ingénieur de ''Hughes Aircraft Company'' travaillant sur un ordinateur de CAO avec un crayon optique.]]
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.
===Un exemple : le NEC μPD7220===
[[File:NECuPD7220BlockDiagram.svg|vignette|NECuPD7220BlockDiagram]]
Un 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''.
===L'exemple des anciens micro-ordinateurs Amiga===
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. Il est intéressant de donner quelques exemples réels de consoles de jeux qui utilisaient de l'accélération 2D. Nous allons voir l'exemple des anciens micro-ordinateurs Amiga.
[[File:Amiga Original Chipset diagram.svg|vignette|Amiga Original Chipset diagram]]
Pour finir ce chapitre, nous allons voir les 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.
Agnus contient un ''blitter'', un CRTC, et un co-processeur spécialisé qu'on détaillera plus bas. 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 temporels.
Agnus incorpore un circuit de gestion de la mémoire assez complexe. Il intégre notamment un contrôleur mémoire de DRAM, pour communiquer avec la mémoire, qui lui-même incorpore un circuit de rafraichissement mémoire. Tous les contrôleurs DRAM de l'époque ne géraient pas le rafraichissement mémoire, mais c'est le cas d'Agnus. Il n'a donc pas besoin de déléguer cette fonction au processeur. De plus, le circuit de gestion mémoire 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. Agnus alloue un cycle sur deux au ''CRTC'', le reste est alloué suivant les besoins, soit au CPU, soit au ''blitter''.
Le ''blitter'' des anciennes consoles Amiga fonctionne suivant trois modes de fonctionnement : copie mémoire, remplissage de polygone, et tracé de ligne. Leurs noms sont assez parlants. Le mode de tracé de ligne permet de tracer une ligne en utilisant l'algorithme de Bresenham. Les deux autres modes se ressemblent, dans le sens où ils font des écritures en mémoire, pour remplir un bloc entier de forme rectangulaire.
Le premier mode copie un bloc de mémoire dans un autre, mais peut aussi effectuer une opération logique entre plusieurs blocs. 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. Les quatre blocs peuvent se recouvrir partiellement. Les blocs en question sont rectangulaires et sont définis par les informations suivantes : une longueur mesurée en multiples de 16 bits, une largeur exprimée en nombre de lignes, et un ''stride'' qui indique la distance entre deux lignes dans le ''framebuffer''.
Pour ce qui est du remplissage de polygone, il s'agit du remplissage de figures géométriques simples avec une couleur uniforme. Les figures géométriques sont soit en forme de rectangles, soit des trapèzes dessinés à l'horizontale. 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. Il peut aussi remplir un polygone d'une couleur uniforme.
Enfin, Agnus contient un co-processeur spécialisé appelé '''Copper'''. Il s'agit plus d'une machine à état que d'un co-processeur, mais passons ce détail sous silence. Il exécute une ''display list'', à savoir une liste d'instructions, un programme. Les instructions en question sont de trois type : MOV, WAIT et SKIP. L'instruction MOV écrit dans les registres de configuration du VDC, ce qui lui permet d'initialiser des transferts DMA, de modifier les ''sprites'', etc. L'instruction WAIT attend que le l'écran en soit arrivé au rendu du pixel de coordonnée X,Y, ce qui peut remplacer partiellement les ''raster interrupts''. L'instruction SKIP est techniquement un branchement qui passe outre certaines instructions de la ''display list'' si l'écran a dépassé le pixel de coordonnée X,Y.
Le co-processeur Copper peut modifier les registres du ''blitter'', ce qui lui permet de démarrer des copies mémoire sans que le CPU ne soit impliqué. Sans lui, le CPU devrait configurer les registres du ''blitter'' pour initier une copie. Ici, le CPU n'a rien à faire : tout est précisé dans la ''display list''. Copper peut modifier les registres de ''sprites'' à la volée, il suffit de coder la ''display list'' adéquate. Copper peut aussi générer des ''raster interrupts'', encore que leur utilité soit ici moindre, vu que ''blitter'' et registres de ''sprites'' sont gérés par la ''display list''. Mais elles sont supportées pour gérer la synchronisation CPU-mémoire vidéo.
{{NavChapitre | book=Les cartes graphiques
| prev=Les accélérateurs de scanline
| prevText=Les accélérateurs de scanline
| next=Les cartes d'affichage des anciens PC
| nextText=Les cartes d'affichage des anciens PC
}}
{{autocat}}
jla936dxxxtzakwpu5zaccilw8zxe4w
774455
774454
2026-10-08T13:41:35Z
Mewtow
31375
/* Le rendu des sprites sur la borne d'arcade CPS-1 */
774455
wikitext
text/x-wiki
Nous avions vu dans le chapitre précédent qu'il existe plusieurs types de VDC. Dans les chapitres précédents, nous avons abordé les CRTC des systèmes à ''framebuffer'', ainsi que des VDC à tile et ceux à tampon de ligne. Dans ce chapitre, nous allons voir des VDC ou des systèmes graphiques originaux.
==Le rendu des ''sprites'' sur la borne d'arcade CPS-1==
La borne d'arcade CPS-1 avait une méthode rendu assez particulière. Contrairement aux VDC normaux, sa carte graphique rendait les ''sprites'' séparément. Elle avait un ''framebuffer'' dédié au rendu des ''sprite'', qui était combiné avec l'arrière-plan pour donner l'image finale. La carte graphique lisait les ''sprites'' depuis la cartouche de jeu et les plaçait dans ce second ''framebuffer'', pour donner une image sans l'arrière-plan. La combinaison se faisait au moment d'afficher l'image, lors du rendu d'une ligne à l'écran.
Un inconvénient de cette méthode est que le rendu du ''framebuffer'' pour les ''sprites'' se faisait pendant le rendu de l'image précédente. En clair, pendant que le GPU affichait une image à l'écran, le VDC accédait à la mémoire vidéo pour lire les ''sprites'' et calculer ce second ''framebuffer''. Le résultat était qu'il y avait une sorte de retard d'une ''frame'' pour les ''sprites'', donc un léger ''input lag''. Un second inconvénient est le cout en hardware. Le ''framebuffer'' pour les ''sprites'' avait sa propre mémoire RAM séparée du reste, d'une capacité mémoire de 384 kilo-octets. Cela coutait cher, mais les bornes d'arcade de l'époque se moquaient un peu de ces considérations, vu leur prix élevé.
L'avantage est que les limitations en matière de ''sprite'' par ligne n'existent pas sur ce système. Les autres systèmes de l'époque devaient faire avec une limite de ''sprite'' par ligne, que les développeurs contournaient en éteignant certains ''sprites'' une image sur deux, donnant un effet de clignotement. De son côté, le CPS-1 avait juste une limite par image, et une limite par image assez élevée qui plus est. Les jeux sur CPS-1 n'avaient donc pas d'effets de clignotement. Par contre, les développeurs devaient faire avec la limitation de ''sprite'' par image. Cela limitait les graphismes sur la CPS-1 car le ''swap'' des ''sprites'' à la volée lors de l'affichage n'était pas possible. Pour résumer, la CPS-1 échangeait une limitation de ''sprite'' par ligne pour une limitation de ''sprite'' par image.
==Le double VDC de la SuperGrafx==
La console de jeu SuperGrafx est le successeur spirituel de la console PC-Engine, aussi appelée Turbo Graphx en occident. Elle a le même CPU, la même quantité de mémoire RAM, le même VDC 2D. Voici l'architecture de la Turbo Graphx, qui est assez simple. Un CPU, une puce sonore, un VDC, un circuit pour la palette et l'interface écran séparé du VDC, une RAM pour le CPU et une mémoire vidéo reliée au VDC. La ROM du système est dans la cartouche de jeu.
[[File:Turbo graphx.png|centre|vignette|upright=2.5|Turbo graphx]]
La Super Graphx a une grosse différence : elle a deux VDC identiques ! L'objectif des concepteurs de la console était de doubler la puissance graphique et la solution la plus simple était évidemment de dupliquer le VDC... La console avait donc deux VDC identique à ceux de la PC-Engine, à savoir deux HuC6270. La mémoire vidéo est elle aussi dupliquée, à savoir que chaque VDC dispose de sa propre mémoire vidéo de 64 kilo-octets.
Les deux VDC calculent une image séparée chacun de leur côté, puis les deux images sont fusionnées pour donner l'image finale. L'intérêt est que cela permet d'avoir deux fois plus d'arrière-plan et de ''sprite'', si la fusion est gérée correctement. La fusion est réalisée par un circuit séparé, appelé le VPC (''Video Priority Controler''). À chaque pixel de l'écran, il choisit si ce pixel provient du premier VDC ou du second. Le tout est géré par un système qui dit quel écran a la priorité, l'écran prioritaire étant codé sur un 1 bit par pixel. Le VPC gère aussi la palette indicée et le RAMDAC, il n'y a pas de VCE séparé comme sur la PC-Engine.
[[File:Super Graphx 01.png|centre|vignette|upright=2.5|Super Graphx]]
==Les ''Video shifters'' : des VDC sans mémoire vidéo associée==
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.
Les VDP les plus simples se passent de mémoire vidéo. Ils n'effectuent pas le rendu ligne par ligne, mais réellement pixel par pixel. Nous les appellerons des '''''Video shifters''''', un nom qui trahit le fait qu'ils reçoivent des données pixel par pixel, et les transforment en un flux de bit, voire un flux analogique (ils intègrent alors un RAMDAC). Ils ne peuvent pas envoyer de commandes/adresses à la mémoire vidéo, ils ne parcourent pas le ''framebuffer'' et ne lisent pas les pixels à envoyer à l'écran dedans, c'est le processeur qui le fait à leur place. Leur avantage est qu'ils se passent de mémoire vidéo dédiée et d'utiliser une mémoire unique pour le processeur et le système vidéo. Ils sont très rares, les seuls à avoir été utilisé sur un microordinateur sont le RCA CDP1861 du micro-ordinateur soviétique COSMAC VIP, et le système vidéo du Sinclair ZX-81 (bien qu'il soit fabriqué avec des portes logiques et non avec un VDP).
Pour résumer, voici les fonctions possibles de ces circuits :
* RAMDAC et transformation octet/pixel en flux de bits ;
* génération des signaux de commande pour l'écran ;
* génération des ''raster interrupts''.
Prenons l'exemple du RCA CDP1861. Il générait les signaux HSYNC et VSYNC de synchronisation verticale et horizontale pour l'écran, et transformait un octet reçu en flux de bits envoyés à l'écran (monochrome). L'intérieur de ce circuit était très simple : un registre à décalage pour la transformation parallèle-série, des compteurs pour la ligne et la colonne, quelques circuits de contrôle associés.
Le RCA CDP1861 était relié au processeur et à la mémoire comme indiqué ci-dessous. Le rendu d'une image se faisait en utilisant les ''raster interrupt''. Le RCA CDP1861 générait une interruption à chaque fin de ligne, grâce à un compteur de ligne interne. Le processeur affichait une image grâce à une routine d'interruption, aidé par un contrôleur DMA intégré dans le processeur. La routine d'interruption configurait le contrôleur DMA, qui s'occupait d'envoyer l'image au RCA CDP1861, octet par octet. Il y avait un signal de synchronisation entre RCA CDP1861 et processeur/DMA : un bit émis par le RCA CDP1861 à destination du processeur indiquait qu'il était prêt à recevoir un nouvel octet.
[[File:VDP shifter.png|centre|vignette|upright=2|VDP shifter RCA CDP1861]]
==Les fonctionnalités supplémentaires==
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.
Les VDC peuvent gérer des fonctionnalités qui dépassent de loin le rendu 2D. Ils peuvent gérer des fonctionnalités très diverses, allant de la gestion de la mémoire vidéo, l'intégration d'un co-processeur graphique, la gestion du clavier et de la souris, une carte son rudimentaire, la gestion des signaux d'horloge du processeur, des ''timers'' et bien d'autres. Faisons un petit tour d'horizon de ces fonctionnalités.
===Le contrôleur mémoire pour la RAM 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.
Un point important est que la mémoire vidéo est quasi-systématiquement 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 VDP les plus complexe peuvent incorporer des circuits pour effectuer ce rafraichissement automatiquement. Mais d'autres VDC plus simples font sans et ajoutent des circuits de rafraichissement mémoire séparés du VDC.
Et outre le rafraichissement, l'accès à une mémoire DRAM est plus complexe que l'accès aux autres mémoires RAM. Les timings des accès mémoire sont complexes, sans compter que l'adresse doit être envoyée en deux fois. Pour cela, un circuit appelé le contrôleur mémoire est nécessaire pour communiquer avec une DRAM. Le contrôleur traduit les adresses mémoires et signaux de commandes en commandes compréhensibles par la mémoire DRAM. Notamment, il reçoit une adresse mémoire et l'envoie en deux fois. Le contrôleur mémoire peut être intégré au VDC, ou être un circuit séparé, tout dépend du VDC en question.
===Les fonctionnalités liées aux périphériques===
Il existe des VIC qui 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).
[[File:Engineer working on CAD workstation with light pen Hughes Aircraft Company.jpg|vignette|Ingénieur de ''Hughes Aircraft Company'' travaillant sur un ordinateur de CAO avec un crayon optique.]]
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.
===Un exemple : le NEC μPD7220===
[[File:NECuPD7220BlockDiagram.svg|vignette|NECuPD7220BlockDiagram]]
Un 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''.
===L'exemple des anciens micro-ordinateurs Amiga===
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. Il est intéressant de donner quelques exemples réels de consoles de jeux qui utilisaient de l'accélération 2D. Nous allons voir l'exemple des anciens micro-ordinateurs Amiga.
[[File:Amiga Original Chipset diagram.svg|vignette|Amiga Original Chipset diagram]]
Pour finir ce chapitre, nous allons voir les 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.
Agnus contient un ''blitter'', un CRTC, et un co-processeur spécialisé qu'on détaillera plus bas. 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 temporels.
Agnus incorpore un circuit de gestion de la mémoire assez complexe. Il intégre notamment un contrôleur mémoire de DRAM, pour communiquer avec la mémoire, qui lui-même incorpore un circuit de rafraichissement mémoire. Tous les contrôleurs DRAM de l'époque ne géraient pas le rafraichissement mémoire, mais c'est le cas d'Agnus. Il n'a donc pas besoin de déléguer cette fonction au processeur. De plus, le circuit de gestion mémoire 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. Agnus alloue un cycle sur deux au ''CRTC'', le reste est alloué suivant les besoins, soit au CPU, soit au ''blitter''.
Le ''blitter'' des anciennes consoles Amiga fonctionne suivant trois modes de fonctionnement : copie mémoire, remplissage de polygone, et tracé de ligne. Leurs noms sont assez parlants. Le mode de tracé de ligne permet de tracer une ligne en utilisant l'algorithme de Bresenham. Les deux autres modes se ressemblent, dans le sens où ils font des écritures en mémoire, pour remplir un bloc entier de forme rectangulaire.
Le premier mode copie un bloc de mémoire dans un autre, mais peut aussi effectuer une opération logique entre plusieurs blocs. 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. Les quatre blocs peuvent se recouvrir partiellement. Les blocs en question sont rectangulaires et sont définis par les informations suivantes : une longueur mesurée en multiples de 16 bits, une largeur exprimée en nombre de lignes, et un ''stride'' qui indique la distance entre deux lignes dans le ''framebuffer''.
Pour ce qui est du remplissage de polygone, il s'agit du remplissage de figures géométriques simples avec une couleur uniforme. Les figures géométriques sont soit en forme de rectangles, soit des trapèzes dessinés à l'horizontale. 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. Il peut aussi remplir un polygone d'une couleur uniforme.
Enfin, Agnus contient un co-processeur spécialisé appelé '''Copper'''. Il s'agit plus d'une machine à état que d'un co-processeur, mais passons ce détail sous silence. Il exécute une ''display list'', à savoir une liste d'instructions, un programme. Les instructions en question sont de trois type : MOV, WAIT et SKIP. L'instruction MOV écrit dans les registres de configuration du VDC, ce qui lui permet d'initialiser des transferts DMA, de modifier les ''sprites'', etc. L'instruction WAIT attend que le l'écran en soit arrivé au rendu du pixel de coordonnée X,Y, ce qui peut remplacer partiellement les ''raster interrupts''. L'instruction SKIP est techniquement un branchement qui passe outre certaines instructions de la ''display list'' si l'écran a dépassé le pixel de coordonnée X,Y.
Le co-processeur Copper peut modifier les registres du ''blitter'', ce qui lui permet de démarrer des copies mémoire sans que le CPU ne soit impliqué. Sans lui, le CPU devrait configurer les registres du ''blitter'' pour initier une copie. Ici, le CPU n'a rien à faire : tout est précisé dans la ''display list''. Copper peut modifier les registres de ''sprites'' à la volée, il suffit de coder la ''display list'' adéquate. Copper peut aussi générer des ''raster interrupts'', encore que leur utilité soit ici moindre, vu que ''blitter'' et registres de ''sprites'' sont gérés par la ''display list''. Mais elles sont supportées pour gérer la synchronisation CPU-mémoire vidéo.
{{NavChapitre | book=Les cartes graphiques
| prev=Les accélérateurs de scanline
| prevText=Les accélérateurs de scanline
| next=Les cartes d'affichage des anciens PC
| nextText=Les cartes d'affichage des anciens PC
}}
{{autocat}}
8w4maxguuhjn8wryj6e52u1sprjgseq
Les cartes graphiques/Les accélérateurs de scanline
0
82283
774452
764905
2026-10-08T13:34:52Z
Mewtow
31375
/* Le système graphique de la console néo-géo */
774452
wikitext
text/x-wiki
Les chapitres précédents ont abordé des VDC couplés à un ''framebuffer'', mais tous les VDC ne sont pas dans ce cas. La raison est assez simple : une image prend beaucoup de mémoire ! Par exemple, prenons le cas d'une image en niveaux de gris d'une résolution de 320 par 240 pixels, chaque pixel étant codé sur un octet. L'image prend alors 76800 octets, soit environ 76 kiloctets. Mine de rien, cela fait beaucoup de mémoire ! Les tout premiers ordinateurs n'avaient pas assez de mémoire vidéo, les cartes d'affichages devaient se débrouiller avec beaucoup de mémoire.
Pour optimiser l'utilisation de la mémoire, les cartes d'affichage n'avaient pas de ''framebuffer'' et fonctionnaient autrement. Il existe plusieurs optimisations pour économiser de la mémoire. La première optimisation est appelée le rendu en tiles, que nous verrons dans le prochain chapitre. Il s'agit techniquement d'une forme de compression d'image spécialisée, qui est assez reliée au mode texte des anciennes cartes graphiques. Nous verrons le rendu en tile et le rendu en mode texte dans un chapitre dédié.
Une autre optimisation rend l'image ligne par ligne. La mémoire vidéo ne contient que la ligne en cours de rendu, pas une image complète. D'ailleurs, la mémoire vidéo est souvent appelée le ''line buffer'', '''tampon de ligne''' en français. Il n'y a pas de terminologie officielle pour désigner de tels VDC, mais nous pouvons les appeler des '''''scanline accelerators''''', '''accélérateurs de ''scanline'''''.
Quelques VDC anciens étaient de ce type. Par exemple, les premières consoles ATARI utilisaient des accélérateurs de ''scanline'', de même que la carte graphique de la console néo-géo. Voyons-en quelques exemples, avec notamment les VDC des consoles ATARI. Nous verrons dans quelques sections qu'ils sont assez complexes, avec une gestion des ''sprites'' matériels, du défilement, et quelques autres techniques d'accélération 2D.
==Généralités sur les accélérateurs de ''scanline''==
Avec un accélérateur de ''scanline'', le processeur doit écrire la prochaine ligne à afficher dans le tampon de ligne, avec un timing assez serré. Il doit attendre que l'affichage de la ligne en cours soit terminé, mais doit finir son travail avant l'affichage de la prochaine ligne. Pour le dire autrement, le processeur met à jour la ligne à afficher pendant l'intervalle de ''horizontal blank''. Il s'agit d'une technique appelée le '''''racing the beam'''''. Pour cela, le VDC émet une interruption matérielle à la fin de l’affichage de la ligne courante.
Les accélérateurs de ''scanline'' peuvent supporter les ''sprites'' matériels. Typiquement, ils supportent un certain nombre de ''sprite'' par ligne. Notez le "par ligne". En effet, le processeur peut en profiter pour changer les ''sprites'' entre l'affichage de deux lignes. L'intérêt est que cela permet d'afficher plus de ''sprites'' que ce que la console permet. Par exemple, si l’accélérateur de ''scanline'' supporte 8 ''sprites'' par ligne, il peut afficher plus de 8 ''sprite'' par image, en respectant la contrainte de 8 ''sprite'' par ligne. C'est juste qu'il y aura, par exemple, 8 ''sprites'' en haut de l'image, 8 au milieu, 8 en bas.
===La ''display list''===
Les accélérateurs de ''scanline'' les plus complexes permettent de réduire grandement le nombre de ''raster interrupts'' en intégrant une sorte de contrôleur DMA programmable. Dans le détail, ce contrôleur DMA exécute un programme composé d'une suite d'instructions simples, chacune commande l'affichage d'une ligne à l'écran. Le programme exécuté est une série d'instructions appelée la '''''display list''''', terme qui reviendra dans quelques chapitres.
La ''display list'' commence par un préambule de configuration, qui indique la résolution, le mode graphique, etc. Puis, elle afficher l'image à l'écran, ligne par ligne, sans intervention du processeur. La display list a une instruction par ligne à afficher, l'instruction copie la ligne à afficher de la mémoire RAM principale vers la mémoire vidéo (le tampon de ligne), pendant une période de ''blanking'' horizontal. La ''display list'' s'occupe de séquencer les lignes une par une, sans intervention du processeur. Le processeur n'a pas besoin de copier lui-même chaque ligne, la ''display list'' le fait automatiquement pour toutes les lignes.
Un avantage est que le défilement vertical est très rapide avec une ''display list''. Il suffit de rajouter ou d'enlever des lignes dans la ''display list''. Pour défiler d'une ligne vers le bas, on rajoute une ligne au début de la ''display list'' et on enlève la dernière. Et inversement pour défiler vers le haut. Le processeur peut le faire sans se synchroniser fortement avec l'accélérateur de ''scanline''. Nous verrons dans quelques chapitres que ce mécanisme a été utilisé sur les ordinateurs Amstrad PCW, mais nous ne pouvons pas en parler pour le moment, vu que leur carte d'affichage est une carte en mode texte, qu'on n'a pas encore vu.
[[File:Vector display architecture.svg|centre|vignette|upright=1.5|Illustration d'un ''Video Interface Controler''.]]
La ''display list'' fait que les ''raster interrupts'' sont inutiles, ou du moins grandement réduites. Les interruptions sont déclenchées pour certaines lignes seulement, celles où l'instruction qui affiche la ligne le précise. La gestion des interruptions est donc totalement programmable. La ''display list'' contient des instructions pour déclencher des ''raster interrupts'', placées stratégiquement dans la ''display list'' par le programmeur. Par exemple, si le programmeur veut changer les ''sprites'' entre deux lignes, il peut déclencher une ''raster interrupts'' entre ces deux lignes, le processeur répondra à l'interruption en changeant les registres de ''sprites''.
==Le ''Television Interface Adaptor'' des consoles Atari 2600==
Un exemple d'accélérateur de ''scanline'' est celui utilisé sur l'''Atari Video Computer System'' et l'Atari 2600. La carte graphique en question s'appelait la '''''Television Interface Adaptor''''', abrévié TIA. Il incorpore un tampon de ligne, qui est modifié entre l'envoi à l'écran de deux lignes. Par contre, il n'y a pas d'interruption pour prévenir le processeur qu'il doit envoyer la ligne suivante.
===Les contraintes de conception===
Le TIA a été conçu avec de nombreuses contraintes en tête, qui ne se comprennent qu'en remettant la console dans son contexte historique. L'époque est celle des '''consoles de première génération''', des consoles qui étaient conçues pour un jeu bien précis. Pas de cartouche, de CD ou quoique ce soit d'autre. Le jeu était intégré directement dans les circuits de la console, qui n'était pas programmable. Il y avait une console pour le jeu Pong, une autre pour le jeu Tank, etc. Les jeux étaient alors très limités niveau gameplay.
<noinclude>[[File:Pong Game Test2.gif|vignette|upright=2|Pong Game Test2]]</noinclude>
Les premières consoles ATARI étaient les premières '''consoles de seconde génération''', conçues pour faire fonctionner plusieurs jeux, conçues pour être programmables. Elles étaient conçues pour faire fonctionner Pong, Tank et des variantes de ces deux jeux. Pour rappel, s'il faut faire un rappel, Pong était un jeu de tennis à deux joueurs. Il se limitait à un arrière-plan fixe sur lequel chaque joueur contrôle une barre verticale, sur laquelle la balle peut rebondir.
Tank était une variante de PONG, où deux joueurs controlaient chaun un tank et se tiraient des missiles dessus avec l'intention de détruire le tank de l'adversaire. Son adaptation sur ATARI s'appelait Combat!, en voici un screenshot :
[[File:Combat.svg|centre|vignette|upright=2|Combat, jeu de l'Atari 2600.]]
La console est donc conçue pour gérer un arrière-plan, deux sprites pour les joueurs, un pour la balle, deux pour les missiles. Chacun est mémorisé dans un registre dédié, qui mémorise la ligne du sprite/arrière-plan à afficher à l'écran. De plus, le TIA incorpore un système pour détecter les collisions, afin de détecter si la balle rebondit sur la raquette dans PONG, sur un missile touche un tank dans TANK. La conception du TIA est très simple, sans compter que diverses optimisations permettent d'encore plus simplifier le TIA. Par exemple, l'arrière-plan est censé être symétrique : la partie gauche est recopiée sur le côté droit de l'écran, avec un effet miroir. Optimisation possible car PONG et TANK sont censé avoir un terrain de jeu symétrique, identique pour chaque joueur.
Avec une architecture aussi simple, il n'est pas possible de faire de miracles. Les jeux sur ATARI devaient faire avec les limitations du TIA : arrière-plan symétrique, moins d'une dizaine de sprites simultanés à l'écran.
<noinclude>{|
|-
|[[File:Mini Golf Atari 2600.jpg|vignette|Mini Golf, Atari 2600]]
|[[File:Backgammon Atari 2600.jpg|vignette|Backgammon , Atari 2600]]
|[[File:Human Cannonball Atari 2600.jpg|vignette|Human Cannonball, Atari 2600]]
|-
|[[File:Frogs & Flies Atari 2600.jpg|vignette|Frogs & Flies, Atari 2600]]
|[[File:DuckAttack-Tank-room-1.png|vignette|Screenshot de Duck Attack !]]
|[[File:Barnstorming Atari 2600.jpg|vignette|Barnstorming Atari 2600]]
|}</noinclude>
Quelques ruses de sioux ont cependant permis de dépasser ces limites techniques.
{|
|-
|[[File:Spider Fighter Atari 2600.jpg|vignette|Spider Fighter Atari 2600]]
|[[File:Robot Tank Atari 2600.jpg|vignette|Robot Tank Atari 2600]]
|[[File:Bradley Trainer screenshot.png|vignette|Bradley Trainer screenshot]]
|}
===L'arrière-plan du TIA : un registre dédié de 20 bits===
Vu que le rendu s'effectue ligne par ligne, l'arrière-plan et les sprites sont en réalité des lignes, pas des images complètes. Le '''registre d'arrière-plan''' est un registre de 20 bits. Un bit correspond à 4 pixels de l'écran, il indique la couleur de ces 4 pixels consécutifs. Peut-être avez-vous remarquez que 20 bits * 4 pixels = 80 pixels. Pourtant, le TIA gére une résolution horizontale par défaut de 160 pixels. Soit deux fois plus que ce qui est géré par le registre d'arrière-plan. La raison est que l'arrière-plan est symétrique. Le registre d'arrière-plan ne définit que la moitié gauche de l'écran, la moitié droite en est le reflet miroir.
Vous pourriez croire que la couleur codée dans ce registre est un simple noir et blanc, mais c'est plus compliqué. Le registre d'arrière-plan indique, pour chaque bloc de 4 pixels, si le pixel prend la couleur de l'arrière-plan ou la couleur de fond par défaut. Un registre séparé contient la couleur de l'arrière-plan, elle-même encodée grâce à un système de palette indicé. La couleur de fond est elle aussi configurable.
En théorie, l'arrière-plan est censé être symétrique, mais quelques astuces permettent d'afficher un arrière-plan asymétrique. L'idée est simplement de modifier le registre d'arrière-plan une fois que la moitié gauche a été affichée. Cela demande un ''timing'' très précis, mais c'est possible et plusieurs jeux Atari 2600 le faisaient. Certains arrivaient même à simuler un défilement horizontal !
===Les ''sprites'' matériels du TIA===
Le TIA gère 5 ''sprites'' matériels, grâce à 5 registres capables chacun de mémoriser une ligne de pixel. Ces registres sont spécialisés, avec deux registres pour les avatars des joueurs, deux missiles et une balle. Ne vous fiez pas aux noms, la différence entre les 5 est la taille des ''sprites'' stocké dedans. Les ''sprites'' des avatars sont de 8 pixels de large, peuvent être dupliquées ou étendus. La balle est un ''sprite'' de même couleur que l'arrière-plan, de 1, 2, 4 ou 8 pixels de large. Les deux missiles sont identiques à la balle, sauf qu'ils ont la même couleur que le ''sprite'' du joueur associé.
Un ''sprite'' matériel sur le TIA est codé sur un octet. Un ''sprite'' fait 8 pixels de large, chacun étant là-encore codé sur 1 bit. Il indique encore une fois si le pixel a la couleur de fond ou une couleur spécifique au ''sprite''. Vu que cet octet écrit une ligne du ''sprite'', il est changé à chaque ligne pour réellement afficher un ''sprite'' à deux dimensions. Vous remarquerez que le ''sprite'' est codé sur un octet, les informations de sa position X et Y à l'écran ne sont pas encodées. La raison est simple : un ''sprite'' est affiché quand le processeur écrit l'octet dedans.
L'affichage d'un ''sprite'' est donc commandé par le processeur et doit se faire au cycle d'horloge près. Le problème est que le processeur n'est pas précis au pixel près. Déjà, sa fréquence est égale à un tiers de la fréquence du TIA, ce qui fait qu'il est précis à 3 pixels près. De plus, le logiciel faisait l'écriture dans le registre de ''sprite'' est basé sur une boucle, dont les instructions prennent 5 cycles pour s'exécuter. Ce qui fait une imprécision de 15 cycles.
Pour compenser ce défaut, le TIA incorpore deux registres, un par avatar/''player''. Ils permettent de décaler le ''sprite'' de quelques pixels, la valeur de décalage étant précisée dans ce registre. La valeur est un entier allant de -7 à 8 cycles/pixels. L'affichage d'un ''sprite'' est donc en deux temps : on déclenche une écriture précise à 15 cycles près, puis on décale le ''sprite'' avec ces deux registres.
===L'architecture du TIA===
Mettre à jour les registres du TIA à chaque ligne n'est pas si différent de modifier une mémoire vidéo entre deux lignes. Mais un défaut des consoles ATARI est que le processeur n'utilisait pas d'interruptions pour gérer l'affichage des lignes. À la place, le bus indiquait au processeur quand le TIA acceptait qu'on modifie ses registres. Le bus avait un signal RDY (READY) qui indiquait si le TIA était occupé à afficher une ligne ou non.
Un autre défaut est que le TIA ne génère pas totalement les signaux de synchronisation verticale, qui servent à indiquer à l'écran qu'il a fini d'afficher une image. La raison est qu'il n'a pas de compteur de ligne ! Il y a bien un compteur pour les colonnes, pour générer les signaux de synchronisation horizontale, mais pas plus. C'est le processeur qui doit générer le signal de synchronisation verticale de lui-même ! Pour cela, le processeur doit écrire dans une adresse dédie associée au TIA, ce qui déclenche un signal de synchronisation verticale immédiat.
Si on devait faire un constat avec tout ce qui a été dit plus haut, il serait que le TIA est très simple, trop simple. Le processeur est très impliqué dans l'affichage à l'écran, notamment pour les ''sprites''. La carte graphique est donc composé par l'association entre le TIA et le processeur proprement dit, bien que celui-ci soit le CPU. Ce qui explique l'absence de mécanismes de synchronisation entre TIA et CPU : c'est le CPU qui commande le TIA au cycle près.
==Les VDC ANTIC et CTIA des consoles Atari post-2600==
Les consoles Atari 8 bits, qui ont succédé à l'Atari 2600, n'utilisaient un VDC TIA amélioré appelé le CTIA pour sa première version (''Color Television Interface Adaptor''), le GTIA pour sa seconde version (''Graphic Television Interface Adaptor''). Les deux étaient un accélérateur de ''scanline'' qui intégraient l'accélération matérielle de ''sprites'' et une palette indicée. Comme pour le TIA, les ''sprites'' servaient pour les joueurs et des missiles, sauf que leur nombre est doublé pour passer à 4 chacun.
Mais la révolution était qu'ils étaient associé à un autre circuit appelé l'ANTIC, qui commandait le CTIA/GTIA sans intervention du processeur CPU. Là où l'Atari 2600 utilisait le processeur pour l'affichage des ''sprites'' et beaucoup d'autres fonctions, une bonne partie de ces traitements est déléguées à l'ANTIC sur les machines suivantes. L'ANTIC est un ''blitter'' amélioré, en charge des copies en mémoire. Mais surtout, il s'occupe du séquencement des lignes envoyées au CTIA. Ce n'est pas le processeur qui s'occupe d'envoyer les lignes une par une au CTIA, cette fonction est déportée sur l'ANTIC.
===La ''display list''===
Le fonctionnement de l'ANTIC est assez complexe, mais on peut le voir comme une sorte de pseudo-processeur, qui exécute un programme de rendu 2D. Le programme en question est une suite d’opération de rendu 2d à exécuter dans l'ordre, qui permet de calculer l'image ligne par ligne. Il est appelé la '''''Display List''''', et est stocké dans la mémoire de la console. L'ANTIC lit le programme instruction par instruction, grâce à un contrôleur DMA intégré dans l'ANTIC, qui contient un ''program counter'' interne à l'ANTIC. L'exécution d'une ''Display List'' sur l'ANTIC permet de décharger le processeur des tâches de rendu 2D, du moins dans une certaine mesure.
Une instruction de la ''display list'' permet de calculer une ligne de l'écran, en précisant comment combiner les ''sprites'' et l'arrière-plan, tous deux stockés dans la mémoire vidéo. La ''display list'' gére entre 0 et 240 instructions, ce qui limite la résolution verticale à 240 pixels. Les instructions peuvent se classer en quatre types principaux : des instructions pour écrire une ligne de pixels colorés, des instructions pour écrire une ligne de caractères, des instructions pour émettre les signaux de synchronisation de ''blanking'', et enfin des branchements qui agissent sur le ''program counter'' de l'ANTIC. Les instructions pour afficher des pixels/caractères peuvent activer le défilement horizontal ou vertical suivant l'instruction.
Une instruction est codée sur 8 bits minimum.
{| class="wikitable"
|-
! Bit 7 !! Bit 6 !! Bit 5 !! Bit 4 !! Bit 3 !! Bit 2 !! Bit 1 !! Bit 0
|-
| DLI || LMS || VS || HS || colspan="4" | Mode
|}
Les 4 bits de mode précisent quelle est l'instruction à utiliser, ce qui fait 16 instructions en tout, réparties dans les 4 classes mentionnées plus haut. Les bits HS et VS activent ou désactivent le défilement vertical et horizontal. Le bit LMS permet à l'instruction de préciser l'adresse où se trouvent les tableaux de données à utiliser, comme la place de l'arrière-plan en mémoire RAM, ce genre de choses. Pour cela, le bit est placé à 1 et l'adresse est dans les 2 octets qui suivent l'instruction. Le bit DLI gère les interruptions dites de ''Display List'', ou interruptions DLI, que nous allons voir dans ce qui suit.
Les interruptions DLI sont l'équivalent sur l'ANTIC des interruptions d'''horizontal blanking'', déclenchées à la fin de l'affichage d'une ligne. Elles préviennent le processeur qu'une ligne entière a été affichée. Le processeur peut alors écrire en mémoire vidéo durant un court laps de temps s'il en a besoin. Il peut alors changer les ''sprites'' à la volée, ce qui permet d'afficher plus de ''sprites'' que ce que supporte le chip CTIA/GTIA. Les interruptions sont déclenchées pour certaines lignes seulement, celles où l'instruction qui affiche la ligne le précise. Si l'instruction a son bit DLI mis à 1, alors une interruption est déclenchée à la fin de la ligne. S'il est à 0, pas d'interruption. La gestion des interruptions DLI est donc totalement programmable.
===Les registres de configuration de l'ANTIC===
[[File:Atari ANTIC (CO12296) pin-out.svg|vignette|Atari ANTIC (CO12296) pin-out]]
Niveau registres, l'ANTIC contient un ''program counter'' et de quoi configurer le contrôleur DMA. Il a aussi des registres pour localiser les données importantes en mémoire, qui sont regroupées dans des tableaux séparés.
Pour ce qui est du rendu, il contient un registre pour l'adresse de l'arrière-plan, un autre pour l'adresse des ''sprites'' en mémoire RAM. Il contient aussi deux registres pour configurer le défilement horizontal et vertical.
Pour gérer la synchronisation avec le CPU, l'ANTIC incorpore un compteur de ligne, ainsi qu'un registre de synchronisation horizontale qui est à 1 quand une ligne est en train d'être affichée (mécanisme redondant avec les interruptions DLI). Il y a aussi plusieurs registres pour gérer les interruptions, avec un registre de statut que le CPU peut lire à loisir pour savoir quelle est la raison qui a déclenché l'interruption, ainsi qu'un registre de configuration des interruptions.
==Le système graphique de la console néo-géo==
La néo-géo est une console assez ancienne, de la période 16 bits, commercialisée en 1990. Elle avait pour caractéristique d'avoir le même hardware que les bornes d'arcade du même nom, les néo-géo MVS. Elle comprenait un processeur Motorola 68000, 64 kilooctets de mémoire RAM, un processeur sonore Zilog Z80, et un VDC avec 64 kilooctets de mémoire vidéo (plus 4 kilooctets de VRAM complémentaire). Elle avait une palette de 65 536 couleurs, grâce à une palette indicée de 8 kilooctets.
===L'avant-plan, l'arrière-plan et les ''sprites'' de la néo-géo===
Un point important est que la mémoire vidéo n'est pas un ''framebuffer'', le VDC utilise un tampon de ligne. La console a pour particularité de ne pas gérer d’arrière-plan, l'image affichée est intégralement composée de ''sprites''. Les ''sprites'' sont tous combinés pour donner l'image finale.
Un autre détail est que les ''sprites'' matériels sur la néo-géo sont différents des ''sprites'' matériels des consoles précédentes. Ils sont composés de plusieurs motifs, de plusieurs ''tiles''. Les motifs font tous des carrés de 16 pixels de côtés, contenant 15 couleurs (plus la couleur de transparence). Un ''sprite'' est un rectangle qui a un pixel de large, mais dont la hauteur va de 1 à 32 motifs, donc de 16 à 512 pixels. Chaque ''sprite'' indique quels sont ses motifs, quelle palette utiliser, s'il faut appliquer l'effet miroir, etc. Un ''sprite'' logiciel est composé en concaténant horizontalement plusieurs ''sprites'' de néo-géo.
A défaut d'un arrière-plan, la console gère un pseudo-arrière-plan composé d'une couleur unie. Pour faire la différence avec une image d’arrière-plan, on peut plutôt parler d'une couleur de fond. Les ''sprites'' sont empilés les uns sur les autres au-dessus de cet arrière-plan. Au-dessus des ''sprites'', il y a un avant-plan spécialisé, qui est rendu au-dessus de tout le reste, qui est utilisé pour rendre le HUD. Cet avant-plan est appelé la ''fix layer'' dans la documentation de la console. Les motifs de l'avant-plan n'ont pas la même taille que pour les ''sprites'' : ce sont des carrés de 8 pixels de côtés, pas 16 !
Les motifs de l'avant-plan sont placés dans une mémoire ROM de celle pour les motifs des ''sprites''. En effet, une cartouche de néo-géo contient plusieurs mémoires ROM :
* une pour les motifs des ''sprites'' ;
* une pour les motifs de l'avant-plan ;
* une pour le programme à exécuter sur le processeur M68000 principal ;
* une pour le programme à exécuter sur le processeur sonore Z80 ;
* une pour les échantillons audio, la musique et le son du jeu.
===Le rendu d'une image===
Lors du rendu d'une ligne, la console détermine quels ''sprites'' sont affichés sur cette ligne, et les ''sprites'' adéquats sont lus depuis la ROM de la cartouche. Les pixels de la ligne sont déterminés par le VDC et écrit dans le tampon de ligne. La console supporte au maximum 96 ''sprites'' par ligne, ce qui dépasse de très loin les capacités des autres consoles 8 ou 16 bits.
La console dispose de deux tampons de lignes, ce qui permet d'afficher une ligne pendant qu'on prépare la suivante. L'idée est la suivante. La première ligne est placée dans un tampon de ligne, elle est envoyée à l'écran pixel par pixel par le VDC. En même temps, le processeur écrit la prochaine dans le second tampon de ligne. Une fois la ligne totalement affichée, le VDC échange les deux tampons de ligne. La ligne suivante est déjà prête dans le second tampon de ligne, elle est envoyée à l'écran pixel par pixel, le premier tampon de ligne est utilisé pour précalculer la ligne suivante, et ainsi de suite.
La gestion de la profondeur des ''sprites'' est assez simple : les ''sprites'' sont placés dans la mémoire vidéo selon leur profondeur. En clair, les ''sprites'' situés au fond de l'image sont placés au début de la mémoire vidéo, ceux situés à l'avant-plan sont placés à la fin de la mémoire vidéo. Pas besoin po_ur le VDC de gérer les priorités et la profondeur des ''sprites'', tout est déjà réglé une fois les ''sprites'' chargés dans la mémoire vidéo.
Le console gère une mise à l'échelle des ''sprites'' matériels, mais ceux-ci ne peuvent qu'être réduits, pas zoomés. La mise à l'échelle est précise au pixel près. La mise à l'échelle peut se faire à l'horizontale et à la verticale, les deux sont séparés. Par exemple, on peut réduire un ''sprite'' d'un facteur 5 sur la verticale, d'un facteur 2 sur l'horizontale.
{{NavChapitre | book=Les cartes graphiques
| prev=Le mode texte et le rendu en tiles
| prevText=Le mode texte et le rendu en tiles
| next=Les Video Display Controler atypiques
| nextText=Les Video Display Controler atypiques
}}
{{autocat}}
l3jyx39srmb46x9gmy5mkjtmz18pwe1
Les cartes graphiques/Les cartes d'affichage des anciens PC
0
82725
774456
764906
2026-10-08T13:51:19Z
Mewtow
31375
/* Le multi-GPU EGA/CGA */
774456
wikitext
text/x-wiki
Dans cette section, nous allons parler rapidement des cartes graphiques des anciens PC, qui étaient des systèmes à ''framebuffer''. Les standards graphiques sur PC étaient le MDA, le CGA, l'EGA et le VGA, dans l'ordre chronologique. C'était les standards les plus communs, qui ont été introduits par les cartes graphiques suivantes :
* IBM Color/Graphics Display Adapter (CGA)
* IBM Monochrome Display and Printer Adapter (MDA or sometimes MDPA)
* IBM Enhanced Graphics Adapter with Graphics Memory Expansion Card (EGA)
* IBM PS/2 Display Adapter (VGA)
Le MDA et le CGA utilisaient des ''framebuffer'' compacts, alors que l'EGA et le VGA utilisaient des ''framebuffer'' planaires. Cependant, par souci de compatibilité, l'EGA et le VGA supportent les résolutions du CGA et du MDA, avec un ''framebuffer'' compact. La mémoire vidéo des cartes graphiques EGA et VGA pouvaient être utilisées soit de manière à avoir un ''framebuffer'' planaire ou compact, elle servait pour les deux.
Les cartes graphiques concurrentes étaient la ''Hercules Graphics Card'', la ''Tandy Video II Graphic Adapter'', la ''Multi-Color Graphics Array'' (MCGA) et quelques autres. Mais elles étaient peu disponibles et eurent peu de succès, à l'exception de la carte Hercules qui a été supportée dans quelques jeux vidéo.
==Les standards MDA et CGA==
Les tout premiers IBM PC, utilisaient la carte graphique ''IBM Monochrome Display Adapter'' (MDA), qui était toujours connectée à un écran ''5151 IBM PC Display'', que nous appellerons IBM 5151 dans ce qui suit. La carte CGA (''Color Graphics Adapter'') étaient disponibles en même temps que la carte MDA, mais les écrans CRT qui pouvaient utiliser ses fonctionnalités ont mis un peu de temps avant d'arriver, ce qui fait qu'on considère parfois à tort que la carte MDA est arrivée avant la CGA. La carte MDA ne gérait que le mode texte, en monochrome, alors que la carte CGA gérait jusqu'à 16 couleurs avec un système de palette indicée.
{|class="wikitable"
|+ Modes CGA officiels
! colspan="2" | Modes graphiques
|-
| 320 par 200 pixels || 4 couleurs par pixel
|-
| 640 par 200 pixels || 2 couleurs par pixel
|-
! colspan="2" | Modes texte
|-
| 320 par 200 pixels || 40 par 25 caractères
|-
| 640 par 200 pixels || 80 par 25 caractères
|}
Les cartes MDA et CGA avaient un hardware très semblable vu de loin. La carte MDA incorporait 4 kilo-octets de RAM vidéo, la carte CGA en avait 16 kilo-octets. Les deux incorporaient un VDC Motorola 6845, qui ne gérait que le mode texte. Les cartes CGA (''Color Graphics Adapter'') ont trouvé un moyen de supporter le mode graphique avec ce VDC, grâce à des circuits qui entouraient le VDC. Beaucoup de bidouilles étaient nécessaires pour lui faire afficher des graphismes pixel par pixel.
Il était possible d'utiliser une carte MDA en complément d'une carte CGA, dans le même PC. Les deux pouvaient être utilisées pour alimenter deux écrans : un écran monochrome pour la MDA et un écran couleur pour la CGA. Mais elles pouvaient aussi être utilisées pour alimenter un seul écran, la carte CGA se chargeant des calculs graphiques et la MDA pour afficher du texte.
Les alternatives aux cartes MDA/CGA étaient assez nombreuses. La quasi-totalité utilisait cependant le même VDC et un hardware très similaire. On parlait d'ailleurs de clones MDA/CGA. Elles se contentaient souvent d'ajouter des fonctionnalités, plus de mémoire vidéo, etc. Par exemple, la carte Hercules était totalement compatible avec la carte MDA, si ce n'est pour l'ajout d'un mode de résolution graphique capable de faire du pixel par pixel. Il y a cependant quelques exceptions comme la ''Professional Graphics Controller'' qui intégrait un processeur Intel 8088 en guise de VDC.
<noinclude>Une description détaille des cartes CGA, avec leurs registres de configuration, est disponible via ce lien. Je n'en conseille pas la lecture maintenant :
* [https://nerdlypleasures.blogspot.com/2016/05/ibms-cga-hardware-explained.html IBM's CGA Hardware Explained]</noinclude>
==Le standard EGA==
Par la suite, les cartes graphiques MDA et CGA ont été remplacées par les cartes EGA. De nombreuses cartes graphiques sensiblement identiques à l'EGA, des clones hardware, ont envahi le marché, transformant l'EGA en standard de facto. Elles géraient entre 4 et 16 couleurs, suivant la résolution, avec un système de palette indicée.
Les cartes EGA ont remplacé le ''framebuffer'' compact des anciennes cartes MDA/CGA par un ''framebuffer'' planaire. La mémoire vidéo de la carte EGA était composée de 4 banques, chaque banque étant un ''bitplane''. En clair, les modes gérant 16 couleurs codaient un pixel sur 4 bits, avec un bit par ''bitplane''/banque. Cependant, la résolution 640 par 350 pixels n'utilisait que deux couleurs et utilisait un ''framebuffer'' compact. Le choix entre un ''framebuffer'' planaire et compact était réalisé en configurant un bit dans le registre de configuration adéquat, à savoir le registre ''Sequencer Memory Mode Register '', faisant partie du ''Sequence Controller''. Le bit nommé Chain 4 Bit valait 0 pour un ''framebuffer'' planaire, 1 pour un ''framebuffer'' compact.
{|class="wikitable"
|-
! Résolution !! Couleurs par pixel || Framebuffer
|+ Modes EGA
|-
| 320 par 200 pixels || 16 couleurs par pixel || rowspan="3" | ''framebuffer'' planaire
|-
| 640 par 200 pixels || 16 couleurs par pixel
|-
| 640 par 350 pixels || 16 couleurs par pixel
|-
! colspan="3" | Mode nécessitant la carte d'extension mémoire
|-
| 640 par 350 pixels || 2 couleurs par pixel || ''framebuffer'' compact
|}
===L'architecture d'une carte EGA===
Une carte EGA était composée de 4 circuits, chacun ayant ses propres registres : le CRT Controller (CRTC), le Sequence Controller (SC), l'Attribute Controller (AC) et le Graphics Controller (GC).
* Le CRTC est un CRTC basique mais ce n'est pas le MC6845 des cartes CGA/MDA, bien que la compatibilité entre eux soit garantie.
* L'Attribute Controller est un RAMDAC amélioré.
* Le ''Sequence Controller '' communique avec le RAMDAC et le CRTC pour accéder à la mémoire vidéo.
* Le circuit restant, le ''Graphics Controller'', sert d'interface entre le bus et la mémoire vidéo.
Les quatre composants précédents sont collés entre eux par un paquet de circuits qui servent d'interface entre eux. Une carte EGA contient de nombreux registres, multiplexeurs et autres circuits. Voici ce que donne l'ensemble en schéma :
[[File:EGA-Karte.svg|centre|vignette|upright=2|Carte EGA.]]
Les cartes EGA intégraient 64 kilo-octets de mémoire vidéo sur la carte elle-même, mais on pouvait ajouter une seconde carte d'extension pour passer à 256 kilo-octets. Certaines résolutions ne fonctionnaient que si cette carte d'extension mémoire était présente.
===Le multi-GPU EGA/CGA===
Les cartes EGA pouvaient fonctionner de concert avec une seconde carte CGA, MDA ou Hercules. Un PC de l'époque pouvait avoir une carte EGA sur un port d'extension, et une carte MDA/CGA sur un autre. Quand une carte CGA est installée, la carte EGA ne fonctionne qu'en mode monochrome. Par contre, quand la seconde carte est une carte MDA ou Hercules, la carte EGA ne fonctionne qu'avec les résolutions à 4 ou 16 couleurs. En clair, elle prend en charge l'affichage monochrome avec les cartes CGA (polychromes), coloré avec les cartes MDA monochromes.
L'utilisation d'une carte EGA avec une autre carte MDA/CGA s'explique par une histoire d'entrée-sorties mappées en mémoire. Pour rappel, des adresses mémoires, censées adresser la mémoire RAM, sont détournées pour adresser la mémoire vidéo. Il en est de même pour les registres de configuration de la carte graphique : des adresses mémoire sont détournées pour pointer vers ces registres. Si on regarde l'ensemble des adresses gérées par le processeur, certaines pointent vers la RAM, d'autres vers la carte graphique, d'autres vers des périphériques, d'autres vers le BIOS, etc. Les cartes EGA et les cartes MDA/CGA détournent des adresses différentes. La mémoire des PC de l'époque était découpée en blocs de 64 kilo-octets, qui étaient alloués comme suit :
{| class="wikitable" style="text-align:center;"
|+ Espace d'adressage en mode réel
|-
! Numéro du bloc !! Contenu du bloc
|-
| 0 || rowspan="10" class="f_rouge" | Mémoire RAM, mémoire conventionnelle
|-
| 1
|-
| 2
|-
| 3
|-
| 4
|-
| 5
|-
| 6
|-
| 7
|-
| 8
|-
| 9
|-
| 10 || class="f_vert" | Mémoire vidéo des cartes EGA
|-
| 11 || class="f_vert" | Mémoire vidéo des cartes MDA ou CGA
|-
| 12 || class="f_vert" | ROM d'extension des périphériques (XT, EGA, 3270 PC)
|-
| 13 || class="f_gris" | Autre, non-réservé
|-
| 14 || class="f_gris" | Autre, non-réservé
|-
| 15 || class="f_jaune" | ROM du BIOS
|}
==Le standard VGA==
Le standard VGA fait suite au standard EGA et gère des résolutions jusqu'à 640 par 480. Il intègre aussi un support du ''double buffering'', à savoir qu'il gère deux ''framebuffers'' séparés. Mais le ''double buffering'' n'est possible que dans les basses résolutions, car il n'y a pas assez de mémoire vidéo pour les deux ''framebuffers'' dans les hautes résolutions. Le VGA intègre aussi d'autres fonctionnalités qui seront détaillées dans le chapitre suivant, notamment pour ce qui est du défilement horizontal/vertical.
===Les résolutions supportées par le VGA===
Le standard VGA décrit plusieurs '''modes de fonctionnement''', chacun correspondant à une résolution avec un nombre de couleur associé. Ils sont numérotés en hexadécimal, les modes 1 à 16 (0xFh) correspondent aux résolutions supportées par les cartes MDA, CGA et EGA, les quatre modes restants sont spécifiques au VGA. D'autres modes non-standards sont supportés par la majorité des cartes VGA, comme le mode X, un mode non-officiel qui définit une résolution de 320 par 240 pixels, avec 256 couleurs par pixel.
Suivant le mode utilisé, le ''framebuffer'' sera soit de type compact, soit de type planaire ! Les modes gérant 256 couleurs par pixel utilisent un ''framebuffer'' compact, avec un octet par pixel. Par contre, les modes 16 couleurs codent un pixel sur 4 bits, ce qui colle mieux avec un ''framebuffer'' planaire à quatre ''bitplanes''. Les modes 16 couleurs sont surtout utilisés avec des résolutions élevées, alors que les modes 256 utilisent des résolutions basses, pour des raisons de mémoire vidéo. Par exemple, une résolution de 640 par 480 fait qu'une image contient 307 200 pixels, ce qui prend au minimum 300 kilo-octets en mode 256 couleurs ! Or, les cartes graphiques VGA disposaient de seulement 256 kilo-octets de mémoire vidéo. Vous devinez que la carte graphique ne supportait pas 256 couleurs par pixels dans cette résolution, mais qu'elle codait un pixel sur moins d'un octet.
Le mode le plus utilisé est le mode 13h, d'une résolution de 320 par 200 pixels pour 256 couleurs par pixel. Il a été utilisé par la grosse majorité des jeux vidéos de l'époque, car son ''framebuffer'' compact était beaucoup plus pratique pour les programmeurs, malgré des performances moindres que les autres modes VGA. Quelques jeux vidéos ont cependant utilisé le mode 11h avec son ''framebuffer'' planaire, comme l'a fait DOOM 1 et sa suite DOOM 2. Cependant, les autres jeux basés sur le même moteur utilisaient le mode 13h, l'exemple typique étant celui des FPS Heretic et Hexen de Raven Software.
{|class="wikitable"
|-
! Mode !! Résolution !! Couleurs par pixel || Framebuffer
|+ Modes principaux
|-
! mode 10h
| 640 par 350 pixels || 4 à 16 couleurs par pixel (dépend de la RAM vidéo installée) || ''framebuffer'' planaire
|-
! mode 11h
| 640 par 480 pixels || Monochrome, 2 couleurs par pixel || ''framebuffer'' planaire
|-
! mode 12h
| 640 par 480 pixels || 16 couleurs par pixel || ''framebuffer'' planaire
|-
! mode 13h
| 320 par 200 pixels || 256 couleurs par pixel || ''framebuffer'' compact
|}
===L'organisation du ''framebuffer''===
La mémoire vidéo fait 256 kilo-octets, répartis dans 4 mémoires RAM de 64 kibioctets chacune. En mode ''framebuffer'' compact, un pixel fait un octet. Chaque octet d'une banque correspond à un pixel.
{|class="wikitable"
|+ VGA en mode ''framebuffer'' compact/linéaire
|-
! Octet banque 1
| Pixel 1, codé sur 8 bits
|-
! Octet banque 2
| Pixel 2, codé sur 8 bits
|-
! Octet banque 3
| Pixel 3, codé sur 8 bits
|-
! Octet banque 4
| Pixel 4, codé sur 8 bits
|}
Avec un ''framebuffer'' planaire, les pixels sont codés sur 4 bits. Idéalement, chaque ''bitplane'' doit être une mémoire bit-adressable, à savoir que chaque adresse mémoire sélectionne un bit. Mais il s'agit là d'un cas idéal que les cartes VGA en respectaient pas. A la place, chaque ''bitplane'' est une mémoire adressable par octet, et non par bit. Heureusement, elles intégraient des circuits pour simuler une mémoire bit-adressable à partir d'une mémoire adressable par octet.
Les quatre banques sont accessibles en parallèles, ce qui permet de lire/écrire 4 octets en même temps, un octet par banque. Dans le mode ''framebuffer'' planaire, les 4 octets lus sont découpés en 8 pixels de 4 bits d'un coup. Le DAC situé en aval de la mémoire vidéo recevait 8 pixels de 4 bits chacun, et mémorisait le tout dans une petite mémoire tampon. Il fournissait, en sortie un pixel par cycle d'horloge. Une seule lecture dans la mémoire vidéo permettait au DAC d'afficher 8 pixels pour une seule lecture en mémoire vidéo.
{|class="wikitable"
|+ VGA en mode ''framebuffer'' planaire
|-
! !! Pixel 1 !! Pixel 2 !! Pixel 3 !! Pixel 4 !! Pixel 5 !! Pixel 6 !! Pixel 7 !! Pixel 8
|-
! Octet banque 1
| 0 || 0 || 1 || 1 || 0 || 0 || 1 || 1
|-
! Octet banque 2
| 1 || 1 || 1 || 1 || 1 || 0 || 1 || 1
|-
! Octet banque 3
| 0 || 1 || 1 || 1 || 0 || 0 || 0 || 0
|-
! Octet banque 4
| 0 || 0 || 1 || 0 || 1 || 0 || 1 || 1
|}
===Le registre intermédiaire : les VGA ''latches''===
Pour gérer au mieux les lectures et écritures, une carte VGA intègre un '''registre intermédiaire''' de 32 bits qui sert d'intermédiaire pour les lectures et écritures. Lors d'une lecture, un octet est lu dans chaque ''bitplane'' et les 4 octets lus sont alors enregistrés dans ce registre intermédiaire. Là, les 32 bits sont alors soit envoyé au RAMDAC pour être affichés à l'écran, soit envoyés au processeur lors d'une lecture.
[[File:VGA, lecture dans la mémoire vidéo.png|centre|vignette|upright=2|VGA, lecture dans la mémoire vidéo]]
Pour les écritures, le registre intermédiaire est utilisé pour diverses opérations. Par exemple, supposez que le programmeur ne veut modifier qu'un seul pixel codé sur 4 bits. Dans ce cas, il doit lire 32 bits, ne modifier que le pixel dans ces 32 bits, et écrire le résultat en mémoire vidéo. Les bits à modifier sont précisés dans un '''registre de masque''', le ''Bit Mask Register''. Il fait 32 bits, soit la même taille que le registre intermédiaire, et chaque bit du registre de masque correspond à un bit du registre intermédiaire. Si le bit en question est à 1, le bit à écrire provient du registre intermédiaire. Sinon, il vient du pixel envoyé par le CPU.
Pour appliquer le masque, le registre intermédiaire est complété par un '''circuit de masquage''', qui permettent de ne modifier que certains bits dans les 32 bits. Le circuit de masquage est concrètement un ensemble de multiplexeurs qui permettent de choisir, pour chaque bit à écrire dans une ''bitplane'', s'il vient du registre intermédiaire, ou des données envoyées par le processeur. Les multiplexeurs sont commandés par un registre de masque.
Le VGA fournit deux modes de lecture et quatre modes d'écriture. Pour les écritures, il y a quatre modes nommés mode 0, 1, 2 et 3, que nous détaillerons dans la suite. L'utilisation des circuits de masquage dépend du mode d'écriture. Pour simplifier, il y a trois cas distincts.
* Dans le mode d'écriture numéro 0, le processeur envoie un nouveau pixel par cycle. Le masque est mémorisé dans un registre et est stable d'une écriture sur l'autre. Il s'agit du fonctionnement intuitif, où le processeur écrit dans le ''framebuffer''.
* Dans le mode d'écriture numéro 3, c'est l'inverse. Le processeur envoie un nouveau masque par cycle, alors que le pixel à écrire est mémorisé dans un registre et reste le même d'une écriture sur l'autre. Ce mode sert à remplir une zone de l'écran avec une même couleur, que ce soit pour tracer des lignes, colorier une figure géométrique, remettre à zéro le ''framebuffer''.
* Dans le mode d'écriture numéro 1, le circuit de masquage est configuré pour recopier le registre intermédiaire dans la RAM directement. Il sert pour les copies mémoire-mémoire, à savoir copier des données de la mémoire vidéo vers un autre endroit.
[[File:Circuit de masquage du VGA.png|centre|vignette|upright=2.5|Circuit de masquage du VGA]]
===Les circuits annexes : ALU, SET/RESET et ''barrel shifter''===
Les circuits de masquage en question sont complétés par une petite unité de calcul 32 bits, séparée du circuit de masquage, qui gère les opérations logiques OR, AND, XOR. La différence est que le circuit de masquage combine un masque stocké dans un registre dédié avec le registre intermédiaire, alors que l'ALU combine les données envoyées par le CPU et le registre intermédiaire.
Toute carte VGA permet aux écritures de n'écrire que dans certaines ''bitplanes''/banques et de laisser les autres tranquilles. Pour cela, il incorpore un registre de 4 bits, le ''Map Mask register'', qui indique quelles banques doivent être activées lors d'une écriture. Il y a un bit par banque, qui indique si elle est éteinte ou allumée lors d'une écriture, seules les banques allumées se font écrire dedans. Notons qu'avec cette technique, les ''bitplanes'' désactivées conservent les données précédentes. Ce n'est pas un problème si on souhaite appliquer des effets graphiques bien précis après avoir rendu une image complète dans le ''framebuffer'' planaire : on calcule une image, puis on applique l'effet en manipulant une ou deux ''bitplanes''. Mais cela pose problème dans d'autres situations. Si jamais une image a été rendue précédemment, ses pixels resteront dans les ''bitplanes''. Désactiver les écritures lors du rendu de l'image suivante donnera un mélange entre image précédente et image nouvelle.
Pour éviter cela, il faut ajouter une fonctionnalité qui remet à 0 le ''framebuffer'', octet par octet. La remise à zéro en question peut effacer totalement le ''framebuffer'', à savoir les quatre ''bitplanes'', en remplissant ses octets avec la valeur 0000 0000. Il est aussi, possible de réinitialiser le ''framebuffer'' en mettant chaque octet à 1111 1111. Mais il est aussi intéressant de ne réinitialiser qu'une partie des ''bitplanes'', par exemple seulement une, deux ou trois ''bitplanes''. Pour cela, la carte VGA incorpore un '''circuit de SET/RESET''' avant l'ALU. Il agit sur les octets envoyés par le processeur et destinés aux ''bitplanes'', à savoir qu'il peut les mettre soit à 0000 0000, soit à 1111 1111, soit ne pas les modifier. La commande de ce circuit est réalisée par deux registres dédiés.
Enfin, cartes VGA intègrent un ''barrel shifter'', qui fait des opérations de rotations sur l'octet envoyé par le processeur (les décalages ne sont pas supportés). Il était rarement utilisé pour aligner les données au pixel près, plutôt qu'à l'octet près. Mais il était peu utilisé, car le processeur sait faire ce genre d'opérations nativement. L'ALU et le ''barell shifter'' sont commandés par un registre unique, le ''Data Rotate register'', qui indique quelle opérations ils doivent faire. Le contenu de ce registre est découpés en deux champs : un qui dit au ''barell shifter'' de combien de rangs il faut rotater, et quelle opération l'ALU doit faire.
===Les modes d'écriture VGA===
La différence entre les modes d'écriture 0, 1, 2 et 3 tient à l'activation ou non des circuits de SET/RESET, de masquage, et autres.
Le '''mode d'écriture 0''' est celui où tous les circuits sont utilisables. Ils ne sont pas forcément tous utilisés, mais peuvent l'être. Les données envoyées par le CPU sont rotatées (ou non), puis passent par le circuit de SET/RESET (qui peut ne rien faire), puis dans l'ALU pour être combinées avec le registre intermédiaire (qui peut simplement recopier l'octet qu'elle recoit), le résultat est masqué (ou non) et enregistré dans les ''bitplanes'' actives, certaines pouvant être désactivées.
[[File:VGA latches.png|centre|vignette|upright=2.5|VGA latches]]
Le '''mode d'écriture numéro 1''' est utilisé pour faire des copies en mémoire vidéo, de la mémoire vidéo vers elle-même. Ce mode d'écriture se contente de recopier le contenu du registre intermédiaire en mémoire vidéo, les octets envoyés par le processeur ne sont pas utilisés s'ils sont présents. On peut l'émuler à partir du mode 0 en configurant les circuits de masquage proprement.
Il faut noter que le registre intermédiaire maintien une donnée indéfiniment et quelques jeux vidéos ou applications utilisaient cette propriété. Par exemple, cela permet de remplir un ''framebuffer'' avec un arrière-plan, avant de re-dessiner dessus. L'idée est d'écrire le motif d'arrière-plan en mémoire vidéo, de le lire pour charger le registre intermédiaire avec, et de lancer une série d'écriture en mode 1 pour recopier le motif dans les adresses mémoires voulues.
[[File:VGA write mode 1.png|centre|vignette|upright=2.5|VGA write mode 1]]
Le '''mode d'écriture numéro 2''' est lui un mode de remplissage. Il permet d'écrire une couleur dans plusieurs pixels consécutifs, dans un ''framebuffer'' planaire. Au lieu de modifier chaque pixel un par un, avec des opérations de masquage, l'idée est alors de modifier plusieurs pixels consécutifs en une seule fois. On peut modifier jusqu'à 8 bits consécutifs en mémoire vidéo, vu que les ''bitplanes'' sont des mémoires adressables à l'octet. C'est très utile pour remplir le ''framebuffer'' avec une couleur par défaut, pour le réinitialiser/effacer, ou encore pour tracer des segments/lignes horizontales ou diagonales, par exemple.
Dans ce mode d'écriture, les circuits de SET/RESET sont au premier plan. Pour comprendre pourquoi, regardons ce qui se passe quand on écrit une même couleur dans 8 pixels consécutifs. Pour simplifier, on part du principe que les 8 pixels sont tous dans le même octet, on dit qu'ils sont alignés sur un octet. La couleur écrite est codée naturellement sur 4 bits, et vaut 0101.
{|class="wikitable"
|+ VGA en mode ''framebuffer'' planaire
|-
! !! Pixel !! Pixel 1 !! Pixel 2 !! Pixel 3 !! Pixel 4 !! Pixel 5 !! Pixel 6 !! Pixel 7 !! Pixel 8
|-
! Octet banque 1
! 0
| 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0
|-
! Octet banque 2
! 1
| 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1
|-
! Octet banque 3
! 0
| 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0
|-
! Octet banque 4
! 1
| 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1
|}
On voit que chaque bit du pixel a été recopié à l'identique dans un octet. Les octets écrits dans le ''framebuffer'' planaire valent donc soit 0000 0000, soit 1111 1111. Vous aurez deviné que c'est un cas parfait pour utiliser les circuits de SET/RESET, qui fournissent naturellement les octets 0000 0000 et 1111 1111 sur leur sortie. En clair, chaque bit d'un pixel commande l'octet envoyé sur la ''bitplane'' correspondante. Par exemple, si le bit de poids faible vaut 0, alors l'octet à écrire dans la ''bitplane'' associée vaut 0000 0000. Pour le dire autrement, l'octet envoyé par le processeur, le pixel envoyé, commande des circuits de SET/RESET : ses 4 bits de poids faible indiquent quels ''bitplanes'' sont à mettre à 0000 0000 et celles à mettre à 1111 1111.
Les octets mis à 0000 0000 ou 1111 1111 passent ensuite dans l'ALU et les circuits de masquage et sont enregistrés dans les ''bitplane'' actives. Les circuits de masquage sont actifs au cas où certaines zones de l'image ne doivent pas être remplies avec le pixel. Par exemple, imaginez qu'on souhaite remplir un carré avec une couleur par défaut, en utilisant le mode d'écriture 2. Dans ce cas, le masque désigne quels pixels sont dans le carré et ceux en-dehors.
[[File:VGA write mode 2.png|centre|vignette|upright=2.5|VGA write mode 2]]
Le '''mode d'écriture numéro 3''' est lui aussi utile pour faire du remplissage ou pour tracer des lignes, mais aussi pour écrire du texte transparent. Son principe est le suivant : on écrit une couleur par défaut dans le ''framebuffer'' dans certains pixels seulement, un masque décrit quels bits/pixels modifier pour ne pas effacer ce qui doit l'être. La différence est qu'avec le mode 3, chaque octet provenant du processeur est un nouveau masque à appliquer. Le mode 2 précise la couleur des pixels à modifier avec un masque préconfiguré, le mode 3 précise quel masque utiliser avec une couleur par défaut préconfigurée.
Là-encore, les circuits de SET/RESET sont programmés pour dessiner une couleur par défaut. Sauf que cette fois-ci, la couleur est précisée en configurant les registres adéquats, elle n'est pas fournie par l'octet provenant du processeur. Là encore, les circuits de masquage permettent de choisir entre ce qui est déjà dans le ''framebuffer'' planaire, et la couleur par défaut. La différence est que le masque est fournit par l'octet envoyé par le processeur.
Pour donner un exemple d'utilisation, prenez le cas où on veut écrire du texte au-dessus d'une image déjà calculée dans le ''framebuffer''. L'écriture du texte se fait caractère par caractère, chaque caractère étant un octet envoyé par le processeur à la carte VGA. Le processeur lit d'abord les pixels sur lesquels écrire le caractère, afin de les copier dans le registre intermédiaire. Puis, il écrit le caractère avec le mode 3 : le calcul du masque donne les pixels à modifier.
[[File:VGA write mode 3 simplifié.png|centre|vignette|upright=2.5|VGA write mode 3 simplifié]]
Précisons cependant que le masque n'est pas l'octet envoyé par le processeur. A la place, le masque est calculé en prenant le masque présent dans le registre de masque, puis en faisant un ET logique avec l'octet envoyé par le processeur. L'octet est potentiellement rotaté avant, pour l'aligner au pixel près, mais c'est facultatif.
[[File:VGA write mode 3.png|centre|vignette|upright=2.5|VGA write mode 3]]
===Les modes de lectures VGA===
Pour la lecture deux modes sont possibles : un plutôt associé à un ''framebuffer'' planaire, le second adapté à un ''framebuffer'' compact.
Le premier mode de lecture, le '''''read mode 0''''' lit les octets d'une ''bitplane'' un par un. La ''bitplane'' en question est sélectionnée en configurant deux bits du ''Read Map register'' : 2 bits, 4 valeurs, 4 ''bitplanes''. Le processeur configure ce registre et peut adresser la ''bitplane'' directement. Par contre, il n'accède qu'à une ''bitplane'' à la fois. Lire un pixel entier demande de changer de ''bitplane'' quatre fois de suite, ce qui a un cout en performance très important. Le cout pour lire 8 pixels est le même, ce qui amorti la situation, mais sans faire de miracles.
Idéalement, il faudrait un mode de lecture qui simule un ''framebuffer'' compact, par exemple un mode qui permette de lire un pixel en allant piocher dans les 4 ''bitplanes'', voire un mode qui permette de lire 8 pixels d'un seul coup. Mais les cartes VGA ne fournissent pas de mode de lecture aussi pratique. Par contre, ils ont un mode plus limité, appelé le '''''read mode 1'''''.
Il lit 8 pixels dans un ''framebuffer'' planaire et les compare à une couleur de référence, stockée dans le registre '' Color Compare Register''. Le résultat est un groupe de 8 bits, chacun stockant le résultat de la comparaison pour un pixel. Lors de la comparaison, il est possible de désactiver des ''bitplanes''. Un registre, le ''Color Don’t Care register'', permet de configurer quelles ''bitplanes''a activer ou désactiver. Il contient un bit par ''bitplane'', qui permet d'activer la ''bitrplane'' s'il est à 1, la désactiver s'ils est à 0.
===Le RAMDAC des cartes VGA===
Les cartes VGA utilisent un système de palette indicée et fournissent en sortie une couleur analogique. Pour cela, elles incorporent une mémoire SRAM pour mémoriser la palette indicée, ainsi qu'un convertisseur numérique vers analogique pour traduire le tout en signaux analogiques pour l'écran. Les deux circuits sont regroupés en un seul, dans un circuit unique appelé un RAMDAC, pour rappel. Le RAMDAC d'une carte VGA est cependant plus complexe qu'un RAMDAC normal, et ce pour une raison simple : le nombre de couleurs dépend de la résolution. Il est de 2, 4, 16, 256 couleurs. Les résolutions à 2, 4 et 16 couleurs sont gérés à par du mode 256 couleurs, et une partie est liée à la compatibilité EGA.
Pour commencer, regardons d'abord le mode 256 couleurs. Dans ce mode, la mémoire vidéo fournit un octet, qui indique quelle couleur afficher. L'octet est alors envoyé à un RAMDAC principal, qui convertit cette couleur 8 bits en signaux analogiques RGB. La conversion se fait en deux temps. La première étape est le passage par une palette indicée, qui traduit l'octet en entrée en une couleur codée sur 18 bits : 6 bits pour le rouge, 6 pour le vert, 6 pour le bleu. La SRAM de la palette indicée prend un octet sur l'entrée d'adresse et contient 256 registres de 18 bits. Les 18 bits sont alors envoyés aux circuits de conversion analogique, pour obtenir la couleur finale. Il y a donc un '''RAMDAC 8 bits''', qui sert de RAMDAC principal.
La gestion des modes 2, 4, 16 couleurs se fait en utilisant le RAMDAC 8 bits. Avec ces modes, la couleur est codée sur 4 bits, et est lue depuis un ''framebuffer'' planaire. Les 4 bits sont alors étendus pour passer vers 8 bits, qui sont ensuite envoyés au RAMDAC 8 bits. La conversion vers l'analogique se fait donc en deux étapes : une première étape traduit un pixel de 4 bits en un octet, la seconde étape prend cet octet et le transforme en signaux analogiques. Le passage de 4 à 8 bits se fait en gardant la compatibilité avec les résolutions des cartes EGA, qui utilisaient une palette indicée spécifique.
Les cartes EGA avaient une palette indicée très simple, qui contenait 16 registres, chacun contenant une couleur codée sur 6 bits. Elle prenait en entrée un pixel codé sur 4 bits, qui adressait un des 16 registres de couleur. Il est possible d'émuler ce système en envoyant les 4 bits du pixels directement dans le RAMDAC 8 bits, en remplacant les 4 bits de poids fort manquant par des zéros. Une solution alternative découpe la palette de 256 couleurs du RAMDAC en 16 sous-palettes de 16 couleurs. Les 4 bits de poids fort décident quelle sous-palette utiliser, les 4 de poids fiable sélectionne une couleur dans la palette choisie. Les 4 bits pour sélectionner la palette proviennent du d'un registre appelé le ''Color Control Register''
[[File:RAMDAC des cartes VGA en mode 16 couleurs pures.png|centre|vignette|upright=2|RAMDAC des cartes VGA en mode 16 couleurs pures]]
Néanmoins, il s'agit d'une émulation de la palette EGA. Les cartes VGA gérent un mode de compatibilité EGA avec un système de ''color paging'' qu'on va décrire ci-dessous. Les cartes VGA conservent la palette EGA, ce qui fait qu'il y a une première phase qui transforme un pixel en 6 bits, ce qui permet de coder 64 couleurs différentes. Pour obtenir l'octet à envoyer au RAMDAC 8 bits, les cartes VGA rajoutent deux bits à la couleur EGA, qui proviennent du ''Color Control Register''.
Pour implémenter les deux modes d'écrit plus haut, les cartes VGA se débrouillent avec le système suivant. Les 4 bits de poids faible proviennent de la platte indicée, toujours. Les deux bits de poids fort proviennent du ''color control register''. Les deux bits entre les deux viennent soit du ''color control register'', soit de la palette EGA, un MUX choisit entre les deux. Le choix entre les deux dépend du bit 7 du registre de configuration ''AC Mode Register''. Dans le cas où les deux bits de poids fort sont remplacés par le ''color control register'', la palette du RAMDAC est découpée en 16 palettes indicées de 4 bits.
[[File:RAMDAC des cartes VGA.png|centre|vignette|upright=2|RAMDAC des cartes VGA]]
===Le mode écran divisé (''splitscreen'')===
Les cartes VGA incorporent des optimisations pour supporter l'affichage en '''''splitscreen''''', appelé en français affichage en "écran divisé", ou affichage divisé. Le standard VGA ne supportait qu'un affichage divisé simple : l'écran était coupé en deux à l'horizontale, avec une moitié haute et une moitié basse. L'écran n'était pas forcément coupé en deux parties égales, la ligne de démarcation entre les deux écrans était 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.
Dans le chapitre "Les systèmes à framebuffer", nous avions vu comment un ''splitscreen'' de ce type est optimisé sur certains CRTC. Les cartes VGA utilisent des techniques similaires, mais avec cependant quelques différences. L'implémentation du ''splitscreeen'' demande d'avoir deux ''framebuffers'' : un pour la moitié haute de l'écran, un pour la moitié basse. Le standard VGA/EGA impose que le ''framebuffer'' de la moitié basse démarre à l'adresse zéro en mémoire vidéo. Le ''framebuffer'' pour la moitié haute est lui placé plus loin en mémoire vidéo. C'est contreintuitif, mais c'est en réalité tout à fait normal.
L'idée se comprend bien quand on se rappelle de ceci : le CRTC parcours le ''framebuffer'' en partant d'une adresse de base, qui lui est fournie dans un registre dédié. Le parcours du ''framebuffer'' se fait grâce à l'aide de deux registres, un compteur de ligne et un compteur de colonne. Les trois registres sont utilisés pour calculer l'adresse du pixel à afficher. L'idée est que quand la ligne de démarcation est atteinte, les trois registres sont réinitialisés, ils sont tous trois mis à zéro. Ce qui fait re-démarrer l'affichage à partir de l'adresse zéro. Et cela implique que la moitié basse de l'écran commence à l'adresse 0. La moitié haute doit donc être placée plus loin en mémoire, assez loin pour laisser assez de place à la moitié basse.
La ligne de démarcation est censée être mémorisée dans un registre de ''splitscreen'', qui indique à quelle ligne il faut commuter de ''framebuffer''. Mais les cartes EGA et VGA n'ont pas de registre dédié pour. A la place, les différents bits de ce registre sont dispersés dans plusieurs registres séparés. Les cartes EGA utilisaient une valeur de 9 bits dispersée dans deux registres : les bits 7 à 0 sont dans le ''Line Compare register'' (''CRTC register 18H''), le 9ème bit est dans le registre ''Overflow register'' (''CRTC register 7''). Les cartes VGA rajoutent un 10ème bit, qui est dans le registre ''Maximum Scan Line register'' (''CRTC register 9''). Toute modification de ces registres a un effet immédiat, contrairement à une modification de la plupart des autres registres du CRTC.
Le support du ''splitscreen'' était présent pour toutes les résolutions officielles, mais quelques cartes VGA le supportaient aussi pour les résolutions non-officielles. Il fonctionne aussi en mode texte ! En mode texte, le passage d'une moitié d'écran à l'autre a le bon ton d'ajouter un saut de ligne automatique, ce qui fait que le texte du second écran reprend au bon endroit, au tout début d'une ligne. Pour cela, le compteur de ligne est réinitialisé à zéro.
==Le PC-98 de NEC==
Les ordinateurs PC-98 sont une gamme d'ordinateurs japonais qui a grandement évolué dans le temps. Ils s'inspiraient grandement des PC IBM et étaient centrés autour d'un processeur x86. Le standard IBM PC était globalement respecté sur les PC-98 et le PC_98 tournait sur MS-DOS, mais il y avait cependant de nombreuses différences avec les PC x86. La carte d'affichage était totalement différente, le bus ISA était remplacé par un C-bus, le BIOS était différent, les adresses des périphériques étaient différentes, etc.
Les PC-98 ont été déclinés en plusieurs versions, chacune ayant un processeur de plus en plus puissant, plus de mémoire RAM, plus de mémoire vidéo, etc. La première génération utilisait un processeur x86 8086 d'Intel, c'était celle du PC-9801. La seconde génération a démarré avec le PC-9801VM. Leurs capacités graphiques étaient différentes, comme on va le voir.
===Le support des caractères japonais===
Le support des caractères japonais demande beaucoup de contraintes. Afficher des Kanjis demande d'utiliser une mémoire de caractère très grosse, capable de mémoriser plusieurs centaines ou milliers de caractères, dont des Kanjis, hiragana et katakana. Concrètement, la mémoire de caractère des PC-98 mémorisait près de 700 caractères, qui suivaient les standards JIS X 0201 et JIS X 0208.
De plus, afficher des kanjis demande d'utiliser des hautes résolutions, au moins 640 par 400, pour que les kanjis soient lisibles. La mémoire vidéo devait être compatible avec de tells résolution. Sur les PC_98, la mémoire vidéo faisait entre 12 et 256 kilo-octets, ce qui était beaucoup pour l'époque, mais était nécessaire pour supporter les hautes résolutions.
<noinclude>[[File:Charset.gif|centre|vignette|upright=2|Charset contenant des caractères Japonais, en basse résolution. Différent de celui du PC_98.]]</noinclude>
===Le double rendu texte-graphique===
[[File:PC-9801VM memory map.svg|vignette|upright=0.75|PC-9801VM memory map]]
Le ''framebuffer'' graphique était de type planaire et intégrait 4 ''bitplanes'', comme le VGA. A partir du PC-9801VM ajoutait de quoi lire/écrire en parallèle dans plusieurs ''bitplanes'', grâce à l'ajout d'un circuit nommé ''Graphics Charger''. C'est aussi à partir de ce modèle que la mémoire de caractère et une palette indicée reconfigurable ont été ajoutés. Le PC-9801VX améliora le ''Graphic Charger'' pour ajouter un ''blitter''.
En général, une carte d'affichage fonctionne soit en mode texte, soit en mode graphique, pas les deux. Les ordinateurs de la gamme PC-98 permettaient de fusionner les deux. Pour cela, ils étaient structurés autour de deux VDC NEC μPD7220 : un qui fonctionnait en mode texte, un autre en mode graphique. Les deux VDC lisaient dans des mémoires vidéos séparées. Le VDC fonctionnant en mode graphique était relié à une mémoire vidéo dédiée, le VDC en mode texte était quant à lui relié à une ''text RAM'' qui servait de tampon de texte. La ''text RAM'' faisait entre 8 et 12 kilo-octets, suivant les modèles, alors que la mémoire vidéo faisait 96, 192 ou 256 kilo-octets. Le ''framebuffer'' était d'abord rendu en mode graphique, et le second VDC pouvait superposer du texte au-dessus.
[[File:N88-BASIC(86) color text with graphics.png|centre|vignette|upright=2|Rendu graphique sur les PC-98]]
<noinclude>{{NavChapitre | book=Les cartes graphiques
| prev=Les Video Display Controler atypiques
| prevText=Les Video Display Controler atypiques
| next=Le rendu d'une scène 3D : concepts de base
| nextText=Le rendu d'une scène 3D : concepts de base
}}{{autocat}}
</noinclude>
noso6d5g8g05ve4vgxozok9fjxwiulw
Les cartes graphiques/Les écritures en VRAM hors ROPs
0
83807
774466
764892
2026-10-08T14:52:15Z
Mewtow
31375
/* Les instructions atomiques des compute shaders */
774466
wikitext
text/x-wiki
Le chapitre précédent nous a expliqué comment les ROPs mettaient à jour le ''framebuffer'' et le tampon de profondeur. Cependant, les ROPs ne sont pas les seuls à écrire en mémoire vidéo. Les ''compute shaders'', ''vertex shaders'' et ''pixel shaders'' peuvent écrire des données en mémoire vidéo, dans des tableaux dédiés. Et n'allez pas croire que c'est un petit sujet. La majeure partie des jeux vidéos actuels utilisent des techniques de rendu différé qui ne fonctionneraient pas sans cela ! Et ne parlons pas des ''compute shaders'', qui ne serviraient à rien sans cela. Dans ce chapitre, nous allons détailler comment se font ces écritures en mémoire.
: Notons que nous mettons de côté les architectures ''sort-middle'' et les GPU à rendu en ''tile'' dans ce chapitre.
==Les fonctionnalités de ''Multiple Render Targets''==
Il faut noter que les API modernes permettent à un ''pixel shader'' d'écrire dans plusieurs ''render-target''. On parle alors de '''''Multiple Render Targets''''', abrévié en MRT. L'implémentation classique est que le ''pixel shader'' peut écrire son résultat dans une ou plusieurs textures. Précisons qu'il s'agit bien du ''pixel shader'' qui écrit dans une texture. Suivant le GPU, l'écriture dans les ''render target'' peut ou non passer par les ROPs. Sur les anciennes versions de DirectX, un seul ''render target'' passait par les ROPs, mais pas les autres. De nos jours, tous les ''render target'' passent par les ROPs. Il est même possible de configurer le mélange ''alpha'' différemment suivant le ''render target''.
<noinclude>[[File:MultiRenderTarget.svg|centre|vignette|upright=3|''Multiple Render Target''.]]</noinclude>
===L'éclairage différé===
Le MRT accélère fortement les techniques d''''éclairage différé''', qui séparent les calculs d'éclairage par pixel dans une passe séparée du reste. Ils fonctionnent en deux passes, au minimum. La première calcule toutes les informations nécessaires pour les calculs d'éclairage par pixel. Elle calcule les normales, la couleur diffuse, de quoi calculer les réflexions spéculaires, et d'autres informations du genre. Le tout est mémorisé dans plusieurs "textures" séparées, qui sont lues par un '''pixel shader''' pour obtenir l'image finale. On parle aussi de rendu différé au lieu d'éclairage différé, mais c'est un détail.
La première passe calcule la couleur de chaque pixel, la profondeur de chaque pixel, et la normale de la surface pour chaque pixel. Dans l'implémentation la plus simple, ces informations sont écrites dans trois "textures" : une qui contient la couleur diffuse, une autre pour la profondeur, et une autre pour les normales. Il faut alors supporter des pseudo-''framebuffer'' pour chaque "texture". Ils sont appelés des '''''G-buffer''''', ce qui est l'abréviation de tampon géométrique.
{|
|[[File:Deferred rendering pass col.jpg|thumb|''G-buffer'' pour la couleur.]]
|[[File:Deferred rendering pass dep.jpg|thumb|''G-buffer'' pour la profondeur.]]
|[[File:Deferred rendering pass nor.jpg|thumb|''G-buffer'' pour les normales.]]
|[[File:Deferred rendering pass res.jpg|thumb|Image finale]]
|}
L'intérêt de l'éclairage différé est d'exécuter le ''pixel shader'' une fois par source de lumière et par pixel. Il y a donc un ''draw call'' par source de lumière, qui lit les ''g-buffer''. La conséquence est que le rendu différé utilise généralement un shader par type de sources de lumières, qu'elles soient ponctuelles, directionnelles, etc. Pour comparer, sans éclairage différé, l'éclairage est réalisé autrement.
L'implémentation la plus simple fait les calculs d'éclairage une fois par source et lumière et par objet, avec un ''draw call'' par objet et par source de lumière. On parle alors de '''rendu direct multi-passe'''. La géométrie d'un objet est donc recalculée plusieurs fois, une fois par source de lumière. Par contre, le ''pixel shader'' est généralement assez simple, car on peut utiliser un ''shader'' différent pour les lumières ponctuelles, directionnelles, etc. Mieux que ça : on a tendance à avoir un ''shader'' par ''material'', vu qu'on a généralement un seul ''material'' par objet, les exceptions étant rares. Le cout en termes de performances est alors le suivant : des ''pixels shaders'' très spécialisés et optimisés, mais des calculs géométriques redondants.
Une implémentation plus optimisée utilise un seul ''draw call'' par objet. Le ''draw call'' reçoit une liste des sources de lumière. Le ''pixel shader'' lit les sources de lumière une par une dans cette liste, et calcule l'éclairage une source de lumière à la fois, jusqu'à obtenir la couleur finale du pixel. La différence est qu'on a un ''draw call'' par objet, et non un par objet et source de lumière. On parle alors de '''rendu direct simple passe'''.
Les calculs géométriques redondants sont éliminés, mais le ''pixel shader'' perd sa spécialisation. Au lieu d'utiliser plusieurs ''pixel shaders'' spécialisés, on utilise un ''über shader'' qui supporte tous les types de sources de lumières et tous les ''materials''. La conséquence est qu'un ''über shader'' tend à être moins bien optimisé, car il utilise plus de registres. Cela est cependant compensé par le fait que le ''pixel shader'' fait peu d'accès mémoire, comparé au rendu différé. Il a beau utiliser plus de registres, ce n'est pas tellement un problème vu qu'il y a moins d'accès mémoire à masquer.
Le rendu différé a donc de nombreux avantages et désavantages. Un avantage est que le rendu différé se marie bien avec les effets graphiques de type ''screen space'', comme certaines formes d'occlusion ambiante. Ces effets ont besoin de connaitre la profondeur de chaque pixel, qui est fournie sur un plateau dans un ''g-buffer''. Par contre, la transparence n'est pas prise en charge, de même que l'antialiasing de type MSAA. Il y a aussi des conséquences en termes de performances, car lire les ''g-buffer'' n'est pas gratuit. Le cout en bande passante mémoire, est totalement absent dans le rendu direct, qui se contente de lire les textures des objets. Au final, le rendu différé fait un compromis différent du rendu direct : il échange du temps de calcul contre de la bande passante mémoire et contre la VRAM occupée par les ''g-buffer''.
Un autre désavantage est que tous les objets doivent faire leurs calculs d'éclairage avec le peu d'informations présentes dans le ''g-buffer''. Ils doivent donc se débrouiller avec une couleur diffuse, spéculaire, les normales et quelques informations comme l'émissivité. En clair, plusieurs ''materials'' partagent des BRDFs très similaires, voire identique. Le rendu direct n'a absolument pas ce problème et peut utiliser un ''material'' différent par objet, voire plusieurs ''materials'' différents sur un même objet. Ils seront simplement rendus dans des ''draw call'' séparés.
===Une prépasse z implicite===
Les avantages et désavantages de l'éclairage différé sont très nombreux. L'un d'entre eux est que l'on n'a pas d'''overdraw'' : seuls les pixels visibles sont éclairés. Ce n'est pas forcément le cas avec le rendu direct (''forward rendenring''), où des pixels sont éclairés par un ''pixel shader'' et enregistrés dans le ''framebuffer'', avant d'être écrasé par la suite. On dit qu'il y a un certain ''overdraw'', d'autant plus important que les pixels invisibles éclairés sont nombreux. Les techniques d'élimination des pixels cachés limitent la casse, mais elles ne fonctionnent à la perfection que si l'on trie les triangles du plus proche au plus lointain, ce qui n'est jamais fait en pratique.
Il est cependant possible d'éliminer tout ''overdraw'' avec le rendu direct, en utilisant une pré-passe z (abordé dans le chapitre sur le ROP). D'ailleurs, le rendu différé effectue une pré-passe z implicitement. En réalité, l'éclairage différé est une sorte de pré-passe z sous stéroïde, qui ne fait pas que calculer le tampon de profondeur final, mais calcule aussi les normales et d'autres informations fournies par la rastérisation. De plus, le rendu différé ne recalcule pas la géométrie dans la seconde passe, alors que le rendu direct avec pré-passe z va le faire.
Par contre, cette pré-passe z implicite est à l'origine de plusieurs défauts de cette technique. Déjà, l'usage d'une prépasse z n'est pas compatible avec l'usage de la transparence. Le rendu différé ne fonctionne que pour les objets opaques. La seule solution est de traiter les objets opaques à part des objets transparents. Les objets opaques sont rendus dans une première passe utilisant le rendu différé, alors que les objets transparents sont rendus dans une seconde passe qui utilise le rendu direct.La prépasse z désactive aussi l'antialiasing, même si je ne peux pas expliquer pourquoi ici.
===L'implémentation sur les GPU modernes===
Sans MRT, le rendu différé est très compliqué à implémenter. Chaque ''g-buffer'' demande une passe de rendu, chacune re-calculant la géométrie. Par contre, avec MRT, les différents ''g-buffer'' sont tous calculés en une seule passe, sans recalculer la géométrie plusieurs fois.
Il est possible d'ajouter d'autres textures pour d'autres informations, comme des textures pour les paramètres de la lumière spéculaire (couleur spéculaire, illumination). Par contre, on est rapidement limité en termes de nombres de textures. En effet, les GPU modernes supportent entre 4 à 8 textures maximum en sortie pour le MRT. Un ''pixel shader'' a donc accès à 4 ''g-buffer'', rarement plus. Pour une compatibilité maximale, les programmeurs se limitent à 4 textures. De plus, utiliser plus de textures aurait un impact en matière de consommation mémoire, et de bande passante.
Pour limiter la casse, il est possible de regrouper des informations séparées dans un seul ''g-buffer'', pour gagner de la place. Par exemple, la couleur diffuse est une couleur RGB codée sur 24 bits, là où une texture utilise souvent des pixels RGBA codés sur 32 bits. Il est alors d'utiliser l'octet pour la composante A pour stocker une autre information, comme une information de métallicité (utilisée pour calculer la couleur spéculaire).
Un autre défaut du MRT est que son usage désactive de nombreuses optimisations intégrées dans les ROPs, comme le ''z-fast pass''. De telles optimisations demandent en effet que les ROPs n'écrivent que dans un seul ''render target'' : le tampon de profondeur. LA vitesse des ROPs est alors doublée, sans compter que la bande passante mémoire est mieux utilisée. Mais dès qu'on active le MRT, ces optimisations sont désactivées. En comparaison, elles sont activées lors d'une prépasse z normale.
Enfin, le MRT désactive automatiquement l'antialiasing matériel. Précisément, c'est l'antialiasing de type ''multisampling'' (MSAA) qui est désactivé, même s'il est possible d'utiliser du ''supersampling'' ou un antialiasing par post-traitement. En conséquence, l'usage de l'éclairage différé désactive totalement le MSAA.
==La fonctionnalité de ''stream output''==
Une fonctionnalité des ''geometry shaders'' est la possibilité d'enregistrer leurs résultats en mémoire. Il s'agit de la fonctionnalité du '''''stream output'''''. On peut ainsi remplir une texture ou le ''vertex buffer'' dans la mémoire vidéo, avec le résultat d'un ''geometry shader''. Notons que celle-ci mémorise un ensemble de primitives, pas autre chose. Cette fonctionnalité est utilisée pour certains effets ou rendu bien précis, mais il faut avouer qu'elle n'est pas très souvent utilisée. Aussi, les concepteurs de cartes graphiques n'ont pas optimisé cette fonctionnalité au maximum. Le ''stream output'' n'a généralement pas accès prioritaire à la mémoire, comparé aux ROP, et n'a souvent accès qu'à une partie limitée de la bande passante mémoire.
Notons qu'il existe deux formes de ''stream output'' : une qui permet aux ''vertex shader'' d'écrire dans une texture, l'autre qui permet aux ''geometry shaders'' de le faire. Notons que le ''stream output'' fournit un flux de primitives, pas de sommets, même pour le flux sortant d'un ''vertex shader''. En clair, beaucoup de sommets sont dupliqués et ont n'a pas d{{'}}''index buffer''. Les résultats du ''stream output'' sont donc assez lourds et prennent beaucoup de mémoire.
[[File:Stream output.png|centre|vignette|upright=2.5|Stream output]]
==Les instructions atomiques des ''compute shaders''==
Dans le chapitre précédent, nous avions parlé des ROPs, et notamment de pourquoi on ne peut pas les émuler dans un ''shader''. Le problème est que plusieurs instances de shaders peuvent lire une même donnée, faire un traitement, et la remplacer par leur résultat. Et l'ordre entre ces instances de shaders n'est pas garanti. Le même problème peut survenir dans un ''compute shader'', à savoir un shader utilisé pour du GPGPU.
Un ''compute shader'' peut accéder à un ou plusieurs tableaux en mémoire, voire des structures de données plus complexes. Mais restons sur des tableaux. Les tableaux en question sont appelés des ''Unordered Access Views'', leur nom indiquant qu'aucune garantie n'est donnée sur l'ordre des écritures. Il n'y a pas de tri entre instances de shaders pour que les shaders écrivent leurs données dans leur ordre de lancement. À la place, les écritures dans un ''compute shader'' sont immédiates. Les shaders peuvent lire et écrire dans la mémoire vidéo dans le désordre. Le problème est qu'il y a des données qui ne doivent pas être traitées par plusieurs instances d'un ''compute shader'' à la fois.
Heureusement, les ''compute shaders'' disposent d'un mécanisme pour éliminer ce problème. La solution en question est similaire à ce qu'on trouve sur les architectures multicœurs, qui sont confrontées au même problème. Il y a des données qui ne doivent pas être traitées par plusieurs ''threads'' à la fois. La solution est d'ajouter au processeur des '''instructions atomiques''', qui lisent, modifient et écrivent une donnée, sans qu'aucune instruction ne puisse les déranger. De telles instructions empêchent tout accès mémoire à la donnée manipulée, tant qu'elles ne sont pas terminées. Le terme atomique veut dire qu'elles donnent l'impression de s'exécuter en une seule fois, elles ne donnent pas l'impression d'associer une lecture, une opération et une écriture séparées.
Les GPU modernes ont des instructions atomiques, utilisables dans les ''shaders''. Par contre, leur implémentation matérielle est différente. Les CPU multicœurs rendent leurs instructions atomiques par divers mécanismes. Le plus simple bloque totalement le bus mémoire, pour empêcher aux autres cœurs d'accéder à la mémoire RAM. Le cout en performance est alors énorme. Une solution alternative utilise les mécanismes de cohérence des caches. L'idée est de bloquer certaines lignes de cache, afin qu'aucun autre cœur ne puisse lire ou écrire la ligne de cache réservée. Mais les GPU ne peuvent pas faire cela.
Les méthodes précédentes fonctionnent quand on a un bus mémoire partagé entre 2-4 cœurs, mais ont un cout en performance trop important au-delà. Aussi, les GPUs font autrement. Ils exécutent les instructions atomiques en dehors du processeur de shader ! À la place, chaque cache est associé à une '''unité atomique''' (''atomic unit''), qui exécute les opérations atomiques directement dans le cache, en dehors des processeurs de shader. Pour exécuter une instruction atomique, les processeurs de shaders envoient l'instruction aux unités atomiques, qui les exécutent directement dans le cache. Les unités atomiques peuvent recevoir plusieurs instructions atomiques en même temps, mais elles forcent l'exécution des instructions atomiques à se faire une par une.
<noinclude>
{{NavChapitre | book=Les cartes graphiques
| prev=Les Render Output Target
| prevText=Les Render Output Target
| next=Le support matériel du lancer de rayons
| nextText=Le support matériel du lancer de rayons
}}{{autocat}}
</noinclude>
gxg6j3ygk2r43wkfmf9ud2cz26bw6bf
Mathc complexes/0af
0
84557
774465
774339
2026-10-08T14:50:13Z
Xhungab
23827
774465
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/a277#Le spectre en module et Le spectre de phase :| Application]]
==Exemple 1==
Voir : [[Mathc complexes/0a2|La TFD sans les matrices de l'exemple]]
* Soit '''le signal''' : s[n] = { +2,+4-1, +3 }
* Sa '''transformée de Fourier discrète''' : S(K) = {+8,+3-i,-6 ,+3+i }
* '''Le spectre de module''' : |S(K)| = {+8,sqrt(10), +6 ,sqrt(10) }
* '''le spectre en phase''' : arg S(K) = {+0 rad, -0.322 rad, PI rad ,sqrt(10) }
{| class="wikitable"
|+ Résumé :
|-
! k !! s[n] !! S(k) !! Module !! Phase
|-
| 0 || +2 || '''+8''' || +8 || +0 rad
|-
| 1 || +4 || +3-i || sqrt(10) = 3,162 || -0.322 rad
|-
| 2 || -1 || '''-6''' || +6 || PI rad
|-
| 3 || +3 || +3+i || sqrt(10) = 3,162 || 0.322 rad
|}
{{AutoCat}}
cuqheg0u4vyzqckbwrjhb59btg6yhqk
Mathc initiation/a9b
0
84561
774463
2026-10-08T14:43:53Z
Xhungab
23827
news
774463
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a594| Sommaire]]
{{Partie{{{type|}}}|Calculons les amplitudes An et les phases φn}}
'''Pour l'harmonique n, on peut calculer son amplitude An = sqrt(an^2 + bn^2)'''
'''Pour l'harmonique n, on peut calculer sa phase φn = arctan2(bn/an)'''
----
'''Calculons l'argument du point P(an,bn) avec atan()'''
θ = atan( b/ a)
| '''o (+3,+2) : θ = atan(+3/+2)'''
o (-3,+2) | o (+3,+2) o (-3,+2) : θ = atan(-3/+2) +PI =-atan(3/2)+PI
-----------------|--------------------- '''o (-3,-2) : θ = atan(-3/-2) -PI = atan(3/2)-PI'''
o (-3,-2) | o (+3,-2) o (+3,-2) : θ = atan(+3/-2) =-atan(3/2)
| o (+3,+0) : θ = 0
o (-3,0) --------|------------o (+3,0) o (-3,+0) : θ = +PI
|
o (0,+3) o (+0,+3) : θ = +PI/2
------------------|--------------------- o (+0,-3) : θ = -PI/2
o 0,-3)
|
{{AutoCat}}
gx92khzz0f6h9j19daad9i298uph8bf
774464
774463
2026-10-08T14:44:33Z
Xhungab
23827
774464
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a594#Calculons les amplitudes An et les phases φn| Sommaire]]
{{Partie{{{type|}}}|Calculons les amplitudes An et les phases φn}}
'''Pour l'harmonique n, on peut calculer son amplitude An = sqrt(an^2 + bn^2)'''
'''Pour l'harmonique n, on peut calculer sa phase φn = arctan2(bn/an)'''
----
'''Calculons l'argument du point P(an,bn) avec atan()'''
θ = atan( b/ a)
| '''o (+3,+2) : θ = atan(+3/+2)'''
o (-3,+2) | o (+3,+2) o (-3,+2) : θ = atan(-3/+2) +PI =-atan(3/2)+PI
-----------------|--------------------- '''o (-3,-2) : θ = atan(-3/-2) -PI = atan(3/2)-PI'''
o (-3,-2) | o (+3,-2) o (+3,-2) : θ = atan(+3/-2) =-atan(3/2)
| o (+3,+0) : θ = 0
o (-3,0) --------|------------o (+3,0) o (-3,+0) : θ = +PI
|
o (0,+3) o (+0,+3) : θ = +PI/2
------------------|--------------------- o (+0,-3) : θ = -PI/2
o 0,-3)
|
{{AutoCat}}
q439eoxrltl8jcrxylvasz22lg5v560
774470
774464
2026-10-08T23:19:31Z
Xhungab
23827
774470
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a594#Calculons les amplitudes An et les phases φn| Sommaire]]
{{Partie{{{type|}}}|Calculons les amplitudes An et les phases φn}}
'''Pour l'harmonique n, on peut calculer son amplitude An = sqrt(an^2 + bn^2)'''
'''Pour l'harmonique n, on peut calculer sa phase φn = arctan2(bn/an)'''
----
'''Calculons l'argument du point P(an,bn) avec atan()'''
θ = atan( b/ a)
| '''o (+3,+2) : θ = atan(+2/+3)'''
o (-3,+2) | o (+3,+2) o (-3,+2) : θ = atan(+2/-3) +PI =-atan(2/3)+PI
-----------------|--------------------- '''o (-3,-2) : θ = atan(-2/-3) -PI = atan(2/3)-PI'''
o (-3,-2) | o (+3,-2) o (+3,-2) : θ = atan(-2/+3) =-atan(2/3)
| o (+3,+0) : θ = 0
o (-3,0) --------|------------o (+3,0) o (-3,+0) : θ = +PI
|
o (0,+3) o (+0,+3) : θ = +PI/2
------------------|--------------------- o (+0,-3) : θ = -PI/2
o 0,-3)
|
{{AutoCat}}
0cky9aiayu8fy3tjs72w9wmm4wo3cqh
774472
774470
2026-10-09T08:53:40Z
Xhungab
23827
774472
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a594#Calculons les amplitudes An et les phases φn| Sommaire]]
{{Partie{{{type|}}}|Calculons les amplitudes An et les phases φn}}
'''Pour l'harmonique n, on peut calculer son amplitude An = sqrt(an^2 + bn^2)'''
'''Pour l'harmonique n, on peut calculer sa phase φn = arctan2(bn/an)'''
----
'''Calculons l'argument du point P(an,bn) avec atan()'''
θ = atan( b/ a)
| '''o (+3,+2) : θ = atan(+2/+3)'''
o (-3,+2) | '''o (+3,+2)''' o (-3,+2) : θ = atan(+2/'''-3''') +PI ='''-'''atan(2/3)+PI
-----------------|--------------------- '''o (-3,-2) : θ = atan(-2/-3) -PI = atan(2/3)-PI'''
'''o (-3,-2)''' | o (+3,-2) o (+3,-2) : θ = atan('''-2'''/+3) ='''-'''atan(2/3)
| '''o (+3,+0)''' : θ = 0
o (-3,0) --------|----------- '''o (+3,0)''' o (-3,+0) : θ = +PI
|
'''o (0,+3)''' '''o (+0,+3)''' : θ = +PI/2
------------------|--------------------- o (+0,-3) : θ = -PI/2
o 0,-3)
|
{{AutoCat}}
c1pl17tj3fo41ggqdjrdqwhe74fnbmv