|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Cet article est le second - et donc dernier - d'une série de deux consacrée à l'affichage de bitmaps sur Amiga. Dans le premier article, nous avons exploré dans le détail les sprites du matériel. A cette occasion, il est apparu que les sprites sont comme les hobbits : pratiques et colorés, mais petits et peu nombreux. Et même si le matériel permet d'en démultiplier apparemment le nombre en les découpant ou en les répétant, recourir à ces astuces reste assez contraignant. C'est pourquoi une autre solution a souvent été préférée pour afficher des bitmaps : les BOB. Le BOB est un bitmap affiché à l'aide du Blitter, le coprocesseur dont la fonctionnalité la plus notoire est la copie des données d'une ou plusieurs zones de mémoire vers une autre. ![]() Des vector balls (avec quelques droites pour mieux constater la 3D)
Décaler, masquer et combiner au Blitter Étant pris en charge par le matériel, les sprites présentent de nombreux avantages pour le codeur. En effet, ce dernier n'a pas à gérer la transparence (le masquage) ; la préservation et la restauration du décor sur lequel ils sont affichés (le recover) ; le découpage pouvant aller jusqu'à l'élimination quand ils débordent ou sortent du champ de jeu (le clipping) ; les priorités entre sprites et entre sprites et plans de bits ; la gestion des collisions entre sprites, et entre sprites et plans de bits. Toutefois, les sprites sont comme les hobbits : pratiques et colorés, mais petits et peu nombreux. Les BOB ne souffrent pas de telles limites. En effet, un BOB étant un bitmap qui sera affiché parce qu'il est dessiné dans les plans de bits, le codeur dispose de toute latitude pour adapter la taille et la profondeur du BOB. Toutefois, si le matériel simplifie le dessin du BOB, il n'offre aucune facilité pour gérer toutes les tâches évoquées à l'instant en présentant les sprites : masquage, restauration, découpage, priorités, collisions. L'affichage du BOB s'effectue au Blitter. Comme expliqué dans un précédent article, ce coprocesseur permet notamment de combiner plusieurs blocs (les sources A, B et C) par des opérations logiques et d'écrire le résultat dans un autre bloc (la destination D), en décalant de 0 à 15 bits A et B sur la droite, et en masquant le premier et le dernier mot de A. Rappelons qu'un bloc est ni plus ni moins qu'une séquence de mots en mémoire, dont il est possible d'ignorer périodiquement un certain nombre de mots. Le Blitter propose de décrire un bloc à l'aide d'une adresse de départ, d'une largeur (en mots), d'un modulo, et d'une hauteur (en lignes de mots), comme s'il s'agissait d'un rectangle. Par exemple, un bloc de 28 mots, dont deux mots sont ignorés tous les cinq mots, est représenté comme un bloc de cinq mots de large sur quatre lignes de hauteur, avec un modulo de deux mots (la flèche parcourt les mots aux adresses consécutives) : Le Blitter offre de nombreuses possibilités pour combiner A, B et C. Dans un premier temps, les bits sont combinés par AND, après avoir été éventuellement inversés. L'ensemble des combinaisons possibles, désignées comme les minterms, sont notées à l'aide des lettres A, B et C (sources non inversées) ou a, b et C (sources inversées). Dans un second temps, les minterms choisis par le codeur sont combinés entre eux par OR : Tout cela permet de cerner l'intérêt du Blitter pour afficher l'équivalent d'un sprite. Pour cela il faut combiner trois sources :
Un exemple avec une charmante petite fée de 16x16 pixels, dessinée par votre serviteur à l'aide de l'excellent Pro Motion NG : ![]() Les étapes de l'affichage d'un BOB Bien évidemment, cette nécessité a été anticipée par les concepteurs du matériel. Le Blitter permet de décaler A et B sur la droite, d'un nombre de bits compris entre 0 et 15. Le décalage de A est indépendant de celui de B. Lorsque les lignes d'un bloc sont ainsi décalées, les bits chassés sur la droite de la ligne Y sont réintroduits sur la gauche de la ligne Y+1. La ligne 0 est un cas particulier : comme il n'y a pas de ligne précédente, ce sont des 0 qui sont introduits sur sa gauche : Comme il est possible de la constater à l'aide de l'exemple d'un décalage de quatre pixels d'un bloc d'un mot de large représenté sur la figure, des bits de poids faible du dernier mot de la dernière ligne se trouvent alors chassés sur la droite et éliminés. En fait, si la largeur du bloc demeure celle du bitmap à afficher, ce bitmap subit une déformation inacceptable. Même exemple de la fée, quand elle subit un décalage de quatre pixels (pour la lisibilité, les pixels de couleur 0, transparente, introduits à gauche de la première ligne sont affichés en rouge) : ![]() Déformation d'un bloc conservant sa largeur à l'occasion d'un décalage Le cas particulier de A et le cas général de B A ce stade, il faut préciser que le Blitter peut masquer le premier et le dernier mot de chaque ligne de A. En exploitant cette possibilité, n'est-il pas possible de se dispenser de rajouter une colonne de mots sur la droite du bitmap, et même un masque, dans un cas particulier mais néanmoins fréquent : quand le bitmap est affiché sur un décor où ses pixels opaques ne viennent remplacer que des pixels de couleur 0 ? De fait, mais pour que cela fonctionne, il ne suffit pas de préciser ces masques. Il faut aussi jouer sur le modulo de A. Modulo ? Quésaco ? Pour comprendre comment de quoi il en retourne, il est nécessaire de rentrer dans le détail du déroulement d'une copie au Blitter. Comme illustré plus tôt, Le Blitter combine les mots des sources A, B et C et copie le résultat dans un mot de la destination D. Il procède mot à mot, incrémentant les adresses contenues dans les registres BLTxPTH et BLTxPTL, par exemple BLTAPTH et BLTAPTL pour A. De plus, à la fin de chaque ligne, le Blitter rajoute à l'adresse de A, B, C et D la valeur contenue dans BLTxMOD, par exemple BLTAMOD. Ainsi, s'il devait s'agir de A, le bloc de la figure devrait être spécifié ainsi avant une copie qui l'implique :
Rappelons que c'est au moment de la copie que le Blitter est informé de la largeur et de la hauteur des blocs impliqués. En effet, c'est en écrivant une combinaison ces valeurs dans BLTSIZE qu'on les spécifie, en même temps qu'on déclenche la copie (dont il faut attendre le terme en testant deux fois un bit de DMACONR, ce que fait la macro WAIT_BLITTER mentionnée ici) :
L'astuce consiste à utiliser un modulo... négatif, de -2 pour être plus précis. Ainsi, au terme de la copie d'une ligne Y de A, le Blitter va commencer la ligne Y+1 non pas au premier mot de cette dernière, mais au dernier mot de la ligne Y. La figure qui suit montre ce qui se passe dans les deux cas s'il s'agissait de copier un bloc de deux mots de large sur trois lignes : avec ou sans colonne de mots supplémentaire :
Dans la continuité de la figure précédente, la figure suivante décrit ce qui se déroule lors de la copie de la deuxième ligne. Attention, il faut suivre ! Cette copie implique deux mots : le mot constituant la deuxième ligne (en vert) suivi du mot constituant la troisième (en bleu). A la fin de cette copie, tous les bits du mot du second mot, ont été effacés suite au AND avec BLTALWM. Par ailleurs, ses quatre derniers bits ainsi effacés se trouvent prêts à être injectés sur la gauche du premier mot de la troisième ligne (en bleu) lors de sa copie à venir. C'était déjà ce qui s'était passé lors de la copie de la première ligne (en rouge), et c'est pourquoi les bits à 0 injectés lors de la copie de la deuxième ligne (en vert) figurent en vert sombre : ce sont les quatre derniers bits du mot constituant la deuxième ligne (en vert) qui avaient été effacés suite au AND avec BLTALWM. Au final, le code pour afficher un BOB de BOB_DX x BOB_DY pixels, composé d'un seul plan de bits (à l'adresse bob), sur un décor de DISPLAY_DX x DISPLAY_DY pixels composé d'un seul plan de bits (à l'adresse backBuffer), est le suivant :
Le rôle des autres registres utilisé dans ce code sera expliqué plus loin. La solution qui vient d'être présentée est pratique car elle permet de ne pas avoir à introduire de colonne de mots supplémentaire, mais elle ne fonctionne que pour A. En effet, le Blitter ne sait masquer que le premier et le dernier mot d'une ligne de A, qui correspondrait au bitmap. Rien pour B, qui correspondrait au masque. Il n'en reste pas moins important de savoir que cette possibilité existe, car elle permet d'afficher un bitmap sans avoir à rajouter une colonne de mots supplémentaire, et sans avoir à fournir son masque :
Comme déjà expliqué, le masque doit être décalé sur la droite, ce qui implique que des bits doivent être injectés sur la gauche et d'autres rejetés sur la droite de chacune de ses lignes, lignes dont la longueur doit être agrandie d'un mot sur la droite. Or, comment positionner les bits superflus à gauche et à droite avant de combiner par AND le masque avec le décor, afin de préserver les bits du décor correspondant ? Impossible d'appliquer des masques BLTBFWM et BLTBLWM, car ils n'existent pas. Par conséquent, il faut que ces bits soient déjà présents dans le masque, donc qu'ils figurent dans une colonne de mots supplémentaire sur sa droite, dont les bits seraient à 1. A 1 ? Très logiquement, on souhaiterait que le masque soit combiné par AND avec le décor, ce qui implique que les bits du masque devaient être à 1 là où le décor devrait être préservé, et à 0 là où le décor devrait être effacé. Or, nous avons vu que le Blitter introduit des bits à gauche de la première ligne du masque pour la décaler, et que ces bits sont nécessairement à 0. Cela signifie qu'un bit à 0 dans le masque doit correspondre à un bit qu'il faut préserver dans le décor, tandis qu'un bit à 1 dans le masque doit correspondre à un bit qu'il faut y effacer. C'est pourquoi c'est b, l'inverse du masque, et non B, le masque, qu'il faut combiner par AND au décor. Fort heureusement, le Blitter permet de combiner indifféremment B ou son inverse, b (pour NOT B), avec C, qui correspond au décor. En conséquence, les bits de la colonne de mots supplémentaires sur la droite du masque doivent être à 0 et non à 1. Pour récapituler :
Pour comprendre totalement le code d'affichage d'un BOB qui a été présenté (cas spécifique du BOB dont les pixels opaques sont affichés sur des pixels de couleur 0), et comprendre le code qui va suivre (cas général du BOB dont les pixels opaques sont affichés sur des pixels de couleurs quelconques), il reste à comprendre comment ils permettent de spécifier au Blitter la manière dont les sources A, B et C doivent être combinées pour produire la destination D. Comme expliqué, le Blitter combine les sources en combinant par OR des minterms, qui sont eux même le produit de combinaisons par AND des sources, éventuellement inversées. Or que s'agit-il de faire, sinon la combinaison suivante : D=A+bC. L'Amiga Hardware Reference Manual explique comment en déduire les minterms qu'il faut activer en positionnant les bits correspondants dans BLTCON0. Il suffit de rajouter des facteurs neutres aux AND (par exemple, c+C qui vaut nécessairement 1) et de développer puis réduire :
Les huit bits de BLTCON0 réservés au minterms sont ceux de son octet de poids faible :
En l'espèce, cela signifie que l'octet doit prendre la valeur $F2. Le reste de la configuration du Blitter consiste simplement à spécifier qu'il doit activer A, B, C et D, et à spécifier la valeur du décalage sur la droite de A et celle de celui de B. D'autres bits de BLTCON0 et de BLTCON1 servent à ces usages - se reporter à l'Amiga Hardware Reference Manual, qu'il ne s'agit pas de réécrire ici, pour les identifier. Maintenant que tout a été présenté, il est possible d'écrire le code d'affichage d'un BOB masqué, dont "bob" est l'adresse du "bitmap" et "bobMask" celle de son masque : moveq #0,d1 move.w #BOB_X,d0 subi.w #BOB_DX>>1,d0 move.w d0,d1 and.w #$F,d0 ror.w #4,d0 move.w d0,BLTCON1(a5) or.w #$0FF2,d0 move.w d0,BLTCON0(a5) lsr.w #3,d1 and.b #$FE,d1 move.w #BOB_Y,d0 subi.w #BOB_DY>>1,d0 mulu #DISPLAY_DEPTH*(DISPLAY_DX>>3),d0 add.l d1,d0 move.l backBuffer,d1 add.l d1,d0 move.w #$FFFF,BLTAFWM(a5) move.w #$0000,BLTALWM(a5) move.w #-2,BLTAMOD(a5) move.w #0,BLTBMOD(a5) move.w #(DISPLAY_DX-(BOB_DX+16))>>3,BLTCMOD(a5) move.w #(DISPLAY_DX-(BOB_DX+16))>>3,BLTDMOD(a5) move.l #bob,BLTAPTH(a5) move.l #bobMask,BLTBPTH(a5) move.l d0,BLTCPTH(a5) move.l d0,BLTDPTH(a5) move.w #(DISPLAY_DEPTH*(BOB_DY<<6))!((BOB_DX+16)>>4),BLTSIZE(a5) Le lecteur attentif doit encore se gratter la tête. Quelles sont ces valeurs stockées dans les registres BLTCMOD et BLTDMOD ? Une constante vient de faire son apparition : DISPLAY_DEPTH. Le code permet d'afficher un BOB de DISPLAY_DEPTH plans de bits de profondeur sur un décor de même profondeur. Or, cela soulève un enjeu intéressant. En effet, si les données du bitmap, du masque et du décor sont organisées comme de coutume, celles du plan de bits 1 sont suivies de celles du plan de bits 2, et ainsi de suite. Cela conduit à afficher les plans de bits du bitmap et du masque un par un dans une boucle, car le modulo utilisé pour passer d'une ligne à l'autre dans D, valant donc (DISPLAY_DX-(BOB_DX+16))>>3 ne peut servir à passer de la dernière ligne utilisée d'un plan de bits à la première ligne utilisée dans le suivant :
Cette manière de procéder contraint d'attendre le Blitter après chaque copie dans un plan de bits avant de passer au plan de bits suivant. Cela ne permet pas de tirer parti du fonctionnement en parallèle du Blitter et du processeur, sauf à l'occasion de la dernière copie - il faudrait alors ne pas appeler la macro WAIT_BLITTER, ce qui contraindrait donc à distinguer les premières boucles de la dernière, pour en rajouter. N'est-il pas possible de copier en un coup les plans de bits du bitmap et du masque dans ceux des plans de bits ? Tout à fait :
Pour conclure (car c'est enfin terminé !), les programmes "bobRAW.s" et "bobRAWB.s" explorent chacune des solutions pour afficher un BOB de 64x64 pixels sur cinq plans de bits : ![]() Affichage d'un BOB de 64x64 pixels sur cinq plans de bits Le programme "bobRAWB.s" pourrait être modifié pour réaliser des opérations au processeur tandis que le BOB est affiché dans les cinq plans de bits. Le programme "bobRAW.s", pourrait être modifié pareillement, mais pour permettre de réaliser ces opérations au processeur tandis que le BOB est affiché dans le dernier plan de bits seulement. La différence pourrait alors être constatée. Pour cette raison, si le recours au RAW peut s'imposer dans des cas particuliers, c'est généralement c'est le RAWB qu'il convient d'utiliser quand il s'agit d'afficher des BOB. "Unlimited BOBs" : des BOB à foison, vraiment ? Les BOB sont utilisés pour produire des effets très variés, dont les plus notoires sont les "unlimited BOBs", et les "vector balls". "The Amiga possibilities have been burst beyond your imagination. And the result is what you see !!!", proclame fièrement l'auteur d'un défilement de la fameuse Megademo de Dragons. Excessivement vintage, cette démo contient un effet très classique à l'époque, dit "unlimited BOBs". Un BOB est visiblement rajouté à chaque trame, et le compteur semble indiquer que cela ne s'arrêtera jamais. Les performances ne sont aucunement pénalisées par cette multiplication des BOB : ![]() 2000 BOB dans la trame, et ce n'est pas fini ! (Megademo par Dragons) ![]() BOB, votre pote rondouillard de 16x16 pixels sur deux plans de bits
Puisqu'il ne s'agit jamais que d'afficher un nouveau BOB à chaque trame, l'effet ne consomme presque pas de temps de calcul. C'est donc extrêmement simple, mais bien pensé. Quant à la trajectoire, elle peut être quelconque. Dans le programme "unlimitedBobs.s", la position du BOB est calculée sur un cercle dont le rayon oscille entre un minimum et un maximum, ce qui permet de rompre la monotonie : ![]() Des BOB tentaculaires Plus sophistiqué : les vector balls. Une démo du groupe Impact en fournit une belle illustration. Comme il est possible de le constater, il s'agit d'afficher des BOB figurant des sphères à certaines positions d'un modèle en 3D : ![]() De très jolies vector balls (Vectorballs par Impact) ![]() Nos vector balls. Si, si ! C'est en 3D !
Cet effet peut être sophistiqué en utilisant des BOB représentant des sphères de couleurs différentes et/ou dont le diamètre est variable selon la profondeur, et en animant des parties du modèle indépendamment les unes des autres. Pour en finir avec les BOB... Ces explications sur la manière d'afficher un BOB et d'utiliser le code pour produire deux effets, les "unlimited BOBs" et les "vector balls", étant données, tout semble avoir été dit au fil des deux articles de cette petite série consacrée à l'affichage de bitmaps animés sur Amiga OCS et AGA. Il ne faut pas perdre de vue que ce ne sont là que des techniques de base, dont l'emploi nécessite un travail de sophistication et d'optimisation pour produire des effets réussis. Or, entre autres, il serait possible d'imaginer une troisième solution : afficher des bitmaps animés au processeur, par exemple pendant que le Blitter affiche un BOB et que le matériel affiche les sprites. On trouve certainement de nombreux exemples d'un tel mélange de techniques dans les jeux et les démos sur Amiga. Un ordinateur dont le matériel reste décidémment des plus fascinants !
|