Obligement - L'Amiga au maximum

Jeudi 19 juin 2025 - 10:53  

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 - WAIT, SKIP et COPJMPX : un usage avancé du Copper
(Article écrit par Yragael et extrait de www.stashofcode.fr - août 2017, janvier 2019)


La programmation du Copper sur Amiga est très particulière. Tout d'abord, ce coprocesseur graphique ne comprend que trois instructions, MOVE, WAIT et SKIP, auxquelles il est toutefois possible de rajouter JUMP, quoiqu'elle ne se programme pas comme les autres. Ensuite, on n'écrit pas du code pour le Copper comme on écrit du code pour le 68000 : point de mnémoniques, il faut écrire directement en codes opérations. Enfin, WAIT et SKIP présentent certaines limitations dont la prise en compte conduit à complexifier le code dès que ce dernier doit pouvoir fonctionner quelle que soit la ligne que le faisceau d'électrons - le raster - est en train de balayer à l'écran.

Paradoxalement, ces limitations n'entravent généralement pas la programmation d'effets visuels complexes comme le plasma ou l'affichage d'une image true color en plein écran - oui, l'Amiga 500 peut afficher en true color, et sans utiliser aucun plan de bits par ailleurs -, c'est-à-dire sur toute la largeur des 320 pixels et toute la hauteur des 256 lignes de pixels d'une surface classiquement utilisée dans une démo en PAL. La surprise du codeur n'en est que plus grande quand il s'y heurte. Pour les dépasser, il faut exploiter l'information qui réside dans une section assez cryptique de l'Amiga Hardware Reference Manual. Explications.

Usage avancé du Copper
Un effet plasma (un plasma RGB pour être plus exact)

  • Mise à jour du 14/01/2019 : j'ai oublié de mentionner qu'ayant la flemme de terminer la rédaction de la seconde partie de cet article, j'en ai inclus l'essentiel ici. En effet, le rotozoom s'appuie sur une solution au problème évoqué à la fin du présent article.
NB : cet article se lit mieux en écoutant l'excellent module Skidtro composé par Sun/Dreamdealers pour la cracktro de Road Rash, mais c'est affaire de goût personnel...

Un bref rappel des bases du Copper

Avec le Copper, il est possible de modifier la valeur de certains registres à l'aide d'une instruction MOVE. Comme toute instruction du Copper, MOVE se programme en composant son code opération sous la forme de deux mots successifs dans la liste des instructions qui constitue le programme que le Copper exécute à chaque trame, la liste Copper.

Le premier mot d'un MOVE fournit le décalage du registre matériel dans lequel le Copper doit écrire une valeur - ce décalage est donné à partir de l'adresse $DFF000, soit par exemple $0180 pour le registre COLOR00 qui de spécifier les valeurs des composantes rouge, vert et bleu de la couleur 0, dit couleur de fond. Le second mot fournit la valeur à écrire dans ce registre. Ce qui donne :

Usage avancé du Copper
Le codage de l'instruction MOVE

Ainsi, le MOVE suivant passe COLOR00 à $0F00 (rouge) :

DC.W $0180,$0F00

Le Copper prend un certain temps pour exécuter un MOVE, celui qu'il faut au faisceau d'électrons pour balayer huit pixels en basse résolution. Par conséquent, une série de MOVE dans COLOR00 va produire à l'écran une séquence de segments de huit pixels, chacun d'une certaine couleur.

Par exemple, la série de MOVE suivante produit trois segments, respectivement rouge, vert et bleu :

DC.W $0180,$0F00
DC.W $0180,$00F0
DC.W $0180,$000F

Au maximum, il est donc possible de produire le résultat de 40 MOVE à la suite, soit 40 segments de couleurs différentes, sur une ligne d'un écran en basse résolution (320 pixels de large). C'est qui permet de produire l'effet dit de ligne Copper, où les valeurs des MOVE successifs proviennent d'un dégradé parcouru comme un barillet à partir d'un décalage incrémenté à chaque trame pour produire une animation :

Usage avancé du Copper
Une ligne Copper

Cliquez ici pour télécharger le source du code minimal pour une ligne Copper.

