> Graphisme
PrograZine issue #10 - http://www.citeweb.net/discase/10/ Edito Sommaire Contribution Contacts


Gestion du mode 13h
par Olivier Pecheux - opecheux@multimania.com
Initié C/Pascal
> fichiers attachés
gnu_13_c.c
gnu_13_pas.pas



    En mode non protégé, on peut accéder comme l'on veut à la mémoire écran et aux ressources des PC. Avec les compilateurs 32 bits gnu C et Pascal (en mode protégé), il est un peu plus difficile d'accéder directement aux ressources, mais c'est bien sûr possible. Cet article va montrer comment on peut faire en prenant l'exemple du mode 13h. Je supposerais connu l'accès de ce mode en C ou en Pascal, vu que cela à déjà été décrit dans prograzine. Pour souligner mon propos, je donnerais le source d'un petit programme permettant de "dessiner" un feu.

    Si seul le Pascal vous intéresse, je vous conseille de lire quand même la description des accès par le C en sautant les programmes, car dans bien des cas, le Pascal gnu fait appel aux fonctions du C. Si seul le C vous préoccupe, vous pouvez laisser de côté les parties réservées au Pascal.

    Notations: 10h représente pour moi la valeur 10 hexadécimale. Je peux aussi l'écrire 0x10 comme en C ou $10 comme en Pascal. Les expressions "TC" (Turbo C) et "TP" (Turbo Pascal) désignent les "vieux compilateurs" de Borland, du temps ou windows n'existait pas. Certains de ces compilateurs sont d'ailleurs gratuits maintenant.
