Revenir au sommaire Tutoriaux

Réaliser des Graphismes en 32bits Par :
Plouf
(plouf@win.be ou theplouf@yahoo.com)

 

En général, un algo se traduit facilement du 8bit vers le 32bits, mais parfois c'est plus difficile ou bien on passe à côté d'optimisations rendues possibles par ce nouvel environement.
Je vais parler des trois sujets suivants :
L'effet feu en 32bits (voir aussi le Tutorial: Comment réaliser un Geisser)
La conversion 32->16bits
Un peu de transparence
.
1°) L'effet feu en 32bits
Le passage semble immédiat : au lieu de prendre pixel par pixel, on prend composante par composante, en raisonant donc octet par octet. Bien sur, en 32bits, les 8premiers bits restent habituellement à 0, et ne méritent donc pas notre attention, on se contentera de "sauter" cet octet inutile.
En clair, pour chaque pixel, ça nous fait l'équivalent de trois pixel en 8bit, donc un algo en moyenne trois fois plus lent...
Figurez-vous un peu qu'il y a moyen de traiter tout le pixel en une passe, en allant donc à peu près à la même vitesse qu'en 8bits?
Illustrons : un pixel en 32bits peut se représenter comme suit en binaire :
00000000|rrrrrrrr|vvvvvvvv|bbbbbbbb
On se rend bien compte qu'on ne peut pas faire la somme des valeurs des pixels directement, car par exemple, le carry de la somme des b va déborder dans le dernier bit des verts, et on aura toutes les peines du monde à le récupérer :
0000000r|rrrrrrr?|vvvvvvv?|bbbbbbbb
Mais si justement on met ce dernier bit à 0 avant de commencer les opérations? De toutes façons on ne perd rien, car il est de toutes façons perdu à la fin quand on fait la division.
.
Donc on se débrouille pour avoir ceci :
00000000|rrrrrrr0|vvvvvvv0|bbbbbbb0
et à ce moment là, on peut en additionner deux du même genre et obtenir ceci :
0000000r|rrrrrrrv|vvvvvvvb|bbbbbbb0
.
ne reste qu'à faire un shift vers la droite, et on obtient directement la moyenne des deux.
Bon, habituellement ça n'est pas deux pixels qu'on somme, mais bien 4, ça n'est pas grave, on fait juste du
00000000|rrrrrr00|vvvvvv00|bbbbbb00
et une fois encore on ne perd aucune précision, vu que ces 2 bits seront perdus en précision lors de la division finale.
.
En clair on obtient un premier algo déjà beaucoup plus rapide :
mov eax [le premier]
and eax, 11111100111111001111110011111100b
mov ebx, [le second]
add ebx, 11111100111111001111110011111100b
add eax, ebx
mov ebx, [le troisieme]
and ebx, 11111100111111001111110011111100b
add eax, ebx
mov ebx, [le quatrieme]
and ebx, 11111100111111001111110011111100b
add eax, ebx
shr eax, 2
mov [destination], eax
.
Mais bon, c'est pas encore fameux, on peut encore faire mieux : Cette fois-ci, on va s'autoriser à perdre réelement de la précision à l'affichage. Rien de bien grave rassurez-vous, je l'ai fait et on ne voit vraiment pas la différence.
Si on pouvait compter sur le fait que ce qui est affiché à l'écran possède de toutes façons ses deux derniers bits de chacune de ses composantes à 0, on aurait plus besoin de refaire ce and à chaque fois, il ne resterait plus qu'à "ander" avant de le retaper sur l'écran pour être sur que ce que l'on envoie à l'écran corresponde à cette condition :
mov eax [le premier]
add eax, [le second]
add eax, [le troisieme]
add eax, [le quatrieme]
shr eax, 2
and eax, 11111100111111001111110011111100b
mov [destination], eax
.
ce qui est indiscutablement plus beau et plus rapide. Bien sur, il vous faut vérifier que tout autre pixel que vous envoyez à l'écran corresponde également à cette condition des derniers bits nuls, donc également les points chauds ou tout autre graphisme. C'est le prix à payer pour la rapidité.
Pour le "geiss" (voir Tutorial: Comment réaliser un Geisser) , on peut reprendre tel quel cet algo, voici par exemple l'algo que j'utilises pour l'effet vis1.dll de PMP pour le 320x200x32 :
ptable est la table de lien de l'effet "geiss"
pto est le buffer de destination
pfrom est le buffer d'origine
andval=11111100111111001111110011111100b
.
J'essaie ici d'optimiser l'algo en "croisant" les registres de façon à ce que le pentium puisse les excécuter en même temps. J'alterne donc l'utilisation de eax et de edx, ce qui me prend une instruction en plus mais normalement le pentium peut excecuter chaque des paires eax et edx en même temps, ce qui fait que j'y gagne (normalement...)
Si vous trouvez plus rapide, n'hésitez pas à me le dire :))
.
mov esi, ptable
mov edi, pto
mov ecx, l
@lp:
mov ebx, [esi+ecx*4-4]
add ebx, pfrom
mov eax, [ebx]
mov edx, [ebx-320*4]
add eax, [ebx-1*4]
add edx, [ebx+321*4]
add eax, edx
shr eax, 2
and eax, andval
mov [edi+ecx*4-4], eax
dec ecx
jnz @lp
.
2°) La conversion 32->16bits
Bien sur, on a pas toujours un mode d'écran 32bits à sa disposition (Et ma voodoo3 n'est pas une exception à cette règle).
Alors ou bien on est capable de reprogrammer une version qui exploite cette autre résolution, ou bien il faut créer une routine capable de convertir le 32bits vers le 16bits. Pour vis1.dll j'ai préféré la seconde solution, je ne sais pas si c'est la meilleure solution (ça me permet de garder en calcul une bonne précision graphique)...
Habituellement, une résolution 16bits s'exprime en binaire comme ceci :
rrrrrvvv|vvvbbbbb
.
il s'agit donc de faire
00000000|rrrrrrrr|vvvvvvvv|bbbbbbbb->rrrrrvvv|vvvbbbbb
A la base j'avais essayé avec une série de mov et de shr, mais mon cher ami Red_Spider (le graphiste de la skin de pmp au passage) m'a frappé bien fort sur la tête pour me signaler qu'avec une série de rotation, on pouvait y arriver très rapidement :
.
1) faire une rotation de 8 vers la droite
-> bbbbbbbb|00000000|rrrrrrrr|vvvvvvvv
2) faire un shift right de ah de trois
-> bbbbbbbb|00000000|000rrrrr|vvvvvvvv
3) faire un shift right de ax de deux
-> bbbbbbbb|00000000|00000rrr|rrvvvvvv
4) faire une rotation de 5 vers la gauche
-> bbb00000|00000000|rrrrrvvv|vvvbbbbb
et bingo :) le résultat se trouve dans ax
.
bref : eax<-la couleur 32
ror eax, 8
shr ah, 3
shr ax, 2
rol eax, 5
la couleur 16<-ax
.
Une fois encore, si vous avez plus rapide, ne le gardez pas pour vous merci :)
.
3°) Un peu de transparence
Red_Spider m'avait à l'époque conseillé d'utiliser un simple OR sur 32bits, ce qui avait l'avantage d'être très rapide, mais j'ai constaté que si les deux couleurs varient dans le temps, le résultat de ce OR varie par saccade, ce qui ne donne pas très très joli.
En fait si vous voulez une transparence simple et rapide, vous pouvez utiliser l'effet feu en lui-même en faisant une somme sur deux pixels et en divisant le résultat par deux, vous pouvez (comme montré plus haut) raisoner sur 32bits à la fois, ce qui est assez rapide.
Mais ce système à un gros défaut, c'est que la luminosité n'est pas préservée : par exemple, du noir en transparence sur du blanc donne du gris, en général on a une diminution de moitié de la luminosité. Et si on décide de ne pas faire la division par deux, on doit se taper des vérifications de débordement car la valeur finale de chacune des composantes pourait dépasser 255...
La seule solution que j'ai trouvée jusqu'ici est de raisoner octet par octet (hélas), en utilisant une table de pré-calcul de 64000octets, de 256x256 et d'un octet par élément. Cette table donne pour deux octets, l'octet qui résulte de leur transparence. Dans l'algo en lui-même on fait donc :
(on suppose que le high word de eax est nul)
mov al, [pixel]
mov ah, [pixel à rajouter en transparence]
mov al, [eax+offset de la table]
mov [pixel], al
.
C'est pas super rapide mais ça donne bien, et si une fois encore vous avez mieux, dites le moi :)

Revenir au sommaire Tutoriaux

Hit-Parade