J'apprends la programmation avec l'assembleur x86, partie 8
en développement
Dans cette partie 8, nous allons afficher des pixels dans le mode VGA 320x200 et afficher non pas du fanta ou du coca, mais du sprite !

Vive notre président Point Carré !
Nous avons vu dans le chapitre 6 que notre écran VGA était composé de 200 lignes de 320 pixels chacunes e que chaque pixel pouvait avoir une couleur parmi, soit une valeur entre 0 et 255.
Pour afficher des images sur cette écran, il va falloir mettre la bonne valeur de couleur au bonne endroit. Pour obtenir cette incroyable résultat, on va écrire la fonction put_pixel.
Nous allons passer 3 paramètres sous forme de variables à notre fonction :
- x : un word pour la position horizontale
- y : un autre word pour la position verticale
- clr : un byte pour la couleur On pourrait utiliser uniquement un byte pour y comme la valeur maximale de celui-ci est de 200, mais c'est plus simple ainsi.
L'oeuf, c'est ?
Pour trouver la position d'un pixel par rapport à l'écran, on va devoir faire un peu de maths :
- on cherche d'abord la ligne de l'écran en multipliant y par le nombre de pixels d'une ligne soit :
y × 320 - puis on y ajoute la position x :
y × 320 + x
Ce calcul va nous donner l'offset du pixel par rapport à l'adresse de 0xa000:0x0000 de la carte VGA, c'est à dire le décallage par rapport au début de cette mémoire.
Bien évidemment (comme le dirait tout prof de math qui se respecte en regardant les yeux hagards et plein de désespoir de ses élèves...) pour que cela fonctionne, le premier pixel de l'écran doit avoir pour coordonnées 0 ; 0 et non 1 ; 1 !
Et oui, avec ce calcul, le pixel 1 ; 1 est à l'offset 1 × 320 + 1 soit 321, et donc à la colonne 2 de la deuxième ligne de notre écran.
Les plus perspicaces d'entre vous en auront déduit que le tout dernier pixel de notre écran a pour coordonnées : 319 ; 199.
Une vraie tête de mul
Et voici comment mettre ce calcul en place, en utilisant l'instruction mul :
mul permet de multiplier les valeurs de ax et dx et le résultat sera stocké dans le même couple de registre dx:ax.
Bin oui, si on multiplie une valeur de 16 bits par une autre valeur de 16 bits, le résultat peut faire jusqu'à 32 bits, une vraie orgie !
Bon on va pas se mentir, cette version de put_pixel n'est pas la plus optimisée du monde, mais elle fait ce qu'on lui demande, ce qui est déjà pas si mal.
.
Le code source de invaders.asm
; ========
; invaders
; ========
;
; C:\nasm -fbin invaders.asm -o invaders.com
org 0x100
global main
VGA_MEMORY_SEGMENT equ 0xa000 ; segment mémoire de la carte VGA
VGA_WIDTH equ 320 ; nombre de pixels horizontaux en VGA
VGA_HEIGHT equ 200 ; nombre de pixels verticaux en VGA
section .text
main:
call enter_vga ; on entre dans le mode VGA
call render ; on dessine un truc
call wait_key ; on attend une touche au clavier
call quit_vga ; on quitte le mode VGA
mov ax, 0x4c00 ; on quitte sans erreur
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_key:
mov ah, 0x01 ; on vérifie si une touche a été appuyée
int 0x16
jz wait_key ; si ZF est activé, aucune touche n'a été appuyée
ret
render:
mov ax, VGA_MEMORY_SEGMENT
mov es, ax
xor dx, dx ; dx est l'index du sprite, soit 0
mov [sprite_id], dx
mov ax, 146 ; x = 146
mov [x], ax
mov ax, 92 ; y = 92
mov [y], ax
mov al, 4 ; clr = rouge
mov [clr], al
call draw_sprite ; on dessine le premier sprite
inc dx ; dx est l'index du second sprite
mov [sprite_id], dx
mov ax, [x] ; x = x + 16
add ax, 16
mov [x], ax
mov al, 2 ; clr vert
mov [clr], al
call draw_sprite ; on dessine le second sprite
ret
put_pixel:
push ax
push dx
push di
mov ax, VGA_WIDTH ; ax = 320
mov dx, [y] ; dx = y
mul dx ; dx:ax = 320 * y
mov di, ax ; di = 320 * y
add di, [x] ; di = 320 * y + x
mov al, [clr] ; al = clr
stosb ; [es:di] = clr
pop di
pop dx
pop ax
ret
draw_sprite:
pusha ; on sauve tous les registres courants
mov ax, [y] ; on sauve y
push ax
mov ax, [x] ; on sauve x
push ax
mov [prevx], ax
mov ax, [sprite_id] ; ax = sprite_id
shl ax, 4 ; ax = sprite_id * 16: il y a 16 bytes par sprites
mov si, sprite1 ; si est l'offset du premier sprite
add si, ax ; on ajoute à si l'offset du sprite choisi par sprite_id
mov cl, 8 ; compteur ligne : 8 lignes par sprites
draw_sprite_line:
lodsw ; ax = [ds:si] = ligne du sprite
mov dx, [x] ; dx = x
mov bx, 0x8000 ; notre masque
mov ch, 16 ; compteur bit : 16 bits par lignes
draw_sprite_bit:
test bx, ax ; test bit & masque
jz draw_sprite_no_pixel ; si 0 on ne dessine rien, sinon...
call put_pixel ; ... on dessine le pixel
draw_sprite_no_pixel:
shr bx, 1 ; masque pour le bit suivant
inc dx ; x = x + 1
mov [x], dx
dec ch ; on passe au bit suivant
jnz draw_sprite_bit
push ax ; on conserve ax qui va nous servir pour affecter les variables
mov ax, [y] ; y = y + 1
inc ax
mov [y], ax
mov ax, [prevx] ; on recupère la valeur de x
mov [x], ax
pop ax ; on récupère ax
dec cl ; on passe à la ligne suivante
jnz draw_sprite_line
pop ax ; on restaure x
mov [x], ax
pop ax ; on restaure y
mov [y], ax
popa ; on restaure tous les registres courants
ret
section .data
old_mode: db 0 ; variable pour conserver le mode vidéo précédent
x: dw 0 ; x du pixel
prevx: dw 0 ; prevx pour conserver x dans notre boucle
y: dw 0 ; y du pixel
clr: db 0 ; couleur du pixel
sprite_id: dw 0 ; index du sprite (0 ou 1)
; ci dessous les lignes des deux sprites :
sprite1: dw 0x0820, 0x0440, 0x0fe0, 0x1bb0, 0x3ff8, 0x2fe8, 0x2828, 0x06c0
sprite2: dw 0x0300, 0x0780, 0x0fc0, 0x1b60, 0x1fe0, 0x0480, 0x0b40, 0x14a0