<-Précédent | Retour à l'accueil | Retour au niveau supéreur | Suivant-> |
Pour faire une sorte de séance de révision, nous allons retravailler les graphiques du chapitre 5. Reprenons tout le déroulement du programme :
org 0x0000 ; Adresse de début .COM
jmp start
%include "affichageTexte.asm"
start:
;On initialise Data Segment et Extra Segment à Code Segment
call initialise_segments
On s'en souvient, on est en binaire pur pour être chargé depuis un chargeur d'amorçage, on a gardé la directive org d'une inutilité certaine ici. On fait un saut pour inclure les fonctions d'affichage de texte au début. C'est un choix, absolument pas justifiable par autre chose que "c'est comme ça". On appelle une fonction "initialise_segments" qui ne sera jamais appelée qu'une fois. On pourrait la mettre en macro, mais nous verrons les macros plus tard. C'est donc encore en fonction.
;Initialisation des segments
initialise_segments:
mov ax, cs
mov ds, ax
mov es, ax
mov ax, 0x8000
cli
mov ss, ax
mov sp, 0xF000
sti
ret
;Fin initialisation segments
mov si, hello; met l'adresse de la chaîne à afficher dans le registre SI
call affiche_chaine
Bon, le message sera affiché, mais tel quel, on a d'excellentes chances de ne pas le voir, parce qu'on va afficher plus d'un écran par la suite. Il faut modifier le code qui viendra après pour le voir.
mov ax, 0x4F00 ; demande infos sur le pilote VESA VBE
mov di, VESASignature
int 10h
cmp ax, 0x4F ; Si AL <> 0x4F, on n'a pas de VESA, donc fin. Si AH <> O, erreur, donc fin.
jne fin
VESASignature est défini dans la partie "données" :
;Informations du pilote VESA
VESASignature: times 4 db 0; 'VESA', signature de 4 octets
VESAVersion: dw 0; numéro de version de VBE
OEMStringPtr: dd 0; Pointeur vers le nom de l'OEM
Capabilities: dd 0; Possibilités de la carte graphique
VideoModePtr: dd 0; Pointeur vers les modes accessibles
TotalMemory: dw 0; Nombre de blocs mémoire de 64ko
reserved: times 236 db 0; Complément à 256 octets, taille du bloc
Ces informations, je ne les sors pas de mon chapeau, évidemment. Elle proviennent d'ici : VESA BIOS Extension (VBE) Core Functions Standard Version 2.0, tout ça parce que dans ma machine virtuelle, c'est VBE 2.0 qui est implanté.
Le traitement des informations VESA consiste principalement en deux choses : gérer les cas d'erreur et pousser plus avant les investigations. Pour les erreurs, on restera très simple puisqu'on sort au moindre problème. Pour le reste, voyons cela.
Première chose, afficher le nom du constructeur :
lignes_vides:cmp ax, 0x4F ; Si AL <> 0x4F, on n'a pas de VESA, donc fin. Si AH <> O, erreur, donc fin.
jne fin
mov si, OEMStringPtr ; pointeur vers le nom de l'OEM stocké offset:segment
lodsw; on charge l'adresse d'offset dans ax
mov bx, ax ; BX contient l'adresse d'offset
lodsw ; on charge l'adresse de segment dans ax
mov si, bx ; SI pointe sur le nom de l'OEM
push ds ; on sauvegarde DSmov ds, ax ; ds contient l'adresse de segment du nom de l'OEM
call affiche_chainepop ds ; on restaure DS
mov cx, 18
mov si, retour_chariot
call affiche_chaine
loop lignes_vides
Notez qu'on ajoute 18 lignes de retours chariot. C'est bête, c'est méchant et ça ne sert qu'à décaler l'affichage pour en avoir plus à visualiser sur l'écran suivant. Cette horreur devrait partir prochainement.
Nous allons ensuite lire les informations de chaque mode supporté par notre interruption 0x10. Ca nous permettra de choisir un bon mode vidéo.
mov si, VideoModePtr ; pointeur vers la liste des modes supportés
lodsw ; on charge l'adresse d'offset dans ax
mov cx, ax ; cx contient l'adresse d'offset
lodsw ; on charge l'adresse de segment dans ax
mov si, cx ; si pointe sur le premier mode supporté
mov dx, ax ; dx contient l'adresse de segment
lit_mode_suivant:
push ds
mov ds, dx ; ds contient l'adresse de segment de la liste des modes
lodsw ;charge dans ax le modepop ds
cmp ax, 0xFFFF ; Fin de la liste
je arret_modes
mov cx, ax
mov ax, 0x4F01 ; demande infos sur le mode VESA
mov di, ModeAttributes
int 0x10
VideoModePtr est défini dans le bloc de données :
;Informations d'un mode vidéo
ModeAttributes: dw 0; Attributs du mode
WinAAttributes: db 0; Attibuts de la fenêtre A
WinBAttributes: db 0; Attibuts de la fenêtre B
WinGranularity: dw 0; Granularité de la fenêtre en ko
WinSize: dw 0; Taille de la fenêtre en ko
WinASegment: dw 0; Segment de la fenêtre A
WinBSegment: dw 0; Segment de la fenêtre B
WinFuncPtr: dd 0; Pointeur vers la fonction "de fenêtrage"
BytesPerScanLine: dw 0; Octets par "scanline"
XResolution: dw 0; Résolution horizontale
YResolution: dw 0; Résolution vertical
XCharSize: db 0; Largeur d'un caractère
YCharSize: db 0; Hauteur d'un caractère
NumberOfPlanes: db 0; Nombre de plans mémoire
BitsPerPixel: db 0; Bits par pixel
NumberOfBanks: db 0; Nombre de banques de style CGA
MemoryModel: db 0; Type de modèle mémoire
BankSize: db 0; Taille des banques de style CGA
NumberOfImagePages: db 0; Nombre de pages image
res1: db 0; Reservé
RedMaskSize: db 0; Taille du masque rouge en couleur directe
RedFieldPosition: db 0; Position du bit faible du masque rouge
GreenMaskSize: db 0; Taille du masque vert en couleur directe
GreenFieldPosition: db 0; Position du bit faible du masque vert
BlueMaskSize: db 0; Taille du masque bleu en couleur directe
BlueFieldPosition: db 0; Position du bit faible du masque bleu
RsvdMaskSize: db 0; Taille du masque réservé en couleur directe
RsvdFieldPosition: db 0; Position du bit faible du masque réservé
DirectColorModeInfo: db 0; Attributs du mode de couleur directe
res2: times 216 db 0; Complément à 256 octets, taille du bloc
Le test d'erreurs :
cmp ax, 0x4F ; Si AL <> 0x4F, la fonction n'est pas supportée, on se contentera du VGA. Si AH <> O, erreur, pareil.
jne lit_mode_suivant
test word [ModeAttributes], 0xF
jz lit_mode_suivant
On affiche les informations pertinentes du mode courant :
;On ecrit les modes
mov di, hello ; on écrit dans hello
mov ax, cx
push cxmov ch, 3
mov bl, 16
call nombre_vers_chaine
mov al, ':'
stosb
mov ch, 4
mov bl, 10
mov ax, [XResolution]
call nombre_vers_chaine
mov ax, ('*' << 8) + ' '
stosw
mov al, ' '
stosb
mov ax, [YResolution]
call nombre_vers_chaine
mov ax, ' '
stosb
mov ch, 2
mov al, [BitsPerPixel]
call nombre_vers_chaine
mov al, ' '
stosb
mov ax, ';' ; on met 2 caractères d'un coup après la chaîne : un '\n' et le zéro terminal.
stosw ; les caractères sont dépilés, c'est à dire qu'il faut placer le premier dans la zone bassepop cx
push si ;sauve si sur la pile
mov si, hello
call affiche_chainepop si ; on récupère si
Les esprits chagrins remarqueront que j'écris sur l'emplacement hello. Alors, il est fait pour cela, et j'en suis parfaitement conscient. Vu le peu que j'écris, j'ai largement la place dans cet espace pour mettre ce qui m'intéresse.
On va déborder un peu sur le paragraphe suivant : on va chercher quelle est la meilleure résolution à utiliser. On va la définir comme étant celle maximisant largeurEnPixels * hauteurEnPixels * TailleDeCodageDesCouleurs. Ca se fait dans la même boucle, c'est pour cela qu'on en parle maintenant.
push dx
mov ax, [XResolution]
shr ax, 5
push axmov ax, [YResolution]
shr ax, 3
push axmov al, [BitsPerPixel]
xor ah, ah
shr ax, 3pop bx
mul bxpop bx
mul bxpop dx
cmp ax, [maxResol]
jb lit_mode_suivant
mov [maxResol], ax
mov [mode_souhaite], cx
jmp lit_mode_suivant
arret_modes:
mov cx, [mode_souhaite] ; On s'enquiert du mode souhaité
mov ax, 0x4F01 ; demande infos sur le mode VESA
mov di, ModeAttributes
int 0x10
mov di, hello ; on écrit dans hello
mov ax, cx
push cxmov ch, 3
mov bl, 16
call nombre_vers_chaine
mov al, ':'
stosb
mov ch, 4
mov bl, 10
mov ax, [XResolution]
call nombre_vers_chaine
mov ax, ('*' << 8) + ' '
stosw
mov al, ' '
stosb
mov ax, [YResolution]
call nombre_vers_chaine
mov ax, ' '
stosb
mov ch, 2
mov al, [BitsPerPixel]
call nombre_vers_chaine
mov ax, 13 ; on met 2 caractères d'un coup après la chaîne : un '\n' et le zéro terminal.
stosw ; les caractères sont dépilés, c'est à dire qu'il faut placer le premier dans la zone bassepop cx
mov si, hello
call affiche_chaine
mov al, [BitsPerPixel]
shr al, 3
mov byte [octetsParPixel], al
mov ax, [WinASegment]
or ax, ax ; on teste l'adresse du segment de la fenêtre. Si elle est nulle, on passe en mode 0x13
jnz adresse_OK
adresse_mode_13h:
mov word [mode_souhaite], 0x0013 ; infos du mode 0x13, le mode VGA
mov word [WinASegment], 0xA000
mov word [YResolution], 200
mov word [XResolution], 320
mov byte [octetsParPixel], 1
adresse_OK:
mov di, hello ; met l'adresse de la chaîne à lire dans le registre SI
call lit_chaine ; On attend l'utilisateur pour nettoyer l'écran
mov ax, 0x4F02
mov bx, [mode_souhaite]
int 0x10 ; Changement de mode vidéo
call nettoyage_ecran
Derrière ce terme ménager se cache juste le remplissage de l'intégralité de l'écran avec une couleur de fond. Comme d'habitude, l'idée n'est pas l'optimisation, le code super rapide comme l'ont tous les logiciels spécialisés. L'idée est d'avoir d'abord un code fonctionnel, créé au plus évident. L'algorithme que je vous propose ne prend en compte aucun prérequis autre qu'un mode VESA disposant d'une fenêtre A en mode écriture. Enfin, il me semble.
On écrit dans la mémoire vidéo par bloc de 64 ko. L'idée est donc de remplir tous ces blocs par la couleur de fond.
nettoyage_ecran:
push di
push es
push ax
push bx
push cx
push dx
mov es, [WinASegment]; On lit l'adresse de départ de la mémoire vidéo
mov cx, [YResolution]
mov ax, [XResolution]
mul cx ; Nombre de points total
mov ax, dx
mov cl, [octetsParPixel]
mul cl
mov cx, ax
xor dx, dx
xor bh, bh
xor bl, bl
boucle_fenetres:
push cx
mov ax, 0x4F05
int 0x10 ; Changement de fenêtre
mov cx, [WinSize]
shl cx, 9 ;Passage de ko en mots.
xor di, di.point:
push cx
push bxmov cl, [octetsParPixel]
xor ch, ch
mov bx, couleur_defaut.couleur:
inc bx
mov al, byte [bx]
stosb
loop .couleurpop bx
pop cx
loop .point
inc dxpop cx
loop boucle_fenetres
.depile:
xor dx, dx
mov [bloc_courant], dl
mov ax, 0x4F05
int 0x10 ; Changement de fenêtre
pop dx
pop cx
pop bx
pop ax
pop es
pop di
ret
;Fin nettoyage_ecran
Avec cette fonction, vous pouvez dorénavant dessiner ce que vous voulez où vous le voulez, en, normalement, n'importe quel mode VESA ! L'idée de base reste de mettre, octet par octet, la couleur spécifiée à l'adresse couleurPoint dans la mémoire vidéo, et de donner le bon numéro de plage. Ce bon numéro de plage est toujours basé sur la sainte formule : ordonnée * octetsParLigne + abcsisse. Je suppose que le débordement de la multiplication me donne le numéro convoité.
;fonction affiche_point : on est déjà dans un mode graphique
;BX : Coordonnée X du point
;AX : Coordonnée Y du point
affiche_point:
push bx ; On sauve les registres qu'on va manipuler
push cx
push es
push di
push dx
push ax
mov cx, word [BytesPerScanLine]
mul cx
mov di, ax
push dx
mov ax, bx
xor ch, ch
mov cl, byte [octetsParPixel]
mul cx
add di, ax
pop dx
.change_fenetre:
mov ax, 0x4F05
xor bh, bh
xor bl, bl
int 0x10 ; Changement de fenêtre
mov es, [WinASegment] ; On va dans la mémoire vidéo
mov bx, couleurPoint
.couleur:
inc bx
mov al, byte [bx]
stosb
loop .couleur
pop ax ; On restaure les registres manipulés
pop dx
pop di
pop es
pop cx
pop bx
ret
;fin de affiche_point
On avait déjà parlé de l'algorithme de Bresenham, le revoici à l'identique. Il a été mis à jour pour refléter l'inversion des paramètres de affiche_point. On met sur la pile X1, puis Y1, puis X2 et enfin Y2.
;fonction affiche_ligne : on est déjà dans un mode graphique
affiche_ligne:
jmp depart_affiche_ligne
Y2: dw 0
X2: dw 0
Y1: dw 0
X1: dw 0
deltaX: dw 0
deltaY: dw 0
incX: dw 0
incY: dw 0
e: dw 0
depart_affiche_ligne:
push si
push ax
push bx
push cx
push dx
push di
push es
mov ax, sp
mov si, ax
add si, 16 ; SI pointe sur Y2
mov di, Y2
mov ax, ds
mov es, ax
mov ax, ss
mov ds, ax
mov cx, 4
rep movsw
mov ax, es
mov ds, ax
mov ax, [X2]
mov bx, [X1]
sub ax, bx
mov [deltaX], ax
mov cx, [Y2]
mov bx, [Y1]
sub cx, bx
mov [deltaY], cx
or ax, ax ; test deltaX
jnz test_deltaX_positif
or cx, cx ; test deltaY
jnz test_deltaY_deltaX_nul
fin_affiche_ligne:
mov bx, [X2]
mov ax, [Y2]
call affiche_point
pop es
pop di
pop dx
pop cx
pop bx
pop ax
pop si
ret
deltaX_positif:
or cx, cx
jnz test_deltaY_deltaX_positif
;vecteur horizontal vers la droite
mov cx, [deltaX]
mov word [incX], 1
mov word [incY], 0
jmp ligne_H_V
test_deltaY_deltaX_nul:
;cx contient deltaY
mov word [incY], 1
mov word [incX], 0
cmp cx, 0
jns ligne_H_V
neg cx
mov word [incY], -1
ligne_H_V:
mov bx, [X1]
mov ax, [Y1]
avance_H_V:
call affiche_point
add bx, [incX]
add ax, [incY]
loop avance_H_V
jmp fin_affiche_ligne
test_deltaX_positif:
cmp ax, 0
jns deltaX_positif
or cx, cx ; CX contient DeltaY
jnz test_deltaY_deltaX_negatif
;vecteur horizontal vers la gauche
mov cx, [deltaX]
neg cx
mov word [incX], -1
mov word [incY], 0
jmp ligne_H_V
charge_registres:
shl cx, 1
shl ax, 1
mov [deltaY], cx
mov [deltaX], ax
mov bx, [X1]
mov ax, [Y1]
ret
charge_e_deltaX_et_cmp_X2:
mov [e], ax
call charge_registres
mov cx, [X2]
ret
charge_e_deltaY_et_cmp_Y2:
mov [e], cx
call charge_registres
mov cx, [Y2]
ret
affiche_et_charge_eY:
call affiche_point
add ax, [incY]
mov dx, [e]
ret
affiche_et_charge_eX:
call affiche_point
add bx, [incX]
mov dx, [e]
ret
octants1_et_4:
call charge_e_deltaX_et_cmp_X2
depart_boucle1:
call affiche_et_charge_eX
cmp bx, cx
je fin_affiche_ligne
sub dx, [deltaY]
cmp dx, 0
jns X_pret1
add ax, [incY]
add dx, [deltaX]
X_pret1:
mov [e], dx
jmp depart_boucle1
deltaY_positif_deltaX_negatif:
neg ax
deltaY_positif_deltaX_positif:
mov word [incY], 1
;deltaY > 0, deltaX > 0
cmp ax, cx
jae octants1_et_4
neg ax
call charge_e_deltaY_et_cmp_Y2
depart_boucle2_et_3:
call affiche_et_charge_eY
cmp ax, cx
je fin_affiche_ligne
add dx, [deltaX]
cmp dx, 0
jns X_pret2_et_3
add bx, [incX]
add dx, [deltaY]
X_pret2_et_3:
mov [e], dx
jmp depart_boucle2_et_3
octant5:
call charge_e_deltaX_et_cmp_X2
depart_boucle5:
call affiche_et_charge_eX
cmp bx, cx
je fin_affiche_ligne
sub dx, [deltaY]
cmp dx, 0
js X_pret5
add ax, [incY]
add dx, [deltaX]
X_pret5:
mov [e], dx
jmp depart_boucle5
octant8:
neg cx
call charge_e_deltaX_et_cmp_X2
depart_boucle8:
call affiche_et_charge_eX
cmp bx, cx
je fin_affiche_ligne
add dx, [deltaY]
cmp dx, 0
jns X_pret8
add ax, [incY]
add dx, [deltaX]
X_pret8:
mov [e], dx
jmp depart_boucle8
test_deltaY_deltaX_positif:
mov word [incX], 1
cmp cx, 0
jns deltaY_positif_deltaX_positif
;deltaY < 0, deltaX > 0
mov word [incY], -1
neg cx
cmp ax, cx
jae octant8
neg cx
jmp octants6_et_7
test_deltaY_deltaX_negatif:
mov word [incX], -1
cmp cx, 0 ; cx contient deltaY
jns deltaY_positif_deltaX_negatif
;deltaY < 0, deltaX < 0
mov word [incY], -1
cmp ax, cx ; ax contient deltaX
jbe octant5
neg ax
octants6_et_7:
call charge_e_deltaY_et_cmp_Y2
depart_boucle6_et_7:
call affiche_et_charge_eY
cmp ax, cx
je fin_affiche_ligne
add dx, [deltaX]
cmp dx, 0
js X_pret6_et_7
add bx, [incX]
add dx, [deltaY]
X_pret6_et_7:
mov [e], dx
jmp depart_boucle6_et_7
;AFFICHE_LIGNE ENDP
Voici la grande nouveauté de ce chapitre : un alphabet ! En mode graphique, oui madame, dessiné par mes soins, à la va-comme-j'te-pousse. Je l'ai fait parce qu'il me semble que bientôt, nous n'en aurons plus. Et puis, ça permettra d'afficher facilement des 'é', 'ç' et autres 'à'. Dès que j'aurai mis ces caractères, bien sûr. La fonction est très simple : on lui donne le numéro du caractère, avec A = 1, dans cx. ax contient l'ordonnée du coin supérieur gauche du caractère et bx son abscisse.
affiche_caractere:
push bx
push dx
push si
push cx
push ax
push ax
mov ax, cx
mov cx, 6
mul cx
mov si, Alphabet; adresse de la lettre
add si, ax; si contient l'adresse de la lettre
.colonne:
pop ax
push cx
mov dl, 0b10000000
mov cx, 8; On affiche 8 colonnes
.ligne:
push ax
mov al, [si]; On charge l'octet à afficher
test al, dl
jz .suite
pop ax
call affiche_point
push ax
.suite:
shr dl, 1
inc bx
pop ax
loop .ligne
pop cx
inc ax; passage à la ligne suivante
sub bx, 8
push ax
inc si
loop .colonne
pop ax
pop ax
pop cx
pop si
pop dx
pop bx
ret