|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
A l'occasion de la sortie du deuxième documentaire de la formidable série From Bedrooms To Millions, cette fois consacré à l'Amiga de Commodore, prenons un instant pour revisiter ce qui fut un des nombreux aspects de la scène de l'époque : la production de "cracktros" (introductions de déplombeurs). Nous prendrons pour exemple une cracktro programmée il y a un quart de siècle pour le célèbre groupe Paradox : ![]() Cracktro sur Amiga Notez qu'il est possible de visualiser un enregistrement vidéo du résultat produit sur YouTube. La cracktro fait même partie de la sélection de portages en HTML5 de We Are Back, mais les auteurs de ce portage ont fait l'impasse sur l'effet le plus notable : l'utilisation d'une police à espace variable... Par contre, leurs homologues de Flashtro ont parfaitement réussi. Cet article est le premier d'une série de deux. Après avoir rappelé comment mettre en oeuuvre un environnement de développement en assembleur 68000 dans le contexte d'un émulateur Amiga, il présentera un des deux coprocesseurs graphiques, le Blitter. Le second article présentera l'autre coprocesseur graphique, le Copper, et fera une petite synthèse sur l'intérêt que revenir sur le passé peut présenter aujourd'hui. Cliquez ici pour télécharger le code (à peine un gros millier d'instructions en assembleur 68000) et les données de la cracktro. Émuler l'Amiga dans Windows WinUAE est l'émulateur par excellence pour faire revivre l'Amiga dans le contexte de Windows. Toutefois, il ne suffit pas de le récupérer pour pouvoir l'utiliser. Il faut aussi se procurer la ROM de l'Amiga, le Kickstart (au moins dans la version 1.3). Par ailleurs, pour pouvoir compiler la cracktro, il faut installer le système d'exploitation, AmigaOS (également nommé "Workbench", au moins dans la version 1.3). Kickstart et Workbench sont encore soumis à des droits. Ils sont commercialisés pour une dizaine d'euros via Amiga Forever. Comme toute démonstration d'un savoir-faire en matière de programmation, de graphisme et de musique digne de ce nom à l'époque, la cracktro a été programmée en assembleur 68000. Dans cet article, nous nous concentrerons sur la partie du code qui gère les effets graphiques (d'ailleurs, qu'il soit rendu à César ce qui revient à César : la partie qui joue la musique est "Coded by Lars "ZAP" Hamre/Amiga Freelancers" et "Modified by Estrup/Static Bytes"). Il n'est pas question ici de s'étendre sur la manière d'utiliser WinUAE. L'objet de cet article est de rappeler ce que c'était que de programmer directement le matériel de l'Amiga et d'essayer d'en tirer quelques leçons pour aujourd'hui, pas d'inciter à programmer de nouveau de cette manière. Seuls les codeurs de l'époque pourraient être motivés à l'idée de compiler la cracktro à partir des données mises à disposition, et encore. Ceux-là seront nécessairement assez chevronnés pour s'y retrouver dans WinUAE et créer une configuration inspirée de l'Amiga 500 ou version ultérieure à mémoire étendue et émulation de disque dur. Les quelques informations fournies ci-après leur permettront de se rafraîchir la mémoire pour assurer la suite. Cliquez ici pour télécharger le source et les données de la cracktro. Pour compiler le source et le lier avec les données au sein d'un exécutable, il vous faudra utiliser ASM-One : ![]() Exécution de la cracktro en mode Debug dans ASM-One Pour ce qui concerne la documentation, la référence de tout programmeur (nous dirons "codeur" pour revenir à l'esprit de l'époque) était l'Amiga Hardware Reference Manual (Addison-Wesley en avait édité une édition plus agréable à lire). Précis et clair car rédigé par les ingénieurs de Commodore eux-mêmes, le manuel donnait toutes les informations requises pour s'adonner à ce que d'aucuns dénonçaient comme du "metal bashing", c'est-à-dire la programmation directe du matériel en assembleur en court-circuitant le système d'exploitation, d'ailleurs totalement coupé le temps de l'exécution. Tout codeur français qui a fait ses armes dans les années 1980 sait la petite guerre qui a opposé les adorateurs de l'Atari ST et de l'Amiga, les deux machines 16 bits en concurrence sur le marché national. Objectivement, il faut noter que les capacités de l'Amiga dépassaient de loin celles de l'Atari ST dans le domaine du graphisme. C'est qu'en la matière, le codeur pouvait s'appuyer non seulement sur le processeur Motorola 68000, mais aussi et surtout sur deux coprocesseurs très puissants : le Blitter et le Copper. Commençons par présenter le premier. Le Blitter est capable de copier des blocs et de tracer des droites. Son contrôle s'effectue en positionnant des bits de registres 16 bits résidant à des adresses spécifiques (par exemple, le registre de contrôle du Blitter BLTCON0 se trouve à l'adresse $DFF040 ou $00DFF040 pour être plus exact, le 68000 adressant la mémoire sur 32 bits). Copier des blocs de mémoire Dans sa fonction de copie, le Blitter peut lire mot par mot (16 bits) jusqu'à trois blocs à des adresses différentes (les sources A, B et C) et les combiner bit à bit. Pour chaque bloc, il faut spécifier l'adresse 32 bits du premier mot via les registres BLTxPTH et BLTxPTL correspondant au mot de poids fort et au mot de poids faible de l'adresse en question. Il faut aussi spécifier le modulo via le registre BLTxMOD, c'est-à-dire le nombre d'octets à ajouter à l'adresse du mot suivant le dernier mot d'une ligne du bloc pour adresser le premier mot de la ligne suivante. Par exemple, sur la figure suivante, le modulo est de deux mots, soit quatre octets (la mémoire est un espace d'adressage linéaire, évidemment, mais le Blitter permet donc de se la représenter comme une surface) : ![]() Adressage d'un bloc de mémoire par le Blitter
"BLTSIZE" est un strobe, c'est-à-dire un registre dont le simple accès en écriture déclenche une action, en l'occurrence la copie attendue. La combinaison des sources correspond à une combinaison par OR de huit combinaisons par AND des sources. Nécessairement assez complexe, la formule générale est spécifiée à l'aide d'une combinaison de bits de l'octet de poids faible d'un des registres 16 bits de contrôle du Blitter, BLTCON0 (X désigne le bit de la source X et x désigne le NOT de ce bit) :
Ainsi, le bit de la destination D résultant de la combinaison des bits issus des sources A, B et C est d'abord calculé de huit manières en combinant par AND des bits des sources éventuellement inversés : ![]() Combinaison des sources par le Blitter ![]() Combinaison des combinaisons de sources par le Blitter
![]() Adressage en mode ascendant ou descendant pour remonter ou descendre une image d'une ligne Dans la cracktro, une copie avec décalage réalisée à chaque trame par le Blitter permet de produire le défilement (un bloc à l'adresse calculée dans A1 est recopié sur lui-même en mode ascendant en décalant ses bits du nombre de bits correspondant à la vitesse du défilement en pixels par trame) :
Pour le détail, c'est donc 11001100 qui est stocké dans l'octet de poids faible de BLTCON0 pour copier à l'identique les bits de la source B dans la destination. En effet, la combinaison logique mise en oeuvre est alors aBc+aBC+ABc+ABC, c'est-à-dire que le bit de la source B est retenu quelles que soient les valeurs des bits des sources A et C (par ailleurs désactivées). Mais j'oubliais... En même temps qu'il copie des blocs de mémoire, le Blitter peut remplir ces derniers en positionnant tous les bits rencontrés sur une ligne. Dans ce mode, le Blitter ne fait rien tant que le bit lu n'est pas 1, positionne tous les bits lus par la suite jusqu'à lire un bit 1, et reboucle alors. Il est possible d'inverser ce fonctionnement, contraignant donc le Blitter à positionner les bits lus dès le début tant qu'il n'a pas lu un bit 1, ne rien faire jusqu'à lire un bit 1, et reboucler alors. ![]() Remplissage normal et inversé par le Blitter ![]() Inclusive-fill et exclusive-fill du Blitter Dans la cracktro, cette fonctionnalité est utilisée pour remplir les surfaces des cubes qui sont donc tracées en s'assurant de ne faire figurer qu'un point par ligne le long de chaque côté. Et pour tracer ces côtés, c'est... le Blitter qui est encore utilisé, comme nous allons le voir plus loin. Pour terminer sur cette fonction de copie du Blitter, il faut noter que toutes les possibilités qui ont été évoquées (décalage, masquage, combinaison logique des sources et remplissage) peuvent être combinées sans pénalité, étant réalisées les unes après les autres dans un pipeline. Le manuel donne quelques conseils pour exploiter le remplissage du pipeline, mais il s'agit là d'un sujet particulièrement avancé - dont les ingénieurs de Commodore ne garantissaient par ailleurs pas la pérennité. Tracer des droites En positionnant un bit particulier de BLTCON0, il est possible de demander au Blitter non plus de copier un bloc en le remplissant ou non, mais de tracer une droite, d'au plus 1024 pixels, à l'aide d'un motif. Dans ce mode, le Blitter interprète différemment certains bits des registres BLTCON0 et BLTCON1. Pour tracer une droite entre A (xA, yA) et B (xB, yB), il faut connaître :
![]() Octants permettant de préciser la position de points d'une droite au Blitter
Le tracé d'une droite est une opération de copie de bloc combinant trois sources A, B et C où C correspond au plan de bits dans lequel la droite doit être tracée, A le bit à positionner dans le plan de bits pour y tracer un pixel de la droite, B le motif qui doit déterminer si le pixel courant de la droite doit effectivement être tracé dans le plan de bits. La combinaison des sources utilisée est donc AB+AC, mais d'autres peuvent être envisagées - notamment ABC+AB pour tracer une droite qu'il sera possible d'effacer en simplement la traçant de nouveau. Le tracé de droite est lancé comme une copie de bloc, en écrivant dans le registre BLTSIZE. Dans la cracktro, après initialisation une fois pour toutes de certains registres (BLTALWM avait été oublié !)... :
...le tracé de droite a été factorisé dans la routine suivante :
Comme déjà mentionné, ce tracé de droite s'appuie sur une fonctionnalité du Blitter qui permet de limiter le tracé de la droite à un pixel par ligne de pixels dans le plan de bits : ![]() Tracé de contours de faces en vue de leur remplissage par le Blitter Soient A, B et C les trois faces visibles (qui peuvent n'être qu'une ou deux) à un instant donné. Dans un plan de bits, A et C sont tracées en omettant soigneusement le côté qu'elles ont en commun, tandis que dans un autre plan de bits, B et C sont tracées en prenant la même précaution. Dans ces conditions, il est possible de remplir chacun des plans de bits au Blitter sans difficulté si bien que :
Un fonctionnement en parallèle du processeur Pour terminer sur le Blitter, il faut noter que fonctionnant en parallèle du processeur car disposant d'un accès direct en mémoire (DMA) - il est même possible d'interdire au processeur de lui voler des cycles d'accès à la mémoire -, il suffit de lancer une copie (avec ou sans remplissage) ou un tracé de lignes et reprendre comme si de rien n'était. In fine, il faut bien s'assurer que le Blitter avait terminé sa tâche, ce qui s'effectue en testant un bit du registre de contrôle DMACONR. Dans la cracktro, ce test revient fréquemment si bien que pour s'éviter d'en faire une routine à laquelle il aurait fallu sauter puis revenir en perdant des cycles d'exécution, elle figure sous la forme d'une macro (le test est doublé pour une bonne raison présentée ici) :
D'une manière générale, il n'y avait pas de petites économies en cycles d'exécution pour "tenir dans la trame" (par exemple : s'assurer qu'une image était reproduite à chaque balayage de l'écran, soit 50 fois par seconde dans le monde PAL, pour produire une animation fluide à l'écran), ce qui conduisait à privilégier la répétition de code, donc les macros telles que WAITBLIT, sur les routines.
|