|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Plan des articles Voici les articles déjà parus de notre série sur la création d'une démo Amiga :
Ma seule constance se situe dans mon incapacité à respecter les délais, à promettre sans tenir, ce cher David Brunet ne le sait que trop. Il n'aurait probablement que faire de mes plates excuses, et pour cause, à ses yeux, ma crédibilité s'en trouve entamée. J'en fais certainement un peu trop, pourtant le constat se fait évident, simple, je suis en retard. Je n'ai jamais éprouvé d'attirance particulière pour les jeux de course auto, excepté peut-être Vroom. A l'époque, on me l'avait présenté comme un jeu doté d'une sensation de vitesse jusqu'alors jamais vue sur Amiga. On ne m'avait pas menti, ce jeu a un sacré punch, agrémenté de détails intéressants : l'horizon descend et monte en fonction d'une brusque accélération ou freinage, la vue tremble si on touche une bande rugueuse, simulation de chocs non élastiques contre les concurrents, prise en compte de la gravité selon la pente de la route, rétroviseurs, contrôle par à-coups dans les virages, saut de trames. Tout ça m'avait épaté mais ce n'est pas pour autant que j'y ai joué, car mon coeur battait presque exclusivement pour les jeux d'arcade 2D. C'est sans compter la découverte récente d'Indianapolis 500. Ce fut ce point particulier, objet de mon questionnement, si on me l'avait présenté durant mon adolescence, y aurais-je joué ? Probablement jusqu'à épuisement. Il n'est pas trop tard pour bien faire. Je m'égare sur les sentiers de la digression, il est temps de recentrer le débat, de partir à pas comptés, pourtant déterminés, vers un tour d'horizon du Blitter de l'Amiga. Présentation Le Blitter est un composant de l'Amiga qui sert essentiellement à copier des blocs mémoire. Mémoire graphique dans l'écrasante majorité des cas. Ce que son acronyme semble confirmer, Blitter pour "Block Image Transfert". Il est rapide, très rapide sur un Amiga 500 sans accélération. Sur des Amiga dont le microprocesseur est gonflé aux amphétamines, le Blitter commence à devenir mou du genou. C'est un composant relativement complexe car il peut aussi tracer des lignes, remplir des polygones ou même décoder des données disquette. Nous allons, toujours dans le strict cadre de la programmation de notre démo, nous focaliser sur le transfert de blocs mémoire. Le Blitter possède quatre canaux DMA. Trois réservés par les adresses sources et un pour l'adresse destination. Il peut combiner ces sources appelées A, B, C qui seront copiées vers la destination D. Comme pour presque tous les composants spécialisés de l'Amiga, son champ d'action reste cantonné à la mémoire Chip, à savoir [$0000-$7FFFF], ceci concerne un Amiga 500 de base. Le principe de la copie La copie de blocs ne s'effectue pas à la manière d'une instruction BASIC évoluée, quelque chose qui pourrait ressembler à :
Le Blitter ne travaille pas avec des coordonnées écran mais avec des mots et des adresses mémoire. Si on voulait faire court pour décrire une opération de copie Blitter, voici ce que cela donnerait : on renseigne le ou les adresses sources, puis l'adresse de destination et enfin les dimensions en mots et hauteur du rectangle à copier. Le Blitter démarre son opération et le 68000, quant à lui, peut continuer à travailler en parallèle (ou presque). C'est bien évidemment loin d'être aussi simple mais cela donne tout de même une idée générale du processus de copie de blocs de mémoire. ![]() Fig. 1 - copie d'un bloc mémoire vers une autre zone
Ici, rien de bien compliqué, il suffit de tester le bit n°14 du registre DMACONR ($DFF002). Tant que ce bit est à 1 (vrai), le Blitter est en cours de travail, on peut commencer à lui demander une autre tâche dès qu'il vaudra 0. 2. BLTCON0 et 1 C'est dans BLTCON1 ($DFF042) que l'on indique au Blitter qu'il se positionne en mode copie. Plus exactement, le bit 0 doit être positionné à faux (=0). 3. Adresses des blocs Généralement, lors d'une opération de copie, l'adresse source se situe au premier mot de notre BOB. Alors que celle de D (destination) se situe quelque part dans le plan de bits (cela dépend de la position du coin supérieur gauche du BOB à l'écran). A noter que le Blitter est un coprocesseur 16 bits, il copie des mots, cela implique que toutes les adresses renseignées devront être paires. Les adresses sont représentées sur 19 bits (sur Amiga OCS du moins), pour évoluer à 21 bits sur les cartes mères rev. 6A et 8 de l'Amiga 500. Cela permet potentiellement d'adresser respectivement 512 ko et 2 Mo de mémoire vidéo. Nous verrons plus en détail dans l'article prochain, la manière de déterminer une adresse mémoire en fonction des coordonnées d'un BOB à afficher à l'écran. Registres adresses mémoire sources et destination : $DFF048 = BLTCPTH (Mot) = adresse pointant vers la source C (partie haute de l'adresse) $DFF04A = BLTCPTL (Mot) = adresse pointant vers la source C (partie basse de l'adresse) $DFF04C = BLTBPTH (Mot) = adresse pointant vers la source B (partie haute de l'adresse) $DFF04E = BLTBPTL (Mot) = adresse pointant vers la source B (partie basse de l'adresse) $DFF050 = BLTAPTH (Mot) = adresse pointant vers la source A (partie haute de l'adresse) $DFF052 = BLTAPTL (Mot) = adresse pointant vers la source A (partie basse de l'adresse) $DFF054 = BLTDPTH (Mot) = adresse pointant vers la destination D C (partie haute de l'adresse). $DFF056 = BLTDPTL (Mot) = adresse pointant vers la destination D (partie basse de l'adresse). 4. Taille du bloc de copie Elle sera renseignée dans le registre BLTSIZE ($DFF058) de la manière suivante :
ou encore...
Cette taille s'applique à A, B, C et D. Toutes les sources et destination ont donc la même taille. 5. Initialisations diverses mais essentielles Modulos Dans les registres suivants sont contenues les valeurs modulos. $DFF060 BLTCMOD : modulo de la source C. $DFF062 BLTBMOD : modulo de la source B. $DFF064 BLTAMOD : modulo de la source A. $DFF066 BLTDMOD : modulo de la source D. Si vous voulez afficher un BOB plus petit que l'écran horizontalement, il faut après chaque ligne du BOB affichée que le Blitter effectue un saut d'un certain nombre d'octets. Cette valeur se nomme modulo. En effet, si on n'avait pas cette fonctionnalité ajoutée, nous aurions chaque ligne du BOB affichée l'une à la suite de l'autre, ce qui nous donnerait un résultat corrompu. Si nous observons la figure 2 (admirez au passage la qualité du graphisme, ces couleurs douces et harmonieuses), la valeur modulo correspond à la longueur totale (en octets et non en pixels) des deux lignes vertes. Elle s'obtient ainsi :
Le résultat obtenu est à renseigner dans BLTDMOD. A noter qu'il faut prendre en compte la largeur du plan de bits et non celle de l'écran visible. Remarque : nous donnons ici le modulo d'un BOB qui a pour but d'être affiché sur un écran. Rien ne vous empêche de copier ce BOB ailleurs que dans un plan de bits. Mais la règle reste la même, connaître la taille horizontale de la source et de la cible. Le cas particulier d'un BOB aussi large que le plan de bits dans lequel il devra être copié nous donne la valeur modulo 0 (toujours en ce qui concerne D). Je le répète encore une fois, le Blitter copie par mot (donc 16 pixels à la fois), les valeurs modulos seront donc toujours paires, en octets, mais paires. Nous avons parlé du modulo de D, mais évoquons ceux de A, B ou C. Les sources aussi ont le leur. La manière de le calculer dépend de la façon dont vous avez ordonné vos BOB en mémoire. Par exemple, la figure 3 nous montre une planche de BOB du jeu Chaos Engine. Cette planche est utilisée par le Blitter pour y piocher un BOB destiné à être copié à l'écran. Le programmeur a choisi d'ordonner ces sources en huit colonnes de BOB par ligne. Pour trouver le modulo correspondant à cette configuration, il faudra soustraire la largeur du BOB (32 pixels = 4 octets) à celle de la planche totale. Taille bob = 4 octets. Taille de la planche = 4*8 = 32 octets. Le calcul du modulo de la source est donc : 32-4=28. Valeur à injecter dans BLTAMOD, BLTBMOD ou BLTCMOD selon la/les source(s) choisie(s). ![]() Fig.2 - modulos destination D ![]() Fig.3 - planche de BOB (sources) © Bitmap Brothers Les blocs copiés ont des tailles horizontales multiples de 16 pixels. Cela pose un problème lorsque le BOB à afficher ne rentre pas dans ce gabarit. Si par exemple notre BOB faisait 36 pixels de large, il y aurait des résidus dans la première et/ou dernière colonne du bloc. Pour palier à ce problème, on a la possibilité de masquer le premier et le dernier mot d'une ligne du BOB. Les masques ont une largeur de 16 bits chacun et c'est la raison d'être des registres suivants : BLTAFWM ($DFF044) = masque (mot) du premier mot d'une ligne BLTALWM ($DFF046) = masque (mot) du dernier mot d'une ligne Un "ET" est appliqué entre respectivement les valeurs de ces deux registres et les premier et dernier mot d'une ligne du bloc à copier, ceci pour chaque ligne traitée. On en conclut que l'affichage du BOB s'en trouvera tronquée selon la valeur des bits de BLTAFWM/BLTALWM. Un bit=1 rendra un bit inchangé de la source, tandis qu'une valeur de 0 donnera systématiquement une valeur nulle pour la source. C'est donc un bon moyen d'éliminer des bordures verticales des résidus non désirés d'un BOB. Il est à noter que ces deux registres "Masque" ne s'appliquent qu'à la source A. Cas particulier : pour un BOB ayant une largeur d'un mot (16 pixels). Les valeurs des deux registres masques sont combinées grâce à un "ET". Le masque appliqué aura une valeur correspondant aux bits allumés aux mêmes positions dans BLTAFWM et BLTALWM. Exemple de masquage pour un BOB d'un mot de largeur BLTAFWM = %1001010111110111 BLTALWM = %0100111111000001 Résultat du masque à appliquer à A = %0000010111000001 Décalage On ne peut afficher un BOB qu'à des abscisses multiples de 16. Et cela pour la même raison invoquée que précédemment. C'est pour cette raison que la possibilité de décaler les mots traités existe, cela permet selon le décalage appliqué de positionner horizontalement un BOB au pixel près. L'amplitude du décalage peut varier de 0 à 15 bits et est toujours orienté vers la droite. Elle ne concerne que les sources A et B (C en est exclu). Les bits ASH0, ASH1, ASH2, ASH3 de BLTCON0 et BSH0, BSH1, BSH2, BSH3 de BLTCON1 sont des compteurs quatre bits qui permettent des valeurs de 0 à 15 correspondant au décalage voulu pour respectivement A et B. Avant de continuer, il me paraît important de préciser que les masques (BLTAxWM) de A sont appliqués avant tout décalage. Comment fonctionnent les décalages ? D'une manière basique, un peu à la manière de l'instruction assembleur du 68000 : ROXR Ax. Cette instruction qui utilise le contenu du drapeau X (une retenue qui est une copie du drapeau C), effectue un décalage d'un bit du mot pointé par le registre Ax vers la droite. Le contenu de l'indicateur X (0 ou 1) rentrera par la gauche et tout bit (qu'il soit activé ou non) qui sort par la droite sera transféré dans le registre X. Ainsi de suite. Ce principe appliqué au Blitter forme une chaîne de décalages cohérente car tous les bits qui sortiront par la droite du mot précédemment traité rentreront dans le mot par la gauche du mot en cours de traitement et tous les bits du mot en cours qui sortiront par la droite seront gardés pour rentrer par la gauche du prochain mot traité. En ce qui concerne le tout premier mot copié par le Blitter, les bits qui rentreront par la gauche du tout premier mot seront nuls, cela semble cohérent car il n'y aura eu aucun transfert au préalable. La figure 4 illustre ce que nous venons de voir. Elle représente un BOB d'un mot de largeur. Les quatre bits ASHx (et éventuellement BSHx) valent %0011=$0003=3, il y a donc un décalage de trois bits à droite de A et/ou B. Cela donne un résultat en D décalé lui aussi de trois bits. ![]() Fig.4 - décalage des bits d'un BOB Il suffit (et nous le verrons dans le prochain article plus en détail) de rajouter un mot à la taille horizontale du BOB et de mettre la valeur $0000 dans le registre BLTALWM. De cette manière les trois derniers bits du dernier mot copié de chaque ligne sera vide, tous ces bits nuls (grâce au masque BLTALWM) rentrerons dans le premier mot de la prochaine ligne par la gauche et n'affecterons pas le rendu visuel du BOB. ![]() Fig. 5 - rajout d'un mot supplémentaire pour obtenir un décalage sans bogue d'affichage Lorsque les blocs sources et destination d'une copie se superposent, il peut arriver dans certains cas que la copie soit corrompue. La raison est simple, le Blitter ne copie pas l'intégralité du bloc en une seule fois, il effectue une copie progressive, mot par mot, en incrémentant les adresses de la source et de la destination. Si l'on se réfère à la figure 6, on s'aperçoit que les blocs A et D se chevauchent. En mode incrémentation (ascending), les adresses sont incrémentées en commençant par les premiers mots des coins supérieurs gauche. Le problème est qu'en copiant les premiers mots vers D, on écrase la partie inférieure de A, précisément à l'endroit où les deux blocs se superposent. Lorsque la copie arrivera à cette zone, les données de A auront déjà été remplacées. Pour y remédier, le Blitter permet d'activer un mode décrémentation (descending), la copie au lieu de débuter par le coin supérieur gauche, débutera au coin inférieur droit. Les adresses renseignées dans BLTxPTH/BLTxPTL par contre devront pointer sur le dernier mot de chaque bloc. Et comme son nom l'indique, les adresses sources et destination seront décrémentées de deux octets à chaque cycle de copie. ![]() Fig. 6 - copie qui génère des erreurs La copie de données d'une source vers une destination n'est pas un long fleuve tranquille, rectiligne. Avant d'arriver à destination, un pixel (ou bit) subit une série de traitements. Trois traitements simples pour être exact.
Pour appréhender ces étapes, et je parle de la troisième, il faut comprendre cette notion de minterms. Elles sont un ensemble de combinaisons booléennes des trois sources qui sont choisies par le programmeur. Minterms : bits LFx contenu dans DMACON0 ($DFF040)
Notez que le nombre de combinaisons est 2^8=256. Le programmeur choisira cette combinaison de huit minterms en fonction des résultats désirés. Une fois choisis, ces minterms déclencheront l'envoi d'un bit activé ou désactivé vers D selon que le résultat de l'opération booléenne soit vrai ou faux. ![]() Fig. 7 - BLTCON0 et BLTCON1 © La Bible de l'Amiga, MicroApplication Soit un BOB pointé par A. B et D pointent, quant à eux, le même bloc à l'écran. Si nous désirons incruster notre BOB à l'écran sans que les pixels vides de A n'écrasent le contenu de l'écran (éviter le rectangle noir autour d'un BOB, et par extension, que tous les pixels noirs du BOB ne viennent écraser le contenu de D) nous devrons effectuer un "OU" entre A et B (d'où l'utilité de prendre B une copie de D pour pouvoir agir sur le décor avant la copie). Un "OU" entre A et B se note A+B. Un "ET" se note par AB. Ce qui nous intéresse dans notre cas est le A+B. La négation de A (NON A) est noté par sa lettre minuscule : a. Il s'agit d'une inversion de bit. Revenons à notre problème. Nous désirons donc faire un "OU" entre A et B. Nous partons de cette équation simple : A+B = D. Développons l'équation : <=> A.1.1+B.1.1 = D <=> A(b+B)(c+C) + B(a+A)(c+C) = D <=> Abc+AbC+ABc+ABC+Bac+BaC+BAc+BAC = D <=> Abc+AbC+ABc+ABC+aBc+aBC+ABc+ABC = D <=>Abc+AbC+ABc+ABC+aBc+aBC+ABc = D <=>Abc+AbC+ABc+ABC+aBc+aBC = D Si l'on se réfère aux LFx dans BLTCON0, nous avons donc : Abc = 16 AbC = 32 ABc = 64 ABC = 128 aBc = 4 aBC = 8 Nous allons donc injecter la valeur 16+32+64+128+4+8=252=$FC dans l'octet de poids faible (bits LFx [0-7]) de BLTCON0. Autre manière d'obtenir cette valeur : Il s'agit d'une méthode moins rigoureuse, plus intuitive. Référons-nous au tableau des minterms. Nous avons besoin d'identifier dans quels cas le bit en D doit être activé. Quels sont-ils ? Il faut qu'il y ait au moins A ou B qui soit activé, donc recherchons dans la liste des LFx les cas où A ou B sont activés. Le "OU" est à prendre au sens d'un "OU" logique booléen, donc non exclusif (A et B peuvent aussi être activés tous les deux en même temps). Il suffit donc de répertorier les cas où l'on retrouve A ou B en majuscule : Pour A nous avons : S'agissant d'un "OU" logique, le seul facteur d'exclusion provient lorsque le bit de A n'est pas activé, les autres conviennent donc, à savoir : Abc, AbC, ABc, ABC. Pour B, c'est du pareil au même : aBc, aBC, ABc, ABC. Éliminons les doublons, on se retrouve avec : Abc, AbC, ABc, ABC, aBc, aBC. ![]() Fig. 8 - combinaison $FC de minterms Remarque : même si le DMA de la source C est désactivé, il sera tout de même traité dans l'opération de filtrage. Sa valeur sera considérée comme nulle cependant prise en compte. Je vous conseille d'utiliser la première méthode de détermination des minterms, vous risquez de faire moins d'erreur. A l'esprit me vient un exemple peu ordinaire d'utilisation des minterms : en 1989, le logiciel Deluxe Paint 3 a introduit une option pour un effet de dessin passé inaperçu mais qui, contre toute attente, a été repris par les créateurs de démos. Il s'agit du mode Coloration (Shade). Cet effet est obtenu en utilisant les minterms du Blitter. Ceci dit, le 68000 est tout à fait capable de faire la même chose (en plus lent sur un Amiga 500 non accéléré). L'astuce réside dans l'idée d'ajouter un BOB progressivement qui s'affiche de telle manière qu'il altère les données de l'écran pour simuler une addition entre les bits du BOB et ceux des plans. Cela passe par deux copies par plan. La palette se trouve donc incrémentée à l'endroit même où le BOB a été incrusté. Les minterms sont calculés pour simuler une addition booléenne ("OU EXCLUSIF" pour l'addition et "OU LOGIQUE + NON" pour la retenue car une addition peut engendrer un bit de retenue). ![]() Fig. 9 - utilisation du mode d'affichage Shade sous Deluxe Paint 3 ![]() Fig. 10 - utilisation de l'effet Shade dans une démo Dans un cas extrême, si vous deviez vous retrouver à utiliser A, B, C et D au complet, avec par exemple six plans de bits activés, et si en plus vous n'avez pas de vraie mémoire rapide (Fast RAM), vous ralentiriez considérablement votre programme car beaucoup de cycles seraient accaparés au détriment du 68000. 7. Lancement opération de copie En fait, il suffit de revenir au paragraphe 4 car, une fois que l'on écrit dans le registre BLTSIZE .W ($DFF058), la copie débute. C'est donc le dernier registre à être écrit. Pour renseigner les registres du Blitter pour une nouvelle opération de copie, il faudra attendre scrupuleusement que le Blitter ait fini son opération en cours. Conclusion Nous avons donc vu d'une manière globale comment le Blitter opère une copie de blocs mémoire. Dans la partie 2 à venir de cet article, nous rajouterons à notre démo (n'oublions pas que le but est de créer une démo Amiga) des BOB en mouvement dans la partie centrale de l'écran. Pour des références plus détaillées et complémentaires, vous pouvez aller jeter un oeil ou deux sur : La Bible Amiga de MicroApplication, Amiga Hardware Reference Manual de chez Commodore et sur le long article de Stash of Code. Sur ce, je vous dis à bientôt pour la partie 2 de notre exploration du Blitter.
|