codefordumm!ies: the cool logo

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 !

piou! piou!

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 :

L'oeuf, c'est ?

Pour trouver la position d'un pixel par rapport à l'écran, on va devoir faire un peu de maths :

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

Pour m'entrainer

Solution