Noter que pour produire cet effet à une certaine hauteur de l'écran, il faut précéder les MOVE d'un WAIT.

Le premier mot d'un WAIT fournit les positions horizontale et verticale auxquelles le Copper doit comparer la position courante du raster tandis que ce dernier balaie l'écran pour afficher l'image à chaque trame. Le second mot fournit des masques à appliquer aux positions attendue et courante, ce qui détermine donc les bits de ces dernières sur lesquelles porte la comparaison. Ce qui donne :

Usage avancé du Copper
Le codage de l'instruction WAIT

La comparaison est de type "supérieur ou égal" : pour que le Copper passe à l'instruction WAIT, il faut que la position courante soit supérieure ou égale à la position attendue. Autrement dit, et c'est un point important, si le raster a déjà passé la position attendue quand le Copper tombe sur le WAIT dans la lite Copper, le Copper ne reste pas planté devant le WAIT en attendant que le raster repasse à cette position lors la trame suivante : il passe à l'instruction suivante dans la liste Copper.

Il n'aura pas échappé au lecteur que la position horizontale et son masque sont programmés sur sept bits à partir du bit 1, tandis que la position verticale est codée sur huit bits sauf pour son masque, dont le bit 7 apparaît exclu. Comme nous le verrons, ces particularités sont autant de sources de complexification. Pour l'heure, retenons que le bit BFD doit être à 1 (il sert à indiquer au Copper qu'il doit attendre le Blitter, ce qui ne présente aucun intérêt ici) et que les bits 0 du premier et second mot doivent être à 0 et 1 respectivement - le Copper se base sur la combinaison des bits 0 des deux mots d'un code opération pour déterminer à quelle instruction il a affaire.

Par exemple, il est possible d'attendre la position horizontale X à la position verticale Y ainsi (les masques ne sont pas utilisés, tous leurs bits étant à 1) :

DC.W (Y<<8)!X!$0001
DC.W $8000!($7F<<8)!$FE

Dans le source de la ligne Copper, la position horizontale attendue pour débuter les MOVE est $3E. Cette position coïncide avec le début d'un plan de bits quand la position horizontale spécifiée dans DIWSTRT est classiquement $81. Elle se retrouve donc souvent.

La possibilité de pouvoir attendre le début de chaque ligne pour modifier la couleur de fond - ou tout autre couleur - en série et produire une ligne Copper est mise à profit pour produire l'effet plasma présenté plus tôt. Dans un tel effet, ce sont 41 MOVE qui sont effectués par ligne. En effet, une petite subtilité consiste à exploiter la précision de la position horizontale d'un WAIT, qui est de quatre pixels en basse résolution, pour décaler une ligne par rapport à la précédente et la suivante et éviter ainsi un effet trop pixelisé :

Usage avancé du Copper
Le même effet plasma, sans décalage de quatre pixels une ligne sur deux

Dans ces conditions, une ligne sur deux permet d'afficher les huit pixels produits par 39 MOVE plus les quatre pixels produits par deux MOVE supplémentaires, soit un total de 41 MOVE. Pour simplifier le code qui modifie les couleurs dans la liste Copper, ce nombre de MOVE par ligne est généralisé à l'ensemble des lignes, décalées ou non.

Cliquez ici pour télécharger le source du code minimal pour un plasma RGB. Si le code est intégralement original, précisons qu'il exploite la technique de séparation des composantes R, G et B décrite par Stéphane Rubinstein dans Amiga News Tech n°31 (mars 1992).

De l'intérêt de boucler au Copper

Le Copper peut servir à bien plus qu'attendre le raster en début de ligne pour procéder à une série de MOVE. L'Amiga Hardware Reference Manual documente deux usages avancés du Copper : pour activer le Blitter et pour réaliser des boucles. C'est sur ce dernier usage que nous allons nous pencher.

Avant de rentrer dans le vif du sujet, précisons qu'il n'est pas évident de trouver un intérêt aux boucles au Copper, et de là à rentrer dans les particularités des instructions WAIT et SKIP, sources comme déjà dit de complexification. La raison en sera exposée plus tard. Pour l'heure, contentons-nous de dire que paradoxalement, il est parfaitement possible de réaliser des effets complexes au Copper sans vraiment savoir comment il fonctionne - c'est cette leçon qui donnera à cet article une portée propre à susciter l'intérêt du lecteur qui n'est pas vintage, du moins nous l'espérons.

Pour illustrer le propos, nous pouvons tout de même nous appuyer sur un exemple concret, celui de l'affichage d'une image en true color au Copper. Eh ! Somme toute, la technique présentée à l'instant pour produire un plasma ne permet-elle pas tout simplement de disposer d'un fond d'écran de 40x32 "pixels" en PAL (chaque "pixel" étant représenté à l'écran par un segment de huit pixels de large et un pixel de haut) ? Dès lors, pourquoi donc ne pas chercher à y afficher une image dont les valeurs des composantes R, G et B de la couleur de chaque pixel sont données sans renvoi à une palette, donc en true color ?

