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
:) |