codefordumm!ies: the cool logo

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 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...