Pour cela, il suffit de convertir une image normale en séries de MOVE. Prenons une image en RAW Blitter (RAWB) de PICTURE_DX sur PICTURE_DY pixels sur PICTURE_DEPTH plans de bits, c'est-à-dire en un << PICTURE_DEPTH couleurs. Pour rappel, on désigne par RAWB un format trivial de fichier image, puisque le contenu du fichier est organisé ainsi :

Ligne 0 dans le plan de bits 1
...
Ligne 0 dans le plan de bits N
...
Ligne PICTURE_DY-1 dans le plan de bits 1
...
Ligne PICTURE_DY-1 dans le plan de bits N
Palette (suite de mots)

Définissons quelques constantes... :

PICTURE_DX=320
PICTURE_DY=256
PICTURE_DEPTH=5

...intégrons le fichier de l'image à l'exécutable... :

picture:	incbin "picture.rawb"

...et convertissons l'image :

	lea picture,a0
	movea.l a0,a1
	addi.l #PICTURE_DEPTH*PICTURE_DY*(PICTURE_DX>>3),a1
	movea.l moves,a2
	move.w #PICTURE_DY-1,d1
_convertY:
	move.w #(PICTURE_DX>>3)-1,d0
_convertX:
	moveq #7,d2
_convertByte:
	clr.w d4
	clr.w d5
	moveq #1,d6
	moveq #PICTURE_DEPTH-1,d3
_convertByteBitplanes:
	btst d2,(a0,d4.w)
	beq _convertBit0
	or.b d6,d5
_convertBit0:
	add.b d6,d6
	add.w #PICTURE_DX>>3,d4
	dbf d3,_convertByteBitplanes
	add.w d5,d5
	move.w (a1,d5.w),(a2)+
	dbf d2,_convertByte
	lea 1(a0),a0
	dbf d0,_convertX
	lea (PICTURE_DEPTH-1)*(PICTURE_DX>>3)(a0),a0
	dbf d1,_convertY

Le convertisseur doit retrouver l'indice de la couleur pour chaque pixel, ce qui génère un nombre outrancier de tests, mais le résultat est tout de même atteint assez rapidement. De toute manière, il ne s'agit que d'une phase d'initialisation. Peu importe donc d'optimiser, car les performances de la boucle principale ne seront pas péjorées.

Ces MOVE servent à constituer une liste Copper : pour afficher 40 pixels d'une ligne sous la forme de "pixels", il suffit de reprendre la série de MOVE correspondante. Toutefois, cela ne produira à l'écran qu'une série de segments de 8x1 pixels. Pour donner au résultat l'apparence d'un écran certes de très basse résolution, mais un écran tout de même, nous devons en toute logique répéter chaque ligne huit fois. Ainsi, un "pixel" apparaitra à l'écran sous la forme d'un bloc de 8x8 pixels.

Par exemple, reprenons un échantillon de la somptueuse (les mots manquent...) image Dragonsun de ce Michel-Ange du graphisme sur Amiga, je veux nommer Cougar du groupe Sanity, vainqueur par K.O. lors de la compétition The Party en 1993 :

Usage avancé du Copper
Une image true color sur Amiga 500 (échantillon de Dragonsun par Cougar/Sanity)

