Obligement - L'Amiga au maximum

Vendredi 23 mai 2025 - 18:13  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

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


Trucs et astuces

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


Glossaire

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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


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 :

Programmer un défilement sinusoïdal
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 :

Programmer un défilement sinusoïdal
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.

Programmer un défilement sinusoïdal
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) :

BLITTER=1

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...


[Retour en haut] / [Retour aux articles] [Article précédent] / [Article suivant]