IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Assembleur Intel avec NASM en mode protégé


précédentsommairesuivant

II. VESA en mode protégé

67 commentaires Donner une note à l´article (5)

Relu par ClaudeLELOUP.

Oui, chers amis, vous avez bien lu : enfin de vrais graphismes en mode protégé ! Adieu mode VGA, SVGA, Hercules et autres reliquats des années 1990 !

II-1. Des émulateurs

Si vous m'avez suivi jusqu'ici, vous devez savoir que nous travaillons sur émulateur. Si jusqu'à présent, n'importe quel émulateur pouvait vaguement convenir à quelques exceptions près, ce n'est plus le cas. Warum ? Weil komssa ist. Traduction : pourquoi ? Parce que visiblement maints émulateurs ont bien des difficultés à reproduire correctement le VESA 2.0. Oui, je sais, l'allemand est une langue magnifique.

Alors, bon, voici les émulateurs que j'ai testés, si vous avez d'autres retours, dites-le-moi.

  • Bochs : je ne me permettrai pas de critiquer vu que plein de gens en sont très contents, mais je n'ai même pas réussi à avoir un vague VESA 1.2.
  • VMWare : le VESA 2.0 est spécifiquement écrit dans l'OEM, mais soit je suis nul, soit c'est une mauvaise blague. Sur un paquet de 80 ko (kilo-octet), les derniers 40 ko pointent sur les mêmes pixels que les premiers 40 ko, ce qui donne des fascés.
  • VirtualBox : ça marche. Enfin, à peu près. 'Faut apparemment pas être trop rapide, notamment pour un remplissage standard d'écran. Sinon, leur VESA 2.0 a un comportement de VESA 2.0.

Donc la suite se déroulera sur VirtualBox.

II-2. Du Linear Frame Buffer

Qu'on sigle LFB. C'est un champ donné à l'octet 40 dans la structure renvoyée par l'appel BIOS 0x10, fonction 0x4F01, quand on demande des informations sur un mode vidéo particulier. Il s'agit d'une adresse sur 32 bits, à partir de laquelle on trouvera la valeur de chaque pixel de l'écran.

Et vous savez quoi ? Pour éviter des additions fastidieuses et nous accélérer le tempo, nous allons déclarer un segment de données qui commence à cette adresse. Après tout, on a appris à jouer avec la GDT, oui ou non ?

 
Sélectionnez
gdt:
	db 0x00, 0x00, 0x00, 0x00, 0x00, 00000000b, 00000000b, 0x00
gdt_cs:
	db 0xFF, 0xFF, 0x00, 0x00, 0x00, 10011011b, 11011111b, 0x00
gdt_ds:
	db 0xFF, 0xFF, 0x00, 0x00, 0x00, 10010011b, 11011111b, 0x00
gdt_flb:
	dw 0xFFFF, 0x0000
	db 0x00, 10010011b, 11011111b, 0xE0
gdtend:

pointeurGDT:
	dw	gdtend-gdt				; taille
	dd	(BOOT_SEG << 4) + gdt	; base

C'est initialisé à 0x00E00000, je crois. C'est parce que VirtualBox place son écran à cet endroit-là. De toute façon, on peut mettre n'importe quoi, ce sera initialisé proprement à la lecture. On ne met pas de limite supérieure au segment, parce que je n'en ai ni envie ni besoin pour l'instant. Le mélange rock'n roll de mots et d'octets n'a pour objectif que de me faciliter la création à la main.

On remplit comme ça :

 
Sélectionnez
		mov ax, [FlatMemory]; bits de poids faible
		mov [gdt_flb + 2], ax
		mov ax, [FlatMemory + 2]; bits de poids fort
		mov [gdt_flb + 4], al
		mov [gdt_flb + 7], ah