Il serait tout à fait possible de nous contenter de bourrer la liste Copper de séries de 40 MOVE en répétant une série huit fois d'affilée, chaque série étant précédées d'un WAIT qui indique au Copper qu'il doit commencer à l'exécuter au début d'une ligne de l'écran.

Toutefois, il semble que cela présenterait deux inconvénients :
  • Ce serait consommateur de mémoire.
  • Cela ferait beaucoup de MOVE à modifier pour modifier l'image dans le fond d'écran.
Pour réduire le nombre de MOVE, il est possible d'utiliser l'instruction SKIP du Copper.

WAIT et SKIP pour faire boucler le Copper

Considérons qu'il faut constituer une liste Copper dont l'adresse figure dans le registre A0. Chaque bloc de huit lignes est composé d'une série COPPER_DX MOVE (40 pour couvrir les 320 pixels d'un écran en basse résolution) qu'il faut répéter à chacune des COPPER_DY lignes de l'écran (32 pour couvrir les 256 lignes de ce même écran en PAL). Commençons donc par attendre la première ligne à laquelle afficher la première ligne de blocs, à savoir COPPER_Y, qui correspond généralement $2C pour désigner le haut de l'écran :

	move.w #((COPPER_Y&$00FF)<<8)!$0001,d0			;D0 X=0 Y=y
	move.w d0,(a0)+
	move.w #$8000!($7F<<8)!$FE,(a0)+			;WAIT X=0 Y=COPPER_Y

C'est une instruction WAIT classique, où le masque de position verticale est $7F et celui de position horizontale est $FE, ce qui indique au Copper qu'il doit comparer la position du raster avec tous les bits de la position verticale et de la position horizontale que nous lui indiquons.

Modifions la position à laquelle le Copper attendra désormais le raster, à moins que nous en masquions certains bits. Par défaut, le Copper attendra en (COPPER_X, COPPER_Y), COPPER_X correspondant généralement à $81 pour désigner le côté gauche de l'écran :

	or.w #(((COPPER_X-4)>>2)<<1)&$00FE,d0			;D0 X=x Y=y

Mémorisons l'adresse à laquelle nous en sommes, car cela va servir plus loin :

_copperListRows:
	move.l a0,d2

Attendons le raster à la position horizontale à laquelle doit figurer le premier "pixel". Nous l'attendons sur une ligne quelconque, en utilisant pour cela la possibilité de masquer les bits de la position verticale :

	move.w d0,(a0)+
	move.w #($00<<8)!$FE,(a0)+				;WAIT X=x Y=?

C'est alors que nous pouvons fournir la série de MOVE pour représenter à l'écran la première ligne de la série de COPPER_DX "pixels". En considérant que nous avons stocké l'adresse des MOVE résultant de la conversion de l'image dans A1, cela donne :

	move.w #COPPER_DX-1,d3
_copperListCols:
	move.w #COLOR00,(a0)+
	move.w (a1)+,(a0)+
	dbf d3,_copperListCols

Les choses deviennent alors subtiles. En effet, nous allons demander au Copper de reboucler sur le WAIT précédent.

Comment demander au Copper de commencer à exécuter une liste d'instructions données ? En modifiant l'adresse que le Copper lit dans les registres COP1LCH et COP1LCL quand une valeur quelconque est écrite dans le registre COPJMP1. C'est l'équivalent d'un JUMP du 68000, d'où le nom de ce registre, et c'est en cela "l'instruction" JUMP du Copper.

Or, ces registres font partie de ceux dans lesquels le Copper peut écrire. Il est donc possible de demander au Copper d'écrire l'adresse sauvegardée plus tôt dans COP1LCH et COP1LCL :

	move.w #COP1LCL,(a0)+
	move.w d2,(a0)+						;MOVE COP1LCL
	swap d2
	move.w #COP1LCH,(a0)+
	move.w d2,(a0)+						;MOVE COP1LCH

La suite naturelle serait rajouter un MOVE pour écrire une valeur quelconque, par exemple 0, dans COPJMP1, provoquant le saut du Copper.

