J'apprends la programmation avec l'assembleur x86, partie 7
Dans ce chapitre totalement passionnant, nous allons programmer un fondu de couleur du noir le plus total au blanc le plus éblouissant, comme le montre l'incroyable vidéo ci-dessous :
Et pour obtenir ce résultat à rendre jaloux Cecil B. DeMille, tels nos ancêtres explorateurs, nous allons utiliser les ports.
Mate le haut de chaque port !
Si on veut changer une des couleur de notre palette VGA, il va falloir spécifier à la carte vidéo de nouvelles composantes RGB (le rouge, le vert et le bleu, mais en anglais).
Il n'existe pas d'interruption pour effectuer cette opération, nous allons communiquer directement avec la carte graphique en modifiant des registres de celle-ci. Et pour pouvoir lire et écrire dans ces registres, les astucieux concepteurs du matériel informatique ont mis à notre disposition des ports.
Comme in out
Les opérations à effectuer pour communiquer avec un port sont :
- la sélection du port en mettant son numéro dans dx
- la lecture d'une valeur dans le registre al (pour un octet) avec l'instruction in
- l'écriture de la valeur du registre al (pour un octet) avec l'instruction out
La communication avec les ports se fait uniquement avec les registres dx et ax, le port est très possessif.
La syntaxe en assembleur est la suivante :
mov dx, 0x3da ; sélectionner le port 0x3da
in al, dx ; pour lire un octet du port sélectionné
out dx, al ; pour écrire un octet dans le port sélectionné
C'est un port tant...
Pour connaître les bonnes adresses de chaque ports, c'est à la charge du développeur de la découvrir en lisant la foultitude de documentation disponible sur le grand Internet.
Dans exemple, on souhaite modifier les composantes RGB de la couleur 0 de notre palette. Du coup le port que l'on va utiliser est le 0x03c8 qui permet de sélectionner l'index de la couleur à modifier :
mov dx, 0x03c8 ; registre d'écriture
mov al, 0 ; al est l'index de la couleur
out dx, al ; on veut écrire des valeurs pour la couleur 0
Puis, on va successivement écrire les valeurs R, G et B pour la couleur 0 dans le port 0x03c9 :
mov dx, 0x03c9 ; registre de données pour RGB
mov al, 0xff ; al est à 255
out dx, al ; valeur pour le rouge
out dx, al ; valeur pour le vert
out dx, al ; valeur pour le bleu
Et voilà, tous les pixels de notre écran qui ont la valeur 0 seront maintenant en blanc, R, G et B auront la valeur 255.
Le fond du port
Maintenant que l'on sait comment changer notre couleur, de quelle manière va-t-on pouvoir obtenir ce magnifique fondu du noir au blanc ?
En utilisant le grand pouvoir de la boucle !
Notre changement de palette sera placé dans la procédure init_palette qui prendra comme paramètre la valeur actuelle des composantes dans le registre bl, ce dernier sera incrémenté à chaque itération de la boucle. Une fois sa valeur maximale atteinte, soit 255, le registre prendra à nouveau la valeur zéro.
Notre boucle s'arrête quand l'utilisateur appuie sur une touche.
On s'emballait...
Si on exécutait notre programme tel quel, la vitesse de changement de couleur sera trop élevée pour obtenir l'effet désiré. Par contre ça nous procurerait une arme de destruction massive contre les épiléptiques.
Pour ralentir notre fondu, nous allons ainsi ajouter dans notre boucle la procédure wait_delay qui va utiliser les propriétés de rendu de la carte graphique.
En effet, quand l'affichage est effectué, chaque pixel est dessiné à l'écran en partant du haut à gauche jusqu'au bas à droite. Et ce procédé prend un certain temps, comme le dirait Fernand Raynaud. On parle ainsi de balayage, et l'idée est ici d'attendre qu'un cycle de balayage complet soit effectué avant de sortir de la fonction wait_delay. Pour un écran qui fonctionne à 60Hz, on aura donc un délai de 16,66 millisecondes !
Un test de bit...
Pour accéder à ces informations de balayage, on va communiquer avec la carte vidéo par l'intermédiaire du port 0x3da. La valeur retournée dans al contient un octet dont le troisième bit est à 0 si le balayage est en cours ou 1 dans le cas contraire.
Pour tester ce bit, on utilise l'instruction test suivi du saut jnz :
mov dx, 0x3da ; registre d'état de la carte vidéo
vsync_start:
in al, dx ; on lit la valeur du registre
test al, 0x08 ; si le 3 ème bit n'est pas à zéro
jnz vsync_start ; on attend le début du balayage vertical
... bien trop long
Toutes ces explications étaient bien longues, mais vous allez pouvoir maintenant saisir le code ci-dessous et profiter pleinement de cette animation digne des plus grands artistes !
Le code source de palette.asm
org 0x100
global main
VGA_MEMORY_SEGMENT equ 0xa000
VGA_WIDTH equ 320
VGA_HEIGHT equ 200
section .text
main:
call enter_vga
call fill_screen
xor bl, bl ; bl est la valeur de chaque composante RGB
call init_palette
animation:
mov cx, 3
wait_delay:
call wait_vsync ; on attend la fin du balayage d'écran
loop wait_delay
inc bl ; on incrémente la valeur des composantes RGB
call init_palette
mov ah, 0x01 ; on attend la saisie d'une touche
int 0x16 ; service 0x01 de l'interruption 0x16
jz animation ; si aucune touche n'est appuyée, on continue l'animation
call quit_vga
mov ax, 0x4c00
int 0x21
enter_vga:
mov ah, 0x0f ; on récupère le mode vidéo en cours
int 0x10 ; dans al
mov [old_mode], al ; on conserve cette valeur dans old_mode
mov ah, 0x00 ; on change le mode vidéo
mov al, 0x13 ; AL = 0x13 soit VGA 320x200x256 couleurs
int 0x10
ret
quit_vga:
mov ah, 0x00 ; on restaure le mode vidéo
mov al, [old_mode] ; conservé dans old_mode
int 0x10
ret
wait_vsync:
push ax ; on préserve la valeur de ax
mov dx, 0x3da ; registre d'état de la carte vidéo
vsync_start:
in al, dx ; on lit la valeur du registre
test al, 0x08 ; si le 3 ème bit n'est pas à zéro
jnz vsync_start ; on attend le début du balayage vertical
vsync_finish:
in al, dx, ; on lit la valeur du registre
test al, 0x08 ; si le troisième bit est à 1
jz vsync_finish ; on attend le retour du balayage
pop ax ; on restaure la valeur de ax
ret
fill_screen:
mov ax, VGA_MEMORY_SEGMENT
mov es, ax
xor di, di
xor ax, ax
mov cx, VGA_WIDTH * VGA_HEIGHT
rep stosb
ret
init_palette:
push ax ; on préserve la valeur de ax
mov dx, 0x03c8 ; registre d'écriture
mov al, 0 ; al est l'index de la couleur
out dx, al ; on veut écrire des valeurs pour la couleur 0
mov dx, 0x03c9 ; registre de données pour RGB
mov al, bl ; al est la nouvelle valeur des composantes
out dx, al ; valeur pour le rouge
out dx, al ; valeur pour le vert
out dx, al ; valeur pour le bleu
pop ax ; on restaure la valeur de ax
ret
section .data
old_mode: db 0
Pour m'entrainer
Modifier la procédure init_palette pour obtenir un fondu au bleu qui, l'on espère, ne sera pas trop odorant !
Solution
Et après...
Dans la partie 8, on va afficher des sprites en utilisant des masques de bits! Et ça n'a rien de pornographique...