>Passage en mode 13 en C gnu

    Pour passer en mode 13h, il suffit d'appeler l'interruption 10h (c'est le bios) en donnant au registre AX la valeur 13h. AH prend la valeur 00h (Fixer le mode vidéo) et AL reçoit 13h (le mode). Si au lieu de passer 13h, on passe 93h. le mode est le même, mais il n'y a pas d'effacement de l'écran. Je ne vois pas trop l'utilité de cet appel.

    En C gnu, la fonction qui permet l'appel de l'interruption est appelée __dpmi_int (int en TC), et elle se trouve déclarée dans le fichier d'entête dpmi.h. Ce n'est pas plus compliqué.

    Pour passer un registre à cette fonction, on le fait en utilisant une structure de registres appelée __dpmi_regs (même fichier d'entête). Cette structure est un peu plus complexe que celles que j'ai connue avec TC et TP, car il faut en plus préciser la taille du registre utilisé. Si je déclare une variable reg du type __dpmi_regs, le registre AX s'accède par reg.x.ax. Le x au milieu indique une taille de 16 bits (h pour les tailles de 8 bits comme AH, AL...). Les noms sont en minuscules.

    La fonction qui permet de passer en mode 13 en C gnu est:
void mode13(void)
{
  __dpmi_regs reg; // Déclare une variable registres
  reg.x.ax=0x13;  // Affecte AX
  __dpmi_int(0x10,®); // Appel du bios
}
l

    On complétera cette fonction un peu plus tard, pour initialiser le sélecteur (l'équivalent du segment en mode non protégé).
>Passage en mode 13 en Pascal gnu

    Se posent deux petits problèmes: je n'ai ni __dpmi_int, ni __dpmi_reg. Pour la fonction, ce n'est pas trop difficile: nous allons la voler aux librairies C. Il faut en plus changer son nom car le Pascal n'aime pas les noms commençant par la barre. J'ai pris Intr comme en TP.

    Il nous faut aussi voler la structure __dpmi_reg. Nous pouvons changer les noms, pourvu que les valeurs passées soient exactement aux mêmes endroits. J'ai voulu supprimer la nécessité de mettre la lettre x, sachant que l'on ne peut pas confondre AX et AH. J'ai pris le nom Registers comme en TP.

    Cela nous donne:
Type Registers=Record
       Case Byte Of
        0:{ Rggistres 32 bits }
           (EDI, ESI, EBP, Res0, EBX, EDX, ECX, EAX:Word);
        1: {Registres 16 bits }
           (DI, DI_hi, ... SS:ShortWord);
        2: { Registres 8 bits }
           (BL, BH, ... EAX_b3:Byte);
       End;

Procedure Intr(Vecteur:Integer; Var Registres:Registers); AsmName '__dpmi_int';

Procedure Mode13;
 Var Registres:Registers;
 Begin
  Registres.AX:=$13;
  Intr($10,Registres)
 End;
l

    Notez que dans la structure (elle est complète dans les sources) apparaît des endroits réservés, qui ne devrait pas être utilisés. Il y en a entre EBP et EBX (je ne sais pas pourquoi), mais la place occupée par les 16 bits de poids forts des registres 32 bits, n'ont pas de noms.
>Retour en mode texte

    Il suffit de remplacer 13 par 3. Il n'y a rien à rajouter.
>Ecrire ou lire un point à l'écran en C

    Le compilateur C nous donne la possibilité d'utiliser un sélecteur qui couvre toute la mémoire conventionnelle (de 00000000 à 000FFFFF). Avant toute chose, il faut appeler la fonction _farsetsel (positionne le sélecteur), en lui donnant comme paramètre _dos_ds qui est défini dans le fichier d'entête go32.h. Il faut donc mettre la ligne _farsetsel(dos_ds); en début de programme. Pour ne pas trop s'en soucier, je l'ai logé directement dans la fonction mode13. Je n'ai pas pris la précaution de vérifier si l'appel n'a pas déjà été fait (cas ou on appelle plusieurs fois mode13), mais vous pouvez le compléter.

    L'envoi proprement dit se fait avec la fonction _farnspokeb qui demande un entier 32 bits comme adresse et un unsigned char pour la valeur à écrire. L'adresse de la carte vidéo VGA est A0000, à laquelle on rajoute l'offset du début d'écran (le 320y+x). Ecrire un pixel se fait donc par la fonction putpixel définie par:
void putpixel(int x, int y, unsigned char couleur)
{
  _farnspokeb(0xA0000+x+((y<<2)+y)<<6,couleur)>
}
l

    J'ai utilisé un décalage de 2 crans et un de 6, ne sachant réellement ce qui est plus rapide.

    Pour lire un pixel à l'écran (ou en mémoire de façon générale), on utilise _farnspeehb qui, si on lui donne l'adresse, nous retourne, l'octet recherché:
unsigend char getpixel(int x, int y)
{
  return(_farnspeekb(0xA0000+x+((y<<2)+y)<<6))>
}
l
>Ecrire ou lire un point à l'écran en Pascal

    Encore une fois, il faut prendre les fonctions du C. J'en profite pour renommer _farnspoke en PokeB (Poke Byte). Le reste est identique:
Procedure FarSetSel(Selecteur:Word); AsmName '_farsetsel';
Procedure PokeB(Adresse:Word; Couleur:Byte); AsmName '_farnspokeb';
Function PeekB(Adresse:Word)::Byte; AsmName '_farnspeekb';


Procedure PutPixel(X,Y:integer; Couleur:Byte);
 Begin
  PokeB($A0000+x+((y shl 2)+y) shl 6,couleur);
 End;

Function GetPixel(X,Y:Integer):Byte;
 Begin
  GetPixel:=PeekB($A0000+x+((y shl 2)+y) shl 6));
 End;
l
>Accès à la palette de couleur en C

    Je viens de vous donner les moyens d'accéder à la mémoire des PC, il reste aussi à accéder aux ports d'entrée sortie. Prenons comme application la gestion de la palette.

    Pour changer une couleur, il suffit d'envoyer dans le port d'adresse 3C8h le numéro de la couleur, puis d'envoyer les trois composantes (dans l'ordre rouge - vert - bleu) l'une après l'autre à l'adresse 3C9h. Au passage, si on vient d'envoyer une couleur, la carte est déjà prête pour recevoir la suivante, sans que l'on soit obligé de donner le numéro de la couleur (incrémentation automatique de la couleur après trois envois). L'accès aux ports utilise la fonction outportb. Il n'y a rien de compliqué. setcolor envoie le numéro de la couleur et ses trois composantes, tandis que setcolorsuite n'envoie que les trois composantes:
void setcolor(unsigned char couleur, unsigned char rouge, 
              unsigned char vert, unsigned char bleu)
{
  outportb(0x3C8,couleur);
  outportb(0x3C9,rouge);
  outportb(0x3C9,vert);
  outportb(0x3C9,bleu);
}

void setcolorsuite(unsigned char rouge, 
              unsigned char vert, unsigned char bleu)
{
  outportb(0x3C9,rouge);
  outportb(0x3C9,vert);
  outportb(0x3C9,bleu);
}
l
>Accès à la palette de couleur en Pascal

    On va continuer de voler les fonctions du C. Elles existent en fait dans un librairie Port, mais je ne l'ai pas utilisée, car à l'heure ou j'écris (03/2000), il y a quelques problèmes pour compiler avec plusieurs unités.
Procedure OutPortB(Adresse:ShortWord; Valeur:Byte); AsmName 'outportb';
Function InPortB(Adresse:ShortWord):Byte; AsmName 'inportb';

Procedure SetColor(Couleur, Rouge, Vert, Bleu: Byte);
 Begin
  OutPortb($3C8,Couleur);
  OutPortb($3C9,Rouge);
  OutPortb($3C9,Vert);
  OutPortb($3C9,Bleu)
 End;

Procedure SetColorSuite(Couleur, Rouge, Vert, Bleu: Byte);
 Begin
  OutPortb($3C9,Rouge);
  OutPortb($3C9,Vert);
  OutPortb($3C9,Bleu)
 End;
l
>Le retour du spot en C

    Pour être complet, il faut aussi que j'aborde la lecture d'un port. Je vais prendre l'exemple de l'attente de la fin du balayage vertical. Il est recommandé si on souhaite faire des affichages rapides, attendre que le spot qui décrit l'écran s'éteigne et entame sa remontée. Si vous déplacez un objet, il se peut qu'il faille être synchrone avec le balayage pour éviter un scintillement.

    La procedure vsync va attendre que le spot entame le début de la remontée. Pour cela, la carte vidéo donne à l'adresse 3DAh un octet dont le bit de poids 8 indique si on descend (bit à 0) ou si on monte (bit à 1). Pour bien commencer au début de la montée et pas au milieu de celle-ci, on va attendre que le spot soit en train de descendre, puis ensuite qu'il soit en train de monter. La fonction est:
void vsync(void)
{
  do {} while (inportb(0x3DA)&8);
  de {} while (!(inportb(0x3DA)&8));
}
l
>Le retour du spot en Pascal

    C'est complètement calqué sur ce qui précède:
Procedure Vsync;
 Begin
  Repeat until (InPortB($3DA) And 8)=0;
  Repeat until (InPortB($3DA) And 8)>0
 End;
l
>Faire un feu

    Toutes ces explications ne seraient qu'un discours vide si je ne vous proposais pas une petite démonstration. Je suis donc passé en mode 13 pour vous offrir un petit feu. L'objectif de ceci n'est pas d'expliquer comment on fait du feu, mais de montrer une mise en oeuvre. Je ne vais donc pa détailler le programme de feu. Je vais toutefois vous donner le principe.

    Les couleurs de la palette doivent aller des couleurs obscures dans les rouges (haut de la flamme) pour les premiers numéros de couleur, puis aller dans les jaunes. En fait les couleurs au del a de 128 sont assez peu visibles et on moins d'importance. Une première partie du programme assigne la palette.

    Pour dessiner le feu, on dessine en bas une ligne au hasard. Cela se repère dans le programme par le commentaire "Ligne du bas". Les lignes au dessus sont crées avec un algorithme simple: le point de coordonné (X,Y) recevra à chaque tour la moyenne des 4 points en dessous diminués d'un (si la couleur n'est pas nulle). Ainsi, les numéros de couleurs vont décroître globalement. C'est la partie "Autres lignes". Les lignes sont calculées à partir du haut. Si on dessine dans l'autre sens, l'effet est différent (cela ressemble plus à un brûleur) et les modifications sont plus rapides. Il faudrait utiliser alors un buffer pour faire le calcul et afficher en synchronisme avec le balayage. En supposant la moyenne de la ligne du bas à 128 (valeurs au hasard entre 0 et 255), et étant donné que si on monte d'une ligne, la moyenne diminue de 1 (à cause de l'algorithme), on va avoir un dessin d'environ 128 lignes de haut. Pour éviter de calculer les lignes qui sont toujours à zéro, la partie repérée "voir si le dessin est monté" met à jour une variable hauteur qui indique la ligne à partir de laquelle se fait le dessin.
.
    Les sources des programmes sont disponibles pour le C gnu (gnu_13_c.c) soit pour le Pascal gnu (gnu_13_pas.pas).
Cet article est la propriété de Olivier Pecheux. La copie et la diffusion sont libres sauf dans un but lucratif sans accord explicite de l'auteur.