On fait un usage intensif de la structure d'un descripteur de segment, donnée au chapitre 8. Ah, oui, c'est vrai : je fais ici, dans le chargeur d'amorçage, référence à FlatMemory, mémoire aplatie. Cette valeur est une adresse codée en dur par un %define :

 
Sélectionnez
;Définitions de données qui seront chargées par le BIOS
;Informations du pilote VESA
%define VESASignature 512;'VESA', signature de 4 octets
%define VideoModePtr VESASignature + 14;: dd 0; Pointeur vers les modes accessibles

;Informations d'un mode vidéo
%define ModeAttributes VESASignature + 256; Attributs du mode mot
%define BytesPerScanLine ModeAttributes + 16;: dw 0; Octets par "scanline" mot
%define XResolution ModeAttributes + 18;: dw 0; Résolution horizontale
%define YResolution ModeAttributes + 20;: dw 0; Résolution verticale
%define BitsPerPixel ModeAttributes + 25;: db 0; Bits par pixel
%define FlatMemory ModeAttributes + 40

Parce que, si on ne mettait pas cette adresse en dur, l'ensemble de ces deux jeux de données ferait 512 octets, soit précisément la taille de notre secteur d'amorçage, et nous n'aurions donc pas de place pour du code. Plutôt que de faire des chargements de secteur, de vérifier qu'on a bien ce qu'on veut là où on le veut, je sais qu'à la fin du segment (on est en 16 bits !), j'ai de l'espace, vu que je n'ai dedans que 512 octets, je balance ça à une adresse fixée à cet endroit.

L'adresse du LFB oblige à se poser la question suivante : que deviennent les deux autres fenêtres, les célèbres fenêtres A et B qu'on utilise en mode réel ? Elles existent toujours et sont définies, mais à première vue, leur utilisation est incompatible avec celle du LFB. Et de fait, les deux utilisations sont incompatibles. Pour utiliser le LFB, il faut, lors de l'appel au changement de mode, passer le bit n° 14 à 1, chose que je fais ainsi :

 
Sélectionnez
		mov ax, 0x4F02
		mov bx, [mode_souhaite]
		or bx, 0100000000000000b ; Le bit n°14 à 1 demande l'utilisation de la mémoire linéaire.
		int 0x10 ; Changement de mode vidéo

Détail pendant que j'y pense : le binaire peut aussi se suffixer par 'b'.

II-3. Du dessin

Allez, jeune France, on y retourne ! Je ne donne pas la fonction de nettoyage d'écran vu qu'elle ne fonctionne pas bien, mais si quelqu'un se sent l'âme d'un peintre en bâtiment, je suis preneur. Comme on n'a pas d'écran de 65536 par 65536, on peut utiliser un registre de 32 bits pour coder à la fois l'abscisse et l'ordonnée. L'abscisse sera ici le mot de poids fort et l'ordonnée le mot de poids faible. On met EDI à 0, le sélecteur de segment ES sur le segment n° 3 (vérifiez la table) avant l'appel, deux multiplications et une somme, on écrit et c'est fini.

 
Sélectionnez
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; metPixel															;;
;;------------------------------------------------------------------;;
;; Colorie le pixel dont la position est dans EAX (mot haut : X,	;;
;; mot bas : Y) avec la couleur contenue dans couleurPoint			;;
;; ES doit contenir le descripteur de segment de l'écran.			;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
metPixel:
	push edi
	push ecx
	push eax
	push ebx
	push edx
	
	mov ebx, eax
	and eax, 0xFFFF
	shr ebx, 16
	xor ecx, ecx
	mov cx, [XResolution]
	mul ecx					; On fait une multiplication 32 bits à partir de valeurs de 16 bits.
	add eax, ebx
	xor ch, ch
	mov cl, [octetsParPixel]
	mul ecx					; On fait une multiplication 32 bits à partir de valeurs de 8 bits.
	mov edi, eax
	mov eax, [couleurPoint]
	stosd

.retour:
	pop edx
	pop ebx
	pop eax
	pop ecx
	pop edi
	ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Fin metPixel														;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

J'espère que vous avez vu l'imbécillité que j'ai faite, je la laisse pour l'édification des masses.

