|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. ![]() Un effet plasma (un plasma RGB pour être plus exact)
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 : Ainsi, le MOVE suivant passe COLOR00 à $0F00 (rouge) :
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 :
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 : ![]() 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 : 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) :
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é : ![]() Le même effet plasma, sans décalage de quatre pixels une ligne sur deux 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 :
Définissons quelques constantes... :
...intégrons le fichier de l'image à l'exécutable... :
...et convertissons l'image :
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 : ![]() Une image true color sur Amiga 500 (échantillon de Dragonsun par Cougar/Sanity) Toutefois, il semble que cela présenterait deux inconvénients :
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 :
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 :
Mémorisons l'adresse à laquelle nous en sommes, car cela va servir plus loin :
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 :
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 :
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 :
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 : 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 :
Si le SKIP n'est pas validé, le Copper doit donc reboucler sur la série de MOVE en écrivant dans 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 :
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" :
Avant _copperListRows, D1 aura été initialisé avec le nombre de lignes de "pixels" à afficher moins une, soit COPPER_DY-1 :
Au total, cela donne :
Toutefois, le Copper nous réserve une surprise...
|