De fait, s'il retombe sur le second WAIT, le Copper va attendre le raster à la position horizontale COPPER_X sur une ligne quelconque. Et comme il va retomber sur ce WAIT alors que le tracé des 320 pixels représentant les COPPER_DX MOVE vient de s'achever sur la ligne N, le Copper va attendre le raster à la position horizontale COPPER_X de la ligne suivante, la ligne N+1. C'est ainsi que le Copper va exécuter de nouveau les COPPER_DX MOVE sur cette ligne.

Ainsi, nous n'avons pas à répéter ces MOVE sur sept lignes, ce qui nous fait économiser de la mémoire et ce qui limite l'ampleur de MOVE à modifier pour modifier l'image affichée sous la forme de "pixels".

Toutefois, c'est uniquement huit fois que le Copper doit exécuter la série de MOVE. Avant d'écrire dans COPJMP1, il faut donc qu'il vérifie si le raster n'a pas dépassé la position à partir de laquelle il a tracé la dernière ligne. Si tel est le cas, le Copper doit ignorer le COPJMP1. C'est exactement ce que permet un SKIP.

Un SKIP se code exactement comme un WAIT. Son premier mot fournit les positions horizontale et verticale auxquelles le Copper doit comparer la position courante du raster. Le second mot fournit des masques à appliquer aux positions attendue et courante, ce qui détermine donc les bits de ces dernières sur lesquelles porte la comparaison. Ce qui donne :

Usage avancé du Copper
Le codage de l'instruction SKIP

Il suffit donc de réutiliser la valeur du premier mot utilisé pour programmer le WAIT, après avoir incrémenté la position verticale de 7 :

	addi.w #$0700,d0
	move.w d0,(a0)+
	move.w #$8000!($7F<<8)!$FE!$0001,(a0)+			;SKIP X=x Y=y+7

Si le SKIP n'est pas validé, le Copper doit donc reboucler sur la série de MOVE en écrivant dans COPJMP1 :

	move.w #COPJMP1,(a0)+
	move.w #$0000,(a0)+					;MOVE COPJMP1

Il faut alors qu'il tombe sur un WAIT lui indiquant d'attendre le raster à la position horizontale COPPER_X sur la ligne d'après :

	addi.w #$0100,d0

Pour finir, nous pouvons passer à la ligne suivante dans l'image, et reboucler sur la production des instructions correspondant à la série suivante de 40 "pixels" :

	lea (PICTURE_DX-40)<<1(a1),a1
	dbf d1,_copperListRows

Avant _copperListRows, D1 aura été initialisé avec le nombre de lignes de "pixels" à afficher moins une, soit COPPER_DY-1 :

	move.w #COPPER_DY-1,d1

Au total, cela donne :

	movea.l moves,a1
	move.w #((COPPER_Y&$00FF)<<8)!$0001,d0			;D0 X=0 Y=y
	move.w d0,(a0)+
	or.w #(((COPPER_X-4)>>2)<<1)&$00FE,d0			;D0 X=x Y=y
	move.w #$8000!($7F<<8)!$FE,(a0)+			;WAIT X=0 Y=COPPER_Y
	move.w #COPPER_DY-1,d1
_copperListRows:
	move.l a0,d2
	move.w d0,(a0)+
	move.w #$8000!($00<<8)!$FE,(a0)+			;WAIT X=x Y=?
	move.w #COPPER_DX-1,d3
_copperListCols:
	move.w #COLOR01,(a0)+
	move.w (a1)+,(a0)+
	dbf d3,_copperListCols
	lea (PICTURE_DX-COPPER_DX)<<1(a1),a1
	move.w #COP1LCL,(a0)+
	move.w d2,(a0)+						;MOVE COP1LCL
	swap d2
	move.w #COP1LCH,(a0)+
	move.w d2,(a0)+						;MOVE COP1LCH
	addi.w #$0700,d0
	move.w d0,(a0)+
	move.w #$8000!($7F<<8)!$FE!$0001,(a0)+			;SKIP X=x Y=y+7
	move.w #COPJMP1,(a0)+
	move.w #$0000,(a0)+					;MOVE COPJMP1
	addi.w #$0100,d0
	dbf d1,_copperListRows

Toutefois, le Copper nous réserve une surprise...


[Retour en haut] / [Retour aux articles]