Allez, entre les push et les pop, y'a pas beaucoup de code, mes enfants !

Mettre la couleur, c'est obligé. Ce n'est pas ça.

Calculer la coordonnée, obligatoire aussi.

Il ne reste que la bourde.

Oui, le nombre d'octets par pixel. On écrit 32 bits de couleur, le nombre d'octets par pixel est donc nécessairement de 4. Ce qui implique que dans le code d'amorçage, on ne sélectionne que les modes à 32 bits. Autre subtilité : comme on fait une multiplication, le registre EDX est modifié. Il faut donc le mettre sur la pile au début de la fonction et le restaurer à la fin. Pensez-y, on peut perdre des heures bêtement.

II-4. De Bresenham

Je vous en avais déjà parlé il y a bien longtemps, voici le retour de l'algorithme de Bresenham, l'étoile des dessinateurs de segment de droite. Sauf qu'à l'époque, il s'agissait d'une interprétation quelque peu cavalière. Nous allons faire mieux, de surcroît en plus court.

II-4-a. L'idée

On se propose de tracer un segment de droite pixel par pixel. Pour ce faire, nous allons débuter à une extrémité du segment. Nous allons nous intéresser aux segments dont la direction est comprise entre Est et Sud-Est. L'idée de base, c'est d'avancer en abscisse de 1 pixel à chaque pas. Deux possibilités : soit on reste sur la même ligne (on va plein Est pour cette itération), soit on passe à la ligne du dessous (on va au Sud-Est). Ça, c'est pour le cas où on dessine de gauche à droite et de haut en bas, avec un écart en abscisse supérieur à l'écart en ordonnée. Ce qui fait qu'il est valable pour un huitième de cercle. Il suffit de changer le sens de parcours et d'inverser abscisse et ordonnée pour aller où l'on veut.

II-4-b. Le comment

  • On se fixe une variable particulière : l'écart à la droite optimale.
  • On initialise cet écart à la longueur à parcourir en abscisse. C'est la valeur maximale que peut prendre cet écart : le segment de droite orienté Sud-Est.
  • On dessine le point où l'on est.
  • On ajoute 1 à son abscisse. On va regarder si on dessine sur la même ligne ou sur la ligne d'en dessous.
  • On ôte à l'écart la longueur à parcourir en ordonnée. Cela donne l'erreur que l'on commet.
  • Si l'écart est inférieur à la longueur à parcourir en ordonnée, c'est qu'on est trop haut. On ajoute 1 à l'ordonnée du point sur lequel on est. On ajoute à l'écart la longueur à parcourir en ordonnée.
  • Et on recommence.

II-4-c. La fonction

 
Sélectionnez
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; afficheLigne														;;
;;------------------------------------------------------------------;;
;; Dessine un segment de droite entre le point stocké dans EAX et 	;;
;; celui stocké dans EBX (mot haut : X,	mot bas : Y) avec la 		;;
;; couleur contenue dans couleurPoint								;;
;; ES doit contenir le descripteur de segment de l'écran.			;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Y2: dd 0
afficheLigne:
	push esi
	push eax
	push ebx
	push ecx
	push edx
	push edi

	cmp 	eax, ebx	;On travaille dans le sens des abscisses positives
	jbe .ordreOK
	xchg 	eax, ebx	;On inverse si ce n'est pas le cas
.ordreOK:
	mov 	[Y2], ebx	;On stocke le point d'arrivée
	sub 	ebx, eax	;On calcule les longueurs à parcourir en X et Y
	mov 	cx, bx		;deltaY
	shr 	ebx, 16		;deltaX
	or 		bx, bx		;Si deltaX est nul, on sera sur une ligne verticale
	jz .ligneVerticale
	or		cx, cx		;Si deltaY est nul, on sera sur une ligne horizontale
	jnz .test_deltaY
	mov 	cx, bx		;CX contient le nombre de pixels à afficher
	mov 	ebx, 0x10000;Incrément de 1 pixel vers la droite
	jmp .avance_H_V
