|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Quoi de plus confortable qu'un sprite ? Le coprocesseur graphique l'affiche en laissant voir le décor derrière ses pixels transparents, et il préserve le décor derrière ses autres pixels pour le restaurer quand le sprite est déplacé. Par ailleurs, il découpe le sprite s'il sort de l'écran. Malheureusement, les capacités de l'Amiga 500 sont en la matière très limitées. Huit sprites de 16 pixels de large, même de hauteur infinie, en quatre couleurs dont une transparente, seulement ? C'est la dèche... Les sprites n'en conservent pas moins une certaine utilité, à condition d'en utiliser pleinement le potentiel. Ainsi, il est possible de les attacher pour former jusqu'à un bitmap de 64 pixels de large, de hauteur infinie, en 16 couleurs dont une transparente, notamment. Ou alors, il est possible d'en réutiliser pour créer un champ de jeu ("playfield") supplémentaire, dont le contenu devra toutefois être un motif répétitif qui, dans le meilleur des cas, occupera 48 pixels de large sur une hauteur infinie, en peu de couleurs. Par ailleurs, l'Advanced Graphics Architecture, dont l'Amiga 1200 est doté, dispose de fonctionnalités un peu étendues en matière de sprites. En particulier, leur largeur unitaire passe de 16 à 64 pixels. ![]() Des sprites un peu plus mieux en AGA
Le B-A-BA : afficher un sprite, pour commencer Cet article présume une certaine familiarité, pour ne pas dire une familiarité certaine, avec la programmation en assembleur du matériel de l'Amiga. Pour s'initier à cette dernière, et notamment installer un environnement de développement, le mieux est se reporter à la série d'articles publiés ici, qui portent sur la programmation d'un défilement sinusoïdal : 1, 2, 3, 4, et 5. L'Amiga dispose d'un coprocesseur graphique nommé Copper. Le Copper exécute une série d'instructions à chaque rafraîchissement de l'écran. Ces instructions sont notamment des écritures dans des registres du matériel, dont certains contrôles les sprites. Le matériel peut afficher huit sprites de 16 pixels de large en quatre couleurs dont une couleur transparente, sur une hauteur illimitée. Les sprites sont couplés (le 0 avec le 1, le 2 avec le 3, etc.). Dans un couple, les sprites partagent la même palette de quatre couleurs, qui n'est qu'un sous-ensemble de la palette de 32 couleurs utilisée pour afficher des pixels à l'écran. Dans ces conditions, la structure de cette dernière palette est la suivante (la mention aux champs de jeu sera expliquée plus tard) :
Un sprite est intégralement décrit par une suite de mots. Les deux premiers sont des mots de contrôle, les suivants sont des mots décrivant ligne par ligne de petits plans de bits, et les derniers sont des 0. Par exemple, pour un sprite de deux lignes :
Pour chaque ligne du sprite, les deux mots sont combinés entre eux pour en déduire les indices des couleurs des 16 pixels de la ligne. Pour poursuivre sur cet exemple, le résultat pour la première ligne est celui représenté ici : Ainsi qu'il est possible de le constater, le premier mot de la ligne fournit les bits 0 de la ligne, et le second mot en fournit les bits 1. Les coordonnées (X, Y) et la hauteur d'un sprite (déduite de l'ordonnée Y + DY de la ligne qui suit la dernière ligne du sprite) sont codées dans les mots de contrôle de manière assez exotique (le bit 7 du second mot est réservé pour l'attachement, ce dont il sera question plus loin). Par exemple, pour afficher un sprite en haut à gauche de l'écran qui débute en classiquement en ($81, $2C) : Oui, contrairement à ce que prétend l'Amiga Hardware Reference Manual, il faut bien coder X - 1 et non X dans les mots de contrôle. C'est une erreur de la documentation. Concrètement, il faut donc coder les mots de contrôle ainsi (dans ASM-One, le point d'exclamation est un OR, ">>" est un décalage non signé sur la droite) :
Pour s'éviter de tels calculs chaque fois qu'il faut déplacer un sprite, une technique consiste à précalculer des mots à combiner pour chacune des 320 positions horizontales, d'une part, et chacune des positions verticales, d'autre part. Pour en savoir plus, référez-vous à ce fil de discusion sur le forum d'English Amiga Board. La suite des mots constituant les données d'un sprite étant écrite, il suffit d'en communiquer l'adresse (qui doit être paire) au matériel pour qu'il affiche le sprite. Chaque sprite dispose de registres SPRxPTH et SPRxPTL pour cela. Par exemple :
Dans les faits, les adresses des sprites ne sont jamais communiquées ainsi par le processeur. Des instructions MOVE sont rajoutées dans la liste Copper pour que le Copper les communique à chaque trame :
Les sprites ne sont affichés que si le matériel peut accéder à leurs données, ce pour quoi il bénéficie d'un accès direct en mémoire (DMA). L'usage du DMA n'est pas incontournable - il est possible de s'y substituer en écrivant au processeur dans les divers registres où le DMA écrit les données des sprites pour les communiquer au matériel : SPRxPOS, SPRxCTRL, SPRxDATB et SPRxDATA. Le programme spriteCPU.s procède ainsi :
Toutefois, cette technique présente peu d'intérêt, sinon aucun. C'est que pour afficher un sprite, il faudrait donc écrire une boucle qui attendrait le faisceau d'électron à chaque ligne à laquelle une ligne du sprite devrait être affichée avant de modifier le contenu des registres SPRxDATB et SPRxDATA avec les données de la nouvelle ligne du sprite, ce qui serait extrêmement contraignant. Partant, le DMA n'est pas émulé, mais utilisé. Pour afficher les sprites, il est donc nécessaire d'activer aux moins les canaux DMA du Copper, des plans de bits et des sprites :
Comme cela peut le suggérer, il n'existe pas de mécanisme pour activer sélectivement tel ou tel des huit sprites. Pour ne pas afficher un sprite, il faut spécifier au matériel que la hauteur du sprite est nulle, ce qui s'effectue en faisant pointer ses données sur des mots de contrôle à 0 :
Tant qu'il est question de DMA, un point de détail, mais qui a tout de même son importance, doit être mentionné. Il faut attendre un blanc vertical pour couper le canal DMA des sprites. A défaut, si un sprite était en cours d'affichage, ses données continuent d'être affichées. Cela produit un effet bien connu de "sprite qui bave verticalement". En effet, comme expliqué précédemment lorsqu'il a été question de l'émuler au processeur, le DMA ne sert qu'à alimenter des registres SPRxPOS, SPRxCTL, SPRxDATA et SPRxDATB dans lesquels le matériel lit systématiquement les données des sprites qu'il combine à celle des plans de bits. S'il est coupé avant d'avoir écrit le dernier jeu de mots à 0 dans SPRxPOS et SPRxCTL (ce qu'il fait lorsqu'il sait que la hauteur du sprite a été parcourue, ce qui explique pourquoi ces deux mots doivent figurer à la fin des données d'un sprite), le DMA ne peut donc interrompre l'affichage du sprite. Partant, ce dernier continue d'être affiché sur tout le reste de la hauteur de l'écran avec les données figurant dans SPRxDATA et SPRxDATB au moment où le DMA a été coupé. C'est pourquoi les programmes proposés attendent le blanc vertical (VERTB), c'est-à-dire le moment où le faisceau d'électrons a terminé de tracer l'écran, pour couper le DMA (la boucle est factorisée dans la sous-routine _waitVERTB) :
La cinétique : déplacer les sprites et détecter les collisions Pour déplacer un sprite, il suffit de modifier ses coordonnées dans ses mots de contrôle à la fin d'une trame, avant que le matériel n'en refasse la lecture pour afficher la nouvelle trame. Le programme "sprite.s" fait ainsi se déplacer un sprite 0 de 16 pixels de hauteur en quatre couleurs, sur un décor composé d'un unique plan de bits représentant un damier :
Dans le cas d'un seul champ de jeu, PF2P2-0 (et non PF1P2-0, comme on l'aurait présumé) sont utilisés. Ils permettent de programmer sur 3 bits le numéro du couple de sprites (et non du sprite !) derrière lequel se trouve le champ de jeu : ![]() Ah ! Je vous ai bien eu. Cet article traite d'Amiga et non d'Androïd ! Dans les exemples déjà cités des sprites en 4 et 16 couleurs, le sprite 0 est positionné devant l'unique champ de jeu en stockant donc $0008 dans BPLCON2. Enfin, il faut mentionner que le matériel permet de détecter facilement une collision entre sprites, ou entre sprite et plans de bits, et cela au pixel près. Le programme "spritesCollision.s" montre comment il est possible de détecter les collisions entre le sprite 0 et le sprite 2, et entre chacun de ces sprites et le plan de bits 1 : ![]() Détection de collisions entre sprites et entre sprites et plan de bits
Il suffit de lire CLXDAT (attention, car cela le remet à 0) pour récupérer à chaque trame l'état des collisions détectées entre deux entités (entre sprites, ou entre sprite et plan de bits, ou entre plans de bits). Sur cette figure, on trouve à côté de chaque bit une case par sprite (0 à 7) ainsi qu'une case pour les plans de bits impairs (I) et une pour les plans de bits pairs (P). Les cases sont colorées pour indiquer la collision détectée (première entité en rouge, seconde en vert). ![]() La composition du registre CLXDAT
En permettant de spécifier sur quelle valeur de bit une collision doit être détectée entre un sprite et certains plans de bits, le matériel permet très facilement de spécifier qu'une collision ne doit être détectée que si un sprite rencontre un pixel d'un certaine couleur (éventuellement transparente) dans le décor. Puissant. Toutefois, cette détection au pixel près n'est pas nécessairement intéressante. Un codeur de jeu vidéo sur Amiga m'a expliqué qu'il convenait mieux d'utiliser des zones de collisions sommaires, incluses dans les entités et le décor, pour moins frustrer le joueur. A cet égard, la détection de collisions du matériel reste rudimentaire. Peut-être a-t-elle été intégrée en référence à une époque où les pixels étaient si gros que cela ne posait pas de problème de jouabilité. Plus de sprites : attacher et/ou réutiliser (multiplexer) les sprites Avec quatre couleurs, dont une transparente, la palette d'un sprite est particulièrement limitée. Sans doute, il est possible de superposer des sprites pour multiplier les couleurs, mais superposer deux sprites ne permet d'étendre la palette qu'à huit couleurs, dont deux transparentes. Par ailleurs, il faut se rappeler que les sprites sont couplés, et que les sprites d'un même couple partagent la même palette de quatre couleurs. Au mieux, il serait donc possible de superposer quatre sprites, étendant la palette à 16 couleurs, dont qutre transparentes. Sachant qu'il y a huit sprites, il ne serait donc possible que d'afficher deux sprites en douze couleurs ? Non. Comme mentionné plus tôt, les deux sprites formant un couple peuvent être combinés pour former un unique sprite, toujours de 16 pixels de large, mais cette fois affiché en 16 couleurs (les couleurs 16 à 31 de la palette de l'écran, la couleur 16 étant transparente). Les lignes du premier sprite fournissent les bits des petits plans de bits 1 et 2 du sprite, tandis que les lignes du second sprite fournissent les bits des petits plans de bits 3 et 4. Pour combiner ainsi deux sprites d'un couple, il faut positionner le bit 7 du second mot de contrôle du second sprite du couple : c'est l'attachement. Par ailleurs, les deux sprites doivent être affichés à la même position, ce qui implique donc de les déplacer simultanément - là où ils ne se chevauchent pas, chacun est affiché dans la palette commune de quatre couleurs. Le programme "sprite16.s" fait ainsi se déplacer un sprite de 16 couleurs composé des sprites 0 et 1 sur le même décor que précédemment :
![]() Réutiliser les sprites d'une ligne à l'autre pour produire un fond étoilé (multiplexage vertical) Pour y parvenir, il suffit de modifier la structure des données du sprite. Les deux derniers mots à 0 doivent être remplacés par de nouveaux mots de contrôle, qui précisent les coordonnées où afficher de nouveau le sprite. Par exemple :
L'ordonnée de la nouvelle occurrence du sprite est contrainte. A la base, elle est nécessairement supérieure à celle de la dernière ligne affichée de la précédente occurrence du sprite. Mais il y a plus : il faut attendre une ligne à l'écran entre deux occurrences du sprite. En effet, à chaque ligne de l'écran, le DMA ne dispose que du temps d'alimenter le matériel avec deux mots par sprite. Ainsi, s'il lit les nouveaux mots de contrôle du sprite durant une ligne, il n'a pas le temps de lire les mots de la première ligne des petits plans de bits du sprite durant cette dernière ; c'est à la ligne suivante qu'il le pourra. En conséquence, un sprite affiché jusqu'en Y ne peut être de nouveau affiché qu'à partir de Y+2. Ce qui explique pourquoi dans le programme "spritesField.s", il n'y a que 256/17=15 occurrences de 16 pixels de hauteur à l'écran. L'astuce : cherche sprites pour plan sympa à plusieurs S'ils sont peu nombreux et peu colorés, les sprites n'en disposent donc pas moins d'atouts pour séduire le codeur. C'est d'autant plus vrai que leur potentiel s'étend au-delà de ce qui est documenté dans l'Amiga Hardware Reference Manual. En particulier, il est possible de réutiliser des sprites horizontalement sur toute la largeur du champ de jeu. C'est énorme, car cela permet tout simplement de rajouter un champ de jeu. Comme Codetapper l'a documenté sur son excellent site, l'astuce a été mise à profit avec diverses variantes dans de nombreux jeux à succès. Le programme "triplePlayfield.s" en fournit une illustration en mettant en place un triple champ de jeu. Dans cette configuration, deux champs de jeu sont affichés en double champ de jeu, en suivant les instructions prodiguées dans l'Amiga Hardware Reference Manual. Par-dessus, un champ de jeu de sprites est affiché en exploitant l'astuce de la réutilisation horizontale. Ce troisième champ de jeu est composé de trois couples de sprites en 16 couleurs, réutilisés horizontalement. Si les sprites n'occupent que 32 pixels de hauteur (de quoi afficher un chiffre et un damier de couleurs), il est entendu qu'ils peuvent s'étendre sur toute la hauteur de l'écran : ![]() Un triple champ de jeu, dont un champ de jeu de sprites (multiplexage horizontal) Pour comprendre comment parvenir à notre bien plus modeste résultat, il faut regarder dans la liste Copper. Cette dernière contient notamment une section générée ainsi :
Le codage des instructions WAIT et MOVE du Copper a été détaillée cet article. Il suffit donc de noter que ce code génère un WAIT au début de chaque ligne de l'écran, suivi de 20 séries de MOVE qui modifient successivement les registres SPR0POS à SPR5POS. Comme expliqué plus tôt, un registre SPRxPOS contient le premier mot de contrôle d'un sprite, donc les 8 bits de poids fort de sa position horizontale. Dans sa description du fonctionnement du DMA des sprites, l'Amiga Hardware Reference Manual précise que la position horizontale du faisceau d'électron est comparée à chaque pixel à celle qui figure dans SPRxPOS pour décider d'afficher ou non les données chargées dans SPRxDATA et SPRxDATB. Autrement dit, si l'on change la position horizontale d'un sprite dans SPRxPOS après que ce sprite a été affiché, il est possible de provoquer de nouveau l'affichage de ce sprite sur la même ligne. C'est tout l'objet des séries de MOVE. Prenons le cas des sprites 0 et 1, qui sont attachés pour former un sprite en 16 couleurs, et donc affichés à la même position, en haut à gauche de l'écran. Le matériel lit les données des plans de bits et des sprites à afficher par paquet de 16 pixels. Le premier WAIT attend le faisceau d'électrons à la position $38, qui correspond à la première de ces lectures qui précède le début de l'affichage (la position horizontale stockée dans DDFSTRT). Tandis que le matériel affiche les 16 pixels des sprites 0 et 1, SPR0POS et SPR1POS sont modifiés par deux MOVE qui reportent la position horizontale des sprites de 16 pixels sur la droite. Comme le Copper prend 16 pixels à exécuter ces MOVE, les nouvelles valeurs des registres sont prises en compte par le matériel à la lecture suivante qu'il fait d'un paquet de 16 pixels à afficher. Ainsi, ces sprites sont de nouveau affichés à leur nouvelle position commune ! La difficulté serait de bien synchroniser les MOVE sur la lecture des données des 16 pixels que le matériel va afficher. Toutefois, cela tombe parfaitement : nul besoin d'intercaler des MOVE inutiles, l'équivalent du NOP pour le Copper, histoire de faire une pause en cours de ligne. Noter que c'est beaucoup plus acrobatique pour d'autres effets qui consistent pareillement à duper le matériel en cours de ligne, mais à des positions précises, notamment pour le zoom matériel horizontal, dont il sera question dans un éventuel prochain article. A ce stade, le lecteur attentif peut se poser deux questions. Pourquoi afficher un double champ de jeu à l'aide de deux plans de bits par champ de jeu (quatre couleurs par champ de jeu, dont une transparente), et non de trois (huit couleurs par champ de jeu, dont une transparente), comme le matériel le permet ? Et pourquoi réutiliser trois couples de sprites et non quatre, ici encore comme le matériel le permet ? C'est que l'astuce a ses limites. Tout accroc à l'accès direct au matériel ("metal basher") qui a lu son Amiga Hardware Reference Manual connaît le fameux schéma intitulé "DMA time slot allocation". Ce schéma permet de visualiser l'attribution de cycles disponibles durant une ligne que le matériel trace à l'écran. Certains cycles sont réservés à des fonctions impératives, comme la lecture des données des plans de bits. Les autres sont disponibles, notamment pour le Copper qui trouve ainsi le temps d'exécuter des MOVE (accessoirement, ceux qui se demandent pourquoi un MOVE prend huit pixels en basse résolution pour être exécuté trouveront l'explication dans le schéma). Le problème, c'est qu'au-delà de quatre plans de bits, le matériel commence à voler des cycles auxquels le Copper aurait pu prétendre pour lire les données des plans de bits supplémentaires : ![]() Les plans de bits 5 et 6 volent des cycles au Copper Par ailleurs, dans le programme "triplePlayfield.s", les champs de jeu défilent horizontalement en exploitant les possibilités du matériel via le registre BPLCON1. Or, cet effet repose sur une lecture anticipée des données des plans de bits (16 pixels avant le début effectif de leur affichage), ce qui vient ici encore voler des cycles, mais cette fois en interdisant de lire via DMA les données du sprite 7 : De ce fait, le couple de sprites 6 et 7 ne peut être utilisé. L'AGA : des sprites un peu plus mieux Alors que les codeurs avaient particulièrement apprécié la qualité du Amiga Hardware Reference Manual documentant l'Original Chip Set (OCS) dans ses moindres détails, Commodore fit le choix de ne pas documenter l'Advanced Graphics Architecture (alias "AGA", ou "AA" pour son appellation d'origine). Cette politique visait à assurer la compatibilité des logiciels dans la perspective d'une révolution du jeu de puces qui, comme on le sait, ne survint jamais. La décision fut conspuée par les codeurs. Dans leur présentation de l'Amiga 1200 publiée dans Grapevine #14, Ringo Star/Classic et Animal & Goofy/Silents ne firent qu'exprimer le sentiment général... non sans faire preuve de la toujours distrayante vantardise des membres s'estimant en vue de la scène ! Or, ce numéro de Grapevine contient un autre article, moins plastronnant et plus constructif, publié par votre serviteur et son acolyte Junkie/PMC (ainsi qu'un certain Spencer Shanson dont le rôle dans cette affaire m'échappe désormais...). L'excellent Junkie/PMC ayant eu l'idée de désassembler la liste Copper du Workbench de l'Amiga 1200, nous avions passé un bon moment à faire la rétro-ingénierie du matériel en testant bit à bit ses registres. Tout cela couché sur le papier, il en avait résulté une documentation officieuse expliquant comment tirer parti des principales fonctionnalités de l'AGA par accès direct au matériel comme on l'aime. Cette documentation fut ensuite complétée et corrigée par de gentils contributeurs, notamment Randy/Comax dont sa version reste visiblement à ce jour la plus aboutie. En ce qui concerne les sprites, les capacités de l'AGA surpassent sans conteste celles de l'OCS, mais c'est plus d'une évolution que d'une révolution dont il s'agit. En effet, il n'est toujours possible d'afficher que huit sprites en quatre couleurs (palettes différentes pour les sprites pairs et impairs), ou quatre sprites en 16 couleurs (même palette pour tous les sprites). Toutefois :
![]() Des sprites un peu plus mieux en AGA Étant pris en charge par le matériel, les sprites présentent plusieurs avantages pour le codeur. En effet, ce dernier n'a pas à gérer la préservation et la restauration du décor sur lequel ils sont affichés (le recover), pas plus que le découpage pouvant aller jusqu'à l'élimination quand ils débordent ou sortent du champ de jeu (le "clipping"). Toutefois, les sprites sont comme les hobbits : pratiques et colorés, mais petits et peu nombreux. C'est pourquoi les codeurs utilisent aussi, voire alternativement, des BOB. L'acronyme correspond à "Blitter OBject", c'est-à-dire des bitmaps dessinés au Blitter. Parce que les BOB sont si souvent utilisés, le second article de cette série consacrée à l'affichage de bitmaps sur Amiga en exposera les détails.
|