============================================================================= *** La programmation graphique en C++ *** --------------------------------- - Shaun Doré 21/09/97 - Mode vidéo VGA ============================================================================= CHAPITRE 1 -- Mode MCGA 320x200x256 Bienvenue dans ce premier chapitre! Si ce n'est pas déjà fait, je vous recommande très fortement de lire mon introduction sur cette série de didacticiel. Ce premier chapitre traite des notions de bases de la programmation graphique : la carte VGA et sa programmation, comment se positionner en mode graphique, afficher des pixels à l'écran, le chaînage linéaire en mode 13h. Le tout sera démontrer dans mon exemple, avec un "starfield", c'est à dire un effet très simple que l'on peu réaliser à partir des fonctions vues. Pour la plupart des cours, je me servirai du compilateur Turbo C++ 3.1 de Borland. En tout temps, si vous avez des questions il me fera plaisir de tenter d'y répondre au meilleur de mes capacités, car n'oublier pas que ces cours sont dispensés par quelqu'un qui lui aussi est entrain d'apprendre. ** NOTIONS DE BASE ** ============================================================================= La programmation graphique sous DOS, avec les outils fourni avec les compilateurs de Borland, n'est pas chose facile. Si vous avez déjà essayer par vous-même de coder des applications graphiques avec les BGI (Borland Graphics Interface), avec des compilateurs tels Turbo C++ ou Turbo Pascal, vous avez du remarquer que non seulement ils ne sont pas adéquoient pour un mode 256 couleurs, mais qu'il sont extremement lents (pour des besoins spécifiques). En plus, les fonctions graphiques ne sont pas optimisées et la documentation laisse beaucoup à désirer. Que faire? Plutôt que de passer par les BGIs, nous allons utiliser directement les fonctions du BIOS VGA pour se mettre en mode graphique. Mais avant tout, il serait utile de connaître la carte VGA(Video Graphic Array) et ses composants. Pour ceux qui ne connaisse pas ce qu'est le BIOS, disons simplement qu'il permet d'accèder directement au matériel (carte vidéo, disquettes, disques durs, interfaces série/ parallèles, clavier, horloge, etc), et qu'il est composé d'un éventail de fonctions standardisées. Nous allons en reparler plus en détail dans la section suivante. ** LA CARTE VGA ET SES COMPOSANTES ** ============================================================================= Par leurs possibilités d'affichage graphique et texte, les cartes EGA/VGA surclassent largement ses prédécesseurs. Lorsque IBM a introduit cette carte en 1987, ils ont offert du même coup une carte souple et facilement programmable, et un mode confortable pour la programmation graphique, le 320x200 en 256 couleurs. Ce mode porte le numéro 13h, c'est à dire 13 en hexadécimal (19 en décimal). Voici la liste de quelques résolutions offertes: Numéro du mode | Résolution et couleurs --------------------------------------- 0Dh 320x200 en 16 couleurs 0Eh 640x200 en 16 couleurs 0Fh 640x350 en monochrome 10h 640x350 en 16 couleurs 11h 640x480 en 2 couleurs 12h 640x480 en 16 couleurs 13h 320x200 en 256 couleurs Cette liste ne comporte que les modes standards. Il est interessant de savoir qu'il existe des modes VESA, qui eux poussent encore plus loin les résolutions, jusqu'a 1024x768, et offre encore plus de couleurs. Le mode 13h, que nous utiliserons, peut se programmer lui-aussi. On peut obtenir ainsi des résolutions de 320x400, émuler un mode de couleur 16bit et écrire sur plusieurs pages de mémoires en même temps. Pour l'instant, nous n'allons pas voir ces modes, mais il est important de savoir qu'ils existent. Les cartes EGA/VGA se composent de 4 contrôleurs qui se répartissent les tâches liées à la génération du signal vidéo. Concrètement, il s'agit de: * Contrôleur CRT * Contrôleur d'attributs * Contrôleur graphiques * Séquenceur * Convertisseur digital en analogique (DAC) digital-analog converter Pour completer le tout, quelques registres généraux envoient eux aussi des informations sur l'état de la carte. Ces régistres sont presque tous programmables, bien que certains soient extrêmement délicats à manipuler. En particulier, le contrôleur CRT, pour Cathode Ray Tube, pilotant la création des signaux vidéo et de synchonisation pour le retour de balayage horizontal et vertical du canon à électrons. Un mauvais usage pourrait causer des problèmes et même endommager le moniteur. Mais la plupart sont manipulables, et je vous avertirai si jamais quelque chose pourrait s'avèrer dommageable. ** LE BIOS ET LES REGISTRES DU PROCESSEUR ** ============================================================================= Maintenant qu'on sait que la carte VGA se compose de différents contrôleurs et qu'on connait quels modes vidéos le standard VGA accepte, il est temps de concrètement utiliser ces registres pour se positionner en mode graphique. Pour ce faire, il existe des fonctions gravés dans le BIOS de notre ROM qui nous permet d'activer le mode désiré. Ces fonctions utilisent naturellement les registres du processeur. Petit rappel ici: l'architecture centrale d'un ordinateur se compose d'un microprocesseur. Ce processeur se compose de trois types de registres : Les registres généraux, les registres de segment et le registre de flag. Dans la programmation système, et surtout en assembleur, la connaissance de ces registres est impérative. Je n'entrerai pas dans les détails maintenant, mais ceux qui désirent plus d'informations peuvent aller lire le chapitre 6. En attendant voici un tableau résumant l'organisation des registres du processeur: Registres généraux 16 Bit ------------------------- AX Accumulateur (16 bits, mais se sépare en deux de 8 bits) AH Octet de poids fort AL Octet de poids faible BX Base (16 bits, mais se sépare en deux de 8 bits) BH Octet de poids fort BL Octet de poids faible CX Compteur (16 bits, mais se sépare en deux de 8 bits) CH Octet de poids fort CL Octet de poids faible DX Données (16 bits, mais se sépare en deux de 8 bits) DH Octet de poids fort Octet de poids faible DI Destination Index SI Source Index SP Pointeur de pile BP Pointeur de base Registres de Segment ------------------------ DS Segment de données ES Extra segment CS Segment de code SS Segment de pile Donc, je sais que tout cela semble très compilqué, mais croyer moi sur parole, tout va devenir beaucoup plus clair avec la pratique. Une fois mis en contexte, nous sommes plus à même d'assimiler la matière! En parlant, il est maintenant temps de programmer un peu. ** PROGRAMMATION EN C DU MODE VIDÉO 13h ** ============================================================================= Nous allons maintenant nous lancer dans le vif du sujet, c'est à dire la programmation en tant que telle. Comme nous avons vu, les registres et le BIOS font le travail pour nous en ce qui concerne le mode graphique. Pour se faire, nous n'avons qu'a invoquer la fonction qui nous interesse, par son numéro. En regardant dans la liste complètes des fonctions, on retrouve celle qui permet de fixer le mode vidéo: Interruption 10h, Fonction 00h Écran: Fixer le mode vidéo Entrée: AH = 00h AL = xxh Sortie: Aucune Donc, si on comprend bien, il suffit d'inscrire le mode vidéo dans le registre AL et d'actionner l'interruption 10h! N'oubliez pas que AL est la partie faible du registre AX. Bon, concrètement, voici comment on fait, en C... void SetVGA(void) { _AX = 0x0013; geninterrupt (0x10); } Le code se comprends en lui-même. En C, la macro _AX correspond au registre qui nous interesse. Par exemple, si nous aurions eu besoins de DI, la macro _DI aurait suffi. La fonction geninterrupt reçoit en paramètre le numéro d'interruption matérielle requise, dans notre cas celui de la carte vidéo, donc l'interruption 10h. Noter que "0x" précede le nombre pour indiquer que nous somme en base 16. Pour les curieux, le même code s'écrit aussi facilement en assembleur: void SetVGA(void) { asm { mov ax, 0x0013 int 0x10 } } Nous voilà en mode graphique 320x200 par 256 couleurs. Rien de très impressionnant pour l'instant j'avoue, surtout avec la technologie d'aujourd'hui qui nous permet des affichages en 1024x768 en milliards de couleurs. Mais comme il faut commencer quelque part, nous nous contenterons pour l'instant de ce modeste mode. De la même façon, on retourne en mode texte par cette méthode: void SetText(void) { _AX= 0x0003; geninterrupt(0x10); } Vous l'aviez deviné, non? Maintenant, il est temps d'afficher quelque chose. Commençons par un bon vieux pixel, c'est à dire un point. Pour ce faire, il existe, comme dans le cas précédant, une fonction dans le ROM pour afficher un point graphique. Cependant, ces fonctions ont recours aux interruptions, qui fonctionnent par définitions sur une base intermittente, c'est à dire par polling. Chaque périphérique est à l'écoute du Bus et attend à chaque cycle s'il est sollicité. Ce procedé est dans notre cas beaucoup trop lent. C'est donc pourquoi nous allons écrire directement dans la mémoire notre pixel. En mode 13h, l'écran se compose de 320 colonnes et de 200 rangés, numérotée respectivement de 0 à 319, et de 0 à 199. Donc, il existe 64000 positions possibles pour un pixel, et chaque pixel peut se composer de 256 couleurs (donc 8 bit par pixel ou bpp). La mémoire alloué pour une page est donc de 64k. Les pixels n'y sont pas représenté dans un plan cartésien, mais plutôt chaîné de façon linéaire, un après les autres. Pour l'ordinateur, il n'y a pas de position (100,50), mais plutôt la somme de 50 rangée de 320 colonnes, plus 50 colonnes. Donc, la formule pour déterminer la position d'un pixel est la suivante: postion == x+(y*320) Dans le cas prédécent, le pixel se situe à la position 16100, ce qui équivaut à notre (100,50) dans notre plan cartésien. Maintenant, voyons examiner le code nécessaire à une telle opération: void Putpixel (int x, int y, unsigned char col) { memset(vga+x+(y*320),col,1); } La fonction Putpixel reçoit en paramètre x et y, nos coordonées, ainsi que col, la couleur désiré qui elle est représenté par un caractère non signé. Un char signé se situe entre -127 et 128, non signé entre 0 et 255, qui correspond au nombre de couleurs en mode 13h, 256 (ne pas oublier le 0). La fonction memset, qui est définie dans la librairie standard mem.h, possède l'architecture suivante: memset (adresse de départ , valeur , nombre de bit) Dans notre exemples, "vga" est en fait un pointeur sur l'adresse de départ de la mémoire VGA, qui se situe juste au dessus de la mémoire conventionnelle, à l'adresse A000:0000. Petit rappel: une adresse mémoire se compose de segment et d'offset. Chaque segment mesure 64k. L'offset est le déplacement que l'on effectue dans ce même segment. Sous DOS, un PC ne peut adresser plus qu'un segment de 64k à la fois. Donc, memset part du début de la mémoire VGA, se déplace de (x+(y*320), car pour chaque y, on doit multiplier par 320, pour descendre sur la rangée voulue. Ensuite, on ajoute x pour se déplacer vers la droite la rangée. Le bit en question est assigné une valeur de couleur col, et le nombre de bit concerné est 1 (pour 1 pixel). Par exemple, si on remplacait le dernier paramètre par 20, on obtiendrais un ligne horizontale de 20 pixel! Une autre application qui nous provient de ce principe : vider l'écran. En effet, en se positionnant au tout début de la mémoire VGA, et en remplacant le dernier paramètre par FFFFh (=65536 = 64k), nous vidons l'écran de son contenu: void Cls (unsigned char col) { memset(vga,col,0xffff); } Avant de terminer, il nous reste à voir le fameux pointeur! Un pointeur, comme on le sait déjà, est une variable qui contient l'adresse mémoire d'une autre variable, donc qui "pointe" dessus. char far *vga = (char far *) MK_FP(0xA000,0); La commmande MK_FP, définie dans la librairie dos.h, est pour "make far pointer", car un PC, en mode réel, ne peut adresser plus de 64k à la fois, utilisant des adresses de 16 bits, ce qui est un problème pour nous dans ce cas-ci, notre mémoire vidéo se situant à elle seule dans un segment. En scindant les adresses en deux, l'ordinateur peut se servir d'un adressage 24-bit, mais on doit alors compiler le code avec un "large memory model" -- j'explique tout cela dans le code source. Donc le pointeur "vga" est un pointeur de pointeur, qui contient 0xA000, c'est à dire le début de notre mémoire vidéo. Ce pointeur deviendra inutiles quand nous coderons en assembleur. Il est maintenant temps pour vous de jeter un coup d'oeuil au code source. Je crois qu'il est très simple et bien documenté (sinon, je m'en excuse!). Il démontre les notions vues aujourd'hui, avec un petit effet très sympathique bien qu'obsolete. Suggestions d'exercices: ------------------------ - Changer la direction des étoiles (haut vers le bas, etc.) - Faire en sorte que les étoiles viennent vers l'observateur - Changer la couleur des étoiles de façon aléatoire Merci! A la prochaine! Shaun "Krashlog" Doré 1998 http://pages.infinit.net/shaun adresse primaire : dores@videotron.ca adresse secondaire : sinking@hotmail.com "tout sur le binaire..."