VII. Assembleur : reconstruction▲
Relu par ClaudeLELOUP.
Depuis la création du secteur d'amorçage au chapitre précédent, je ne veux pas dire qu'on n'a plus rien, mais peu s'en faut. Les routines sont un peu bringuebalantes, tout ça. Cela nécessite une petite remise à niveau.
VII-1. Du foutoir▲
Il faut avouer ce qui est : il devient rude de retrouver quoi que ce soit dans le code. NASM permet de s'y retrouver un peu mieux en séparant le code en plusieurs fichiers. La directive %include "[nom du fichier]" permet d'insérer à cet endroit le contenu du fichier spécifié.
VII-2. De l'affichage de texte▲
Les routines d'affichage de texte ont été déportées dans un fichier "affichageTexte.asm". Il y a deux fonctions : une fonction qui affiche une chaîne de caractères à l'écran "affiche_chaine", et une fonction qui transforme un nombre en chaîne de caractères "nombre_vers_chaine". Il faut garder à l'esprit que ces fonctions ont vocation à disparaître, puisqu'elles fonctionnent en mode texte, qui disparaît rapidement. Elles sont reprises du chapitre 3, clarifiées, déboguées et ainsi de suite.
VII-2-a. affiche_chaine▲
Cette fonction affiche la chaîne de caractères pointée par si et se terminant par le caractère 0, aussi appelé caractère nul, de valeur numérique 0. Elle suppose que l'écran mesure 80 caractères de large sur 25 de haut. En cas de débordement, elle efface la page et continue son écriture en haut. Elle ne connaît que deux caractères non affichables : le 0 et le 13, respectivement marqueur de fin de chaîne et retour à la ligne.
;Affiche à l'écran une chaîne de caractères
affiche_chaine:
push
ax
push
bx
push
cx
push
dx
xor
bh
, bh
; RAZ de bh, qui stocke la page d'affichage
mov
ah
, 0x03
int
0x10
; appel de l'interruption BIOS qui donne la position du curseur, stockée dans dx
mov
cx
, 1
; nombre de fois où l'on va afficher un caractère
affiche_suivant:
lodsb
or
al
, al
;on compare al à zéro pour s'arrêter
jz
fin_affiche_suivant
cmp
al
, 13
je
nouvelle_ligne
mov
ah
, 0x0A
;on affiche le caractère courant cx fois
int
0x10
inc
dl
; on passe à la colonne suivante pour la position du curseur
cmp
dl
, 80
jne
positionne_curseur
nouvelle_ligne:
inc
dh
; on passe à la ligne suivante
xor
dl
, dl
; colonne 0
cmp
dh
, 25
jb
positionne_curseur
xor
dh
, dh
mov
ah
, 0x02
;on positionne le curseur
int
0x10
mov
cx
, 25
*
80
mov
ah
, 0x0A
;on affiche le caractère courant cx fois
mov
al
, ' '
int
0x10
mov
cx
, 1
positionne_curseur:
mov
ah
, 0x02
;on positionne le curseur
int
0x10
jmp
affiche_suivant
fin_affiche_suivant:
pop
dx
pop
cx
pop
bx
pop
ax
ret
VII-2-b. nombre_vers_chaine▲
Cette fonction écrit dans la chaîne pointée par di le nombre contenu dans ax.
;écrit dans la chaîne pointée par DI le nombre contenu dans AX
;si CL est à un, on écrit un caractère terminal
;CH contient le nombre minimal de caractères à utiliser
;BL contient la base
nombre_vers_chaine:
push
bx
push
dx
push
cx
xor
bh
, bh
mov
cl
, 1
stocke_digit:
xor
dx
, dx
div
bx
push
dx
;sauve le reste dans la pile
inc
cl
or
ax
, ax
jne
stocke_digit
;Les digits sont stockés dans la pile
inc
bh
;
ajout_zero:
cmp
ch
, cl
jb
clearCH
push
' '
-
'A'
+
10
inc
cl
jmp
ajout_zero
clearCH:
xor
ch
, ch
;Affichage du chiffre
boucle_digit:
dec
bh
loop
affiche_digit
pop
cx
; on récupère le paramètre bx
test
cl
, 0b1
; s'il est à 1, on écrit un caractère terminal
jz
depile
mov
byte
[di
], 0
depile:
pop
dx
pop
bx
ret
affiche_digit:
pop
ax
cmp
ax
, 10
jae
dixPlus
add
ax
, '0'
stosb
; met AL dans l'octet pointé par DI et incrémente DI
jmp
boucle_digit
dixPlus:
add
ax
, 'A'
-
10
stosb
jmp
boucle_digit
;fin nombre_vers_chaine
VII-3. Des modes graphiques▲
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 :
- saut au début, optionnel ;
- p'tit message pour ne pas perdre la main ;
- demande d'informations sur le VESA BIOS EXTENSION ;
- traitement de ces informations ;
- choix du mode graphique ;
- passage dans le mode choisi ;
- affichage de points ;
- affichage de droites ;
- affichage d'un alphabet.
VII-3-a. Saut au début, optionnel▲
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
VII-3-b. P'tit message pour ne pas perdre la main▲
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.
VII-3-c. Demande d'informations sur le VESA BIOS EXTENSION▲
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. Elles 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é.
VII-3-d. Traitement de ces informations▲
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 :
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 DS
mov
ds
, ax
; ds contient l'adresse de segment du nom de l'OEM
call
affiche_chaine
pop
ds
; on restaure DS
mov
cx
, 18
lignes_vides:
mov
si
, retour_chariot
call
affiche_chaine
loop
lignes_vides
Notez qu'on ajoute 18 lignes de retour 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 mode
pop
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 verticale
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
cx
mov
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 basse
pop
cx
push
si
;sauve si sur la pile
mov
si
, hello
call
affiche_chaine
pop
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
ax
mov
ax
, [YResolution]
shr
ax
, 3
push
ax
mov
al
, [BitsPerPixel]
xor
ah
, ah
shr
ax
, 3
pop
bx
mul
bx
pop
bx
mul
bx
pop
dx
cmp
ax
, [maxResol]
jb
lit_mode_suivant
mov
[maxResol], ax
mov
[mode_souhaite], cx
jmp
lit_mode_suivant
VII-3-e. Choix du mode graphique▲
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
cx
mov
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 basse
pop
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
VII-3-f. Passage dans le mode choisi▲
mov
ax
, 0x4F02
mov
bx
, [mode_souhaite]
int
0x10
; Changement de mode vidéo
call
nettoyage_ecran
VII-3-f-1. Nettoyage de l'écran▲
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
bx
mov
cl
, [octetsParPixel]
xor
ch
, ch
mov
bx
, couleur_defaut
.couleur:
inc
bx
mov
al
, byte
[bx
]
stosb
loop
.couleur
pop
bx
pop
cx
loop
.point
inc
dx
pop
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
VII-3-g. Affichage de points▲
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 + abscisse. 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
VII-3-h. Affichage de droites▲
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
VII-3-i. Affichage d'un alphabet▲
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