.ligneVerticale:
	mov 	ebx, 0x00001;Incrément de 1 pixel vers le bas
.avance_H_V:
	call metPixel		;Affichage du pixel
	add eax, ebx		;Incrément
	loop .avance_H_V
.fin_afficheLigne:
	call metPixel		;Affichage du dernier pixel
	pop edi
	pop edx
	pop ecx
	pop ebx
	pop eax
	pop esi
	ret

.test_deltaY:
	push 0x10000		;Incrément de 1 pixel vers la droite
	push 1				;Incrément de 1 pixel vers le bas
	jns .suite			;Si deltaY est positif, on est standard
	neg cx				;Sinon, on l'oppose, ainsi que l'incrément en Y
	neg dword[esp]
.suite:
	cmp bx, cx			;Si deltaY>deltaX
	jae .principal
	xchg bx, cx			;On échange deltaY et deltaX
	mov edx, [esp + 4]	;Ainsi que les incréments
	xchg edx, [esp]
	mov [esp + 4], edx
.principal:				;Et on continue normalement
	mov dx, bx			;DX contient l'écart
.boucle:
	call metPixel
	sub dx, cx			;On enlève deltaY
	cmp dx, cx			;Si on est inférieur ou égal à deltaY
	ja .affichage
	add eax, [esp]		;On applique l'incrément en Y
	add dx, bx			;On ajoute deltaX à l'écart
.affichage:
	add eax, [esp + 4]	;On applique l'incrément en X
	cmp eax, [Y2]		;Sommes-nous arrivés ?
	jb .boucle
	pop edx				;On s'en va proprement
	pop edx
	jmp .fin_afficheLigne
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Fin afficheLigne													;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

C'est quand même sacrément plus propre que la version précédente. Le 32 bits permet quand même un joli coup de rabot en taille de code, ça fait plaisir. Et en général, un code plus court est un code plus rapide. Là, on pourrait calculer directement l'adresse mémoire pour faire mieux. S'il y a des amateurs, encore une fois, c'est avec plaisir. C'est assez simple à faire.

II-5. De La Bruyère

La fonction d'affichage des caractères a eu aussi droit à un coup de lifting.

 
Sélectionnez
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; afficheCaractere													;;
;;------------------------------------------------------------------;;
;; Dessine un caractère au point stocké dans EAX (mot haut : X,		;;
;; mot bas : Y) avec la couleur contenue dans couleurPoint.			;;
;; ES doit contenir le descripteur de segment de l'écran.			;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
afficheCaractere:
	push edx
	push esi
	push ecx
	push eax
	push ebx

	mov ebx, eax		;EBX stocke le pixel courant
	mov eax, ecx		;ECX contient le n° du caractère
	mov ecx, 6			;6 lignes par caractère
	mul ecx
	mov esi, Alphabet	; adresse de la lettre
	add esi, eax		; si contient l'adresse de la lettre
.colonne:
	push ecx
	mov dl, 0b10000000
	mov ecx, 8; On affiche 8 colonnes
	lodsb
.ligne:
	test al, dl
	jz .suite
	xchg eax, ebx
	call metPixel
	xchg eax, ebx
.suite:
	shr dl, 1
	add ebx, 0x10000
	loop .ligne
	pop ecx
	inc ebx; passage à la ligne suivante
	sub ebx, 8*0x10000
	loop .colonne

	pop ebx
	pop eax
	pop ecx
	pop esi
	pop edx
	ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Fin afficheCaractere												;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Ici aussi, on peut faire la même optimisation que précédemment. Si une âme charitable s'en sent le courage...

II-6. Du neuf

Il faut toujours faire du neuf, même pas beaucoup, pour dire. Là, ce sera une première approche d'édition de texte avec nos fonctions. On tape au clavier, on appuie sur Entrée, hop ça s'affiche.

II-7. Du code

L'ensemble du code se trouve ici : Paquet


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Etienne Sauvage. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.