Suivez-nous sur X

|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - Programmer un défilement sinusoïdal ("sine scroll") sur Amiga (partie 3)
(Article écrit par Yragael et extrait de www.stashofcode.fr - juillet 2017)
|
|
Cet article est le troisième d'une série de cinq consacrés à la programmation d'un "one pixel sine scroll"
sur Amiga, un effet très utilisé par les codeurs de démos et autres cracktros durant un temps. Par exemple,
dans cette cracktro du groupe Supplex :
Défilement sinusoïdal dans une cracktro du groupe Supplex
Dans le premier
article, nous avons vu comment installer en environnement de développement sur un Amiga émulé avec
WinUAE et programmer la liste Copper de base pour afficher quelque chose à l'écran. Dans le
deuxième article,
nous avons vu comment préparer une police de caractères 16x16 pour en afficher facilement les colonnes de
pixels des caractères, précalculer les valeurs du sinus requises pour déformer le texte en modifiant
l'ordonnée des colonnes, et mettre en place un triple tampon mémoire pour alterner proprement les images
à l'écran.
Dans ce troisième article, nous allons rentrer dans le vif du sujet en voyant comment dessiner et animer
le défilement sinusoïdal, d'abord au processeur, puis au Blitter.
Cliquez ici pour télécharger l'archive contenant
le code et les données du programme présenté dans cet article - c'est la même que dans les autres articles.
NB : cet article se lit mieux en écoutant l'excellent
module composé par Nuke/Anarchy pour la
partie magazine de Stolen Data #7, mais c'est affaire de goût personnel...
Faire défiler et animer le défilement sinusoïdal
La boucle principale peut maintenant être intégralement décrite. Elle accomplit successivement les
tâches suivantes à chaque itération :
- Attendre que le faisceau d'électron a terminé de dessiner la trame.
- Permuter circulairement les trois plans de bits pour afficher la dernière image.
- Attendre le Blitter et démarrer l'effacement du plan de bits C contenant la pénultième image.
- Dessiner le texte dans le plan de bits B qui contenait l'antépénultième image.
- Animer l'indice de la première colonne du premier caractère du texte à dessiner.
- Animer le sinus de cette première colonne.
- Tester si le bouton gauche de la souris est pressé.
Les trois premières tâches ont déjà été décrites. Nous allons donc décrire les suivantes.
Le défilement sinusoïdal est dessiné par une boucle qui dessine SCROLL_DX colonnes de caractères
consécutifs du texte à partir de la colonne SCROLL_X dans le plan de bits. Les indices de la première
colonne à dessiner et du premier caractère dont elle provient sont conservés dans les variables
"scrollColumn" et "scrollChar", respectivement. Le décalage du sinus de la première colonne du
défilement sinusoïdal est conservé dans une variable "angle".
Réglons immédiatement la question de l'animation du défilement sinusoïdal dans la boucle principale.
Faire défiler le texte le long d'un sinus ne présenterait pas beaucoup d'intérêt si ce dernier
n'était pas lui-même animé : il donnerait simplement l'impression de parcourir des montagnes russes.
Pour cela, nous décrémentons le décalage du sinus de la première colonne du défilement sinusoïdal
à chaque trame, non sans oublier de gérer l'éventuel débordement :
move.w angle,d0
sub.w #(SINE_SPEED_FRAME<alt;1),d0
bge _angleFrameNoLoop
add.w #(360<<1),d0
_angleFrameNoLoop:
move.w d0,angle
|
De plus, le texte doit défiler de gauche à droite. Pour cela, nous incrémentons de SCROLL_SPEED l'indice
de la première colonne dans le texte. Ici, nous devons gérer deux éventuels débordements : le débordement
d'un caractère pour passer de la dernière colonne d'un caractère à la première du caractère suivant, et
le débordement du texte pour passer du dernier caractère du texte au premier :
move.w scrollColumn,d0
addq.w #SCROLL_SPEED,d0
cmp.b #15,d0 ;La colonne suivante se trouve-t-elle après la dernière
;colonne du caractère courant ?
ble _scrollNextColumn ;Si non, en rester là
sub.b #15,d0 ;Si oui, utiliser une colonne dans le caractère suivante à la place...
move.w scrollChar,d1
addq.w #1,d1 ;...et passer au caractère suivant
lea text,a0
move.b (a0,d1.w),d2
bne _scrollNextChar ;...et vérifier si ce caractère ne se trouve pas après la fin du texte
clr.w d1 ;...et si oui, reboucler sur le premier caractère du texte
_scrollNextChar:
move.w d1,scrollChar
_scrollNextColumn:
move.w d0,scrollColumn
|
Nous pouvons maintenant passer au dessin du défilement sinusoïdal. Ce dernier est assuré par la boucle évoquée
plus tôt, qui est logée dans la boucle principale. Avant d'entamer cette boucle, nous devons procéder à un
certain nombre d'initialisations dans l'objectif de maximiser l'utilisation des registres pour minimiser
la nécessité d'aller chercher des données en mémoire.
Tout d'abord, nous déterminons le décalage (D6) du mot dans le plan de bits dans lequel se trouve le bit
qui correspond à la première colonne où dessiner, et nous identifions ce bit (D7) :
;Déterminer le décalage du mot du plan de bits de la première colonne où dessiner
moveq #SCROLL_X,d6
lsr.w #3,d6 ; Décalage de l'octet de la colonne dans le plan de bits
bclr #0,d6 ; Décalage du mot (revient à lsr.w #4 puis lsl.w #1)
;Déterminer le bit de ce mot correspondant à cette colonne
moveq #SCROLL_X,d4
and.w #$000F,d4
moveq #15,d7
sub.b d4,d7 ;Bit dans le mot
Ensuite, nous déterminons l'adresse (A0) du caractère courant ainsi que celle (A1) du mot dans la police
16x16 de sa colonne courante (D4) à dessiner dans la colonne courante du plan de bits évoquée à l'instant :
move.w scrollChar,d0
lea text,a0
lea (a0,d0.w),a0
move.w scrollPixel,d4
clr.w d1
move.b (a0)+,d1
subi.b #$20,d1
lsl.w #5,d1 ;32 octets par caractère dans la police 16x16
move.w d4,d2 ;Colonne du caractère à dessiner
lsl.w #1,d2 ;2 octets par ligne dans la police 16x16
add.w d2,d1
move.l font16,a1
lea (a1,d1.w),a1 ;Adresse de la colonne à dessiner
Dans le code précédent, le décalage de la première colonne d'un caractère se déduit du code ASCII de
ce caractère auquel il faut soustraire $20 - aspect pratique de la police 8x8 qui a servi de base à
la police 16x16, les caractères sont ainsi triés.
Enfin, nous procédons à des initialisations diverses de registres utilisés de manière récurrente dans
la boucle, dont le décalage du sinus de la colonne courante (D0) et le nombre de colonnes restant à
dessiner (D1) :
move.w angle,d0
move.w #SCROLL_DX-1,d1
move.l bitplaneB,a2
In fine, les registres se présentent ainsi au démarrage de la boucle :
Registre |
Contenu |
D0 |
Décalage du sinus de la colonne courante où dessiner dans le plan de bits |
D1 |
Colonne courante où dessiner dans le plan de bits |
D4 |
Colonne courante du caractère courant à dessiner |
D6 |
Décalage du mot dans le plan de bits qui contient la colonne courante où dessiner |
D7 |
Bit dans ce mot qui correspond à cette colonne |
A0 |
Adresse du caractère courant dans le texte |
A1 |
Adresse du mot dans la police correspondant à la colonne courante de ce caractère |
A2 |
Adresse du plan de bits où dessiner |
La boucle de dessin des SCROLL_DX colonnes du défilement sinusoïdal accomplit successivement les tâches
suivantes à chaque itération :
- calculer l'adresse du mot contenant le premier pixel de la colonne dans le plan de bits.
- Afficher la colonne courante du caractère courant.
- Passer à la colonne suivante du caractère courant, ou à la première colonne du caractère suivant,
ou à celle du premier caractère du texte.
- Décrémenter l'angle de la colonne courante.
Le calcul de l'adresse (A4) du mot contenant le premier pixel de la colonne dans le plan de bits
s'appuie sur une multiplication par une valeur de sinus précalculée par une puissance de 2, comme
nous l'avons déjà vu :
lea sinus,a6
move.w (a6,d0.w),d1
muls #(SCROLL_AMPLITUDE>>1),d1
swap d1
rol.l #2,d1
add.w #SCROLL_Y+(SCROLL_AMPLITUDE>>1),d1
move.w d1,d2
lsl.w #5,d1
lsl.w #3,d2
add.w d2,d1 ;D1=(DISPLAY_DX>>3)*D1=40*D1=(32*D1)+(8*D1)=(2^5*D1)+(2^3*D1)
add.w d6,d1
lea (a2,d1.w),a4
|
Oui ! D1 est utilisé ici comme variable temporaire alors qu'il a été initialisé pour servir de compteur à
la boucle. C'est qu'ainsi que nous allons bientôt le constater, nous sommes à court de registres.
En conséquence, la boucle commence et se termine par de brefs échanges avec la pile :
_writeLoop:
move.w d1,-(sp)
;...
move.w (sp)+,d1
dbf d1,_writeLoop
|
Nous pouvons alors afficher la colonne courante du caractère courant dans la colonne courante du plan de bits.
Le principe de l'affichage est assez simple du fait que la police 16x16 a subi une rotation qui permet de
tester les bits successifs d'un mot correspondant à la colonne à afficher plutôt que de tester le même bit
de mots successifs :
Affichage d'un caractère pixel par pixel
Afficher la colonne courante (mot en A1) du caractère courant dans la colonne courante du plan de bits
(bit D7 du mot en A4), cela donne :
move.w (a1),d1
clr.w d2
moveq #LINE_DX,d5
_columnLoop:
move.w (a4),d3
btst d2,d1
beq _pixelEmpty
bset d7,d3
bra _pixelFilled
_pixelEmpty:
bclr d7,d3
_pixelFilled:
move.w d3,(a4)
lea DISPLAY_DX>>3(a4),a4
addq.b #1,d2
dbf d5,_columnLoop
|
Cet affichage accompli, nous pouvons passer à celui de la colonne suivante du texte, c'est-à-dire la
colonne suivante du caractère courant ou la première colonne du caractère suivant, à moins que le
caractère courant ne soit le dernier et que nous devions reboucler sur le premier caractère du texte,
répétant le texte du défilement sinusoïdal à l'infini :
addq.b #1,d4
btst #4,d4
beq _writeKeepChar
bclr #4,d4
clr.w d1
move.b (a0)+,d1
bne _writeNoTextLoop
lea text,a0
move.b (a0)+,d1
_writeNoTextLoop
subi.b #$20,d1
lsl.w #5,d1
move.l font16,a1
lea (a1,d1.w),a1
bra _writeKeepColumn
_writeKeepChar:
lea 2(a1),a1
_writeKeepColumn:
|
Cette colonne suivante se trouvera à une autre ordonnée, déterminée en décrémentant le décalage
courant du sinus... :
subq.w #(SINE_SPEED_PIXEL<<1),d0
bge _anglePixelNoLoop
add.w #(360<1),d0
_anglePixelNoLoop:
|
...et elle sera affichée dans la colonne suivante du plan de bits, éventuellement dans le mot suivant
le mot courant :
subq.b #1,d7
bge _pixelKeepWord
addq.w #2,d6
moveq #15,d7
_pixelKeepWord:
|
Dessiner à l'économie grâce au Blitter
L'affichage des colonnes au processeur est une tâche intensive pour ce dernier. Il existe un moyen
expéditif pour le soulager : recourir au Blitter.
Dans un précédent article, nous avons vu que le Blitter permet de copier des blocs et de tracer des
droites. Cette dernière fonctionnalité est particulièrement intéressante en l'espèce, car le Blitter
peut tracer une ligne reproduisant un motif de 16 pixels. Ce motif ne pourrait-il pas être la colonne
d'un caractère à dessiner ? Certainement. Nous allons donc utiliser le Blitter pour tracer autant de
lignes de 16 pixels, verticales et texturées, qu'il y a de colonnes du défilement sinusoïdal à dessiner.
Le dessin d'une colonne au processeur s'effectue de haut en bas, mais le dessin au Blitter doit s'effectuer
en sens inverse. En effet, le motif de la colonne est orienté de telle sorte que son bit 15 ne correspond
pas au premier pixel de la colonne, mais à son dernier.
Affichage d'une colonne d'un caractère au Blitter
Configurer le Blitter pour tracer des lignes est un peu fastidieux, car il faut stocker des valeurs
dans nombre de registres. Une bonne partie de cette initialisation peut être effectuée une fois pour
toutes les lignes à tracer. En effet, le contenu des registres concernés n'est pas modifié lors d'un
tracé de ligne. Il vaut donc pour toutes les lignes.
Commençons par définir quelques paramètres pour nous y retrouver :
LINE_DX=15 ;# de lignes de la droite -1 : LINE_DX=max (abs(15-0),abs(0,0))
LINE_DY=0 ;# de colonnes de la droite -1 : LINE_DY=min (abs(15-0),abs(0,0))
LINE_OCTANT=1
|
Procédons ensuite à la partie récurrente de l'initialisation du Blitter :
move.w #4*(LINE_DY-LINE_DX),BLTAMOD(a5)
move.w #4*LINE_DY,BLTBMOD(a5)
move.w #DISPLAY_DX>>3,BLTCMOD(a5)
move.w #DISPLAY_DX>>3,BLTDMOD(a5)
move.w #(4*LINE_DY)-(2*LINE_DX),BLTAPTL(a5)
move.w #$FFFF,BLTAFWM(a5)
move.w #$FFFF,BLTALWM(a5)
move.w #$8000,BLTADAT(a5)
move.w #(LINE_OCTANT<<2)!$F041,BLTCON1(a5) ;BSH3-0=15, SIGN=1, OVF=0,
;SUD/SUL/AUL=octant, SING=0, LINE=1
|
Pour chaque colonne à dessiner, nous devons fournir au Blitter l'adresse du mot contenant le pixel
de départ de la droite dans BPLCPTH/BLTCPTL et BLTDPTH/BLTDPTL, le numéro de ce pixel dans BLTCON0
et le motif de la droite dans BLTADAT.
WAITBLIT
lea LINE_DX*(DISPLAY_DX>>3)(a4),a4
move.l a4,BLTCPTH(a5)
move.l a4,BLTDPTH(a5)
move.w (a1),BLTBDAT(a5)
move.w d7,d2
ror.w #4,d2
or.w #$0B4A,d2
move.w d2,BLTCON0(a5) ;ASH3-0=pixel, USEA=1, USEB=0, USEC=1, USED=1, LF7-0=AB+AC=$4A
|
Une écriture dans BLTSIZE demandant le tracé d'une ligne de 16 pixels permet de lancer le Blitter :
move.w #((LINE_DX+1)<<6)!$0002,BLTSIZE(a5)
|
Ce code vient presque se substituer à celui utilisé pour dessiner une colonne au processeur. La seule
différence notable est la manière dont cette colonne est identifiée. Avec le processeur, c'est le
numéro du bit dans le mot courant qui est utilisé. Avec le Blitter, c'est le numéro du pixel dans
ce mot. Ces numérotations sont en sens inverse l'une de l'autre : le pixel 15 correspond au bit 0 ;
le pixel 14, au bit 1 ; etc.
Dans le source, les codes des versions au processeur et au Blitter coexistent. Une constante "BLITTER"
permet de basculer de l'une à l'autre pour tester (0 pour dessiner au processeur et 1 pour dessiner au Blitter) :
La valeur de cette constante conditionne la compilation de parties du code. Par exemple :
IFNE BLITTER
moveq #SCROLL_X,d7
and.w #$000F,d7
ELSE
moveq #SCROLL_X,d4
and.w #$000F,d4
moveq #15,d7
sub.b d4,d7
ENDC
|
C'est fini ! Le défilement sinusoïdal s'affiche. Toutefois, il reste à l'enjoliver en rajoutant
quelques effets peu coûteux en cycles, et surtout à en optimiser le code...
|