Zoom hardware avec BPLxMOD et BPLCON1 sur Amiga

Le choc de l'arrivée de la Super Nintendo fin 1991 pour les amateurs de micros 16 bits, ce fut le Mode 7. La console était capable d'appliquer une rotation et un redimensionnement à un bitmap de grande taille dans la trame, et il était possible de trafiquer pour produire un effet de perspective.
L'Amiga 500 était doublement handicapé pour parvenir à produire de tels effets : pas de circuit spécialisé pour cela, et une organisation des données affichées sous forme de bitplanes exigeant, pour y parvenir, de nombreuses opérations au CPU et/ou au Blitter. Toutefois, cela n'empêcha pas certains de produire des effets du genre, par exemple le zoom au démarrage de la démo World of Commodore 92 de Sanity :
Zoom dans la démo World of Commodore 92 de Sanity
Parmi tous ces effets de zoom avec ou sans perspective, la plupart se sont appuyés sur le zoom vertical hardware, découvert dès les débuts de l'Amiga, et certains sur le zoom horizontal hardware, qui l'a été bien plus tard.
Comment procéder à l'un et l'autre de ces zooms avec le hardware ? En particulier, quel est ce fameux "$102 trick" régulièrement évoqué dans les forums de demomakers, souvent succinctement, et parfois abusivement ? Et dans quelle mesure est-il possible de zoomer ainsi ? Tout cela et plus encore dans ce qui suit.
Mise à jour du 11/09/2018 (matin) : Correction de la figure représentant le scénario du zoom horizontal hardware (une étape de trop!) et de la "magic table" (bogue dans le programme HTML5 générateur!).
Mise à jour du 11/09/2018 (soir) : Ajout d'un paragraphe et d'une figure pour expliquer pourquoi le zoom horizontal hardware limite à 4 le nombre de bitplanes.
Mise à jour du 12/09/2018 (matin) : Modification de la fin de la section sur le passage de la dissimulation à la suppression, pour expliquer pourquoi et comment il faut optimiser.
Mise à jour du 01/10/2018 : Tous les sources ont été modifiés pour intégrer une section "StingRay's stuff" qui permet d'assurer le bon fonctionnement sur tous les modèles d'Amiga, notamment dotés d'une carte graphique.
Cliquez ici pour télécharger l'archive contenant le code et les données des programmes présentés ici.
Cette archive contient plusieurs sources :
  • zoom0.s pour dissimuler par zoom vertical hardware des lignes avant, au milieu et en bas d'une image et recentrer cette dernière verticalement à l'écran ;
  • zoom1.s pour identifier quand utiliser le Copper pour modifier la valeur de BPLCON1 afin de dissimuler un certain nombre de colonnes formées des derniers pixels d'un groupe de 16 pixels ;
  • zoom2.s pour visualiser le résultat produit par le zoom horizontal hardware reposant sur la technique précédente généralisée pour dissimuler de 1 à 15 colonnes de pixels d'une image ;
  • zoom3.s pour tester un zoom reposant sur la technique précédente pour réduire une image de 306 à 15 pixels de largeur ;
  • zoom4.s pour tester un zoom combinant zoom horizontal hardware et zoom vertical hardware pour réduire une image de 306 x 256 pixels à 15 x 15 pixels.
NB : Cet article se lit mieux en écoutant l'excellent module Helmet for sale composé par Jason / Kefrens pour R.A.W. #2, mais c'est affaire de goût personnel...

Le zoom vertical hardware, un usage banal des modulos

Comme expliqué ici, le hardware affiche une image composée de bitplanes dont les adresses lui sont fournies via les couples de registres BPLxPTH / BPLxPTL. Une fois qu'il a lu et affiché les données d'une ligne, le hardware rajoute l'équivalent en octets à ces adresses - 40 octets pour un écran de 320 pixels de large -, puis il y rajoute un modulo. Ce modulo est stocké dans BPL1MOD pour les bitplanes impairs (1, 3, etc.), et BPL2MOD pour les bitplanes pairs (2, 4, etc.).
Sachant que le hardware lit donc dans des registres que le Copper permet de modifier (au moins) à chaque ligne de l'écran, on conçoit facilement comment tirer profit de ce fonctionnement. Prenons l'exemple d'une image de 320 x 256 pixels en 16 couleurs, donc 4 bitplanes.
Il est possible de demander au Copper d'attendre le début d'une ligne et d'écrire dans les registres BPLxPTH/L pour modifier l'adresse de la ligne qui sera affichée. Cette adresse sera déterminée en fonction du zoom, pour sauter ou répéter des lignes, ou simplement passer à la ligne suivante. La Copper list contient alors notamment un bloc suivant par ligne N, ici présenté en pseudo-code pour en faciliter la lecture :
	WAIT <début de ligne N>
	MOVE <valeur>, BLTP1PTH
	MOVE <valeur>, BLTP1PTL
	MOVE <valeur>, BLTP2PTH
	MOVE <valeur>, BLTP2PTL
	MOVE <valeur>, BLTP3PTH
	MOVE <valeur>, BLTP3PTL
	MOVE <valeur>, BLTP4PTH
	MOVE <valeur>, BLTP4PTL
Qui est certain de l'alignement des données en mémoire peut se dispenser d'écrire dans BPLxPTH sachant que sa valeur ne changera pas quel que soit l'offset rajouté pour progresser dans les bitplanes, mais peu importe : c'est le principe qu'on illustre ici.
Il est aussi possible de simplement écrire dans BPL1MOD et BPL2MOD. Dans ce cas, c'est l'adresse de la ligne suivante, et non l'adresse de la ligne courante, qui sera impactée, le hardware utilisant les modulos en fin de ligne. La Copper list contient alors notamment un bloc suivant par ligne N, ici encore présenté en pseudo-code pour en faciliter la lecture :
	WAIT <début de ligne N-1>
	MOVE <valeur>, BPL1MOD
	MOVE <valeur>, BPL2MOD
Utiliser BPLxMOD affecte tous les bitplanes impairs et/ou tous les bitplanes pairs, sans discrimination. Toutefois ce n'est pas gênant, car le zoom à réaliser est généralement le seul effet à produire dans les bitplanes. Par ailleurs, utiliser ces registres est plus économique qu'utiliser BPLxPTH/L.
En effet, il ne faut pas oublier que le facteur de zoom variant d'une trame à l'autre, la Copper list devra être modifiée au CPU ou au Blitter pour mettre à jour les valeurs que le Copper écrit dans les registres utilisés. Or le calcul est vite fait :
  • avec BPLxPTH/L, il faut modifier les valeurs de deux MOVE par bitplanes, soit 8 valeurs au total (éventuellement 4 en jouant sur l'alignement en mémoire) ;
  • avec BPLxMOD, il faut modifier les valeurs de deux MOVE tout court, soit 2 valeurs au total.
Toutefois, il ne suffit pas de dissimuler des lignes pour produire un zoom : à toute étape, il faut que l'image reste centrée à l'écran. Or modifier les modulos entraîne le tassement de l'image vers le haut de l'écran. Pour compenser il faut repousser vers le bas l'image de la moitié du nombre de lignes dissimulées. C'est possible grâce au moins à deux solutions :
  • modifier la position verticale de départ de l'affichage dans le registre DIWSTRT, et la hauteur de cet affichage dans le registre DIWSTOP ;
  • modifier la position verticale d'un WAIT du Copper, position à partir de laquelle des MOVE modifient les registres BPLxPTH/L pour pointer sur les adresses de départ des bitplanes de l'image zoomée.
Dans l'un et l'autre cas, il est inutile de modifier la position verticale des WAIT qui indiquent au Copper à quelle ligne il doit attendre avant d'exécuter le MOVE qui, en écrivant une valeur dans BPLxMOD, entraîne ou non la dissimulation d'une ou plusieurs lignes. En effet, à chaque étape du zoom, il suffit d'actualiser directement dans le code de la Copper list les valeurs que ces MOVE écrivent dans BPLxMOD, en tenant compte de la nouvelle position verticale de l'image pour sélectionner ces MOVE. Bref, s'il faut réduire la hauteur d'une image de 256 à 0 pixels en N étapes, il suffit de construire une Copper list qui contient toujours 256 WAIT suivis de MOVE sur BPLxMOD, et de modifier à chaque étape les valeurs que ces MOVE écrivent dans BPLxMOD pour animer le zoom.
Dans le programme final zoom4.s, c'est la première solution qui a été adoptée par simplicité. Il faut noter que cela implique de modifier BPLxPTH/L pour parvenir à dissimuler une ou plusieurs des premières lignes. En effet, le modulo est une valeur que le hardware ajoute à l'adresse de la ligne qui vient d'être affichée. Or par définition, la première ligne ne vient après aucune autre ligne. Toutefois, c'est le seul cas où ces registres sont modifiés pour zoomer.
Par ailleurs, il faut noter que le faisceau d'électrons ne trace donc rien au-dessus ni en-dessous de l'image zoomée. Dans ces conditions, il est impossible d'afficher des bandeaux encadrant l'image zoomée. Pour ce faire, il faut opter pour la seconde solution.
La figure suivante récapitule ce qui se passe dans le cas où les lignes 0, 1, 2 et 5 de l'image doivent être dissimulées :
  • Comme il s'agit de dissimuler quatre lignes, DIWSTART est modifié pour que l'affichage démarre deux lignes plus bas, assurant ainsi que l'image reste verticalement centrée à l'écran.
  • En conséquence, DIWSTOP est aussi réduit pour que le hardware n'affiche pas du garbage après avoir affiché la 256ème ligne de l'image.
  • Pour dissimuler les trois premières lignes, impossible d'utiliser BPLxMOD. C'est donc BPLxPTH/L qui est modifié avant le début de l'affichage de la ligne 3.
  • Pour dissimuler la ligne 5, BPLxMOD est passé à l'équivalent d'une ligne en octets, soit 40 octets, avant la fin de l'affichage de la ligne 4.
Principe du zoom vertical hardware
Le programme zoom0.s constitue un exemple de base où les 16 premières lignes, les 16 lignes médianes et les 16 dernières lignes d'une image sont ainsi dissimulées - pour la beauté du geste, c'est l'image Dragon Sun de Cougar / Sanity qui est utilisée :
Zoom vertical hardware d'une image
A ce stade, il reste un point essentiel à régler : comment identifier les lignes à dissimuler à une étape du zoom donnée ? Cette question, qui se pose presque dans les mêmes termes pour le choix des colonnes à dissimuler, sera abordée plus loin.

Le zoom horizontal hardware, un détournement surprenant du scrolling

L'histoire dira à qui revient le mérite d'avoir trouvé l'astuce (on évoque ici le génial Chaos / Sanity). Comme chacun sait, il est possible de demander au hardware de retarder l'affichage de l'image à l'écran d'un nombre de pixels allant de 0 à 15. La valeur de ce retard doit être spécifiée dans le registre BPLCON1, qui comporte 4 bits (PF1H3-0) pour le retard des bitplanes impairs (1, 3, etc.) et 4 bits (PF2H3-0) pour le retard des bitplanes impairs (2, 4, etc.). C'est le scrolling hardware.
Ainsi, une valeur de $005F dans BPLCON1 va retarder l'affichage des bitplanes pairs de 5 pixels et celui des bitplanes impairs de 15 pixels. A moins d'utiliser le dual-playfield ou de chercher à manipuler distinctement les bitplanes pairs et impairs, les retards spécifiés pour ces bitplanes seront identiques. Il s'agira de produire un scrolling horizontal en jouant sur BPLCON1 et sur les couples BPLxPTH / BPLxPTL - on ne s'étendra pas sur ce sujet trivial.
Or le hardware lit les données qu'il va afficher par groupe de 16 pixels. A chaque fois, il tient compte des décalages spécifiés dans BPLCON1 pour retarder plus ou moins l'affichage de ce groupe. Que se passerait-il si la valeur de ces décalages devait être réduite d'un groupe à un autre ? En particulier, le hardware n'afficherait-il pas plus tôt le second groupe, écrasant les derniers pixels du premier ?
C'est exactement ce qui se passe. Et comme dissimuler des colonnes de pixels dans l'image produit une réduction de la largeur de cette dernière, il y a lieu de parler de zoom horizontal hardware :
Dissimulation des 2 derniers pixels d'un groupe en réduisant le scrolling hardware de 2
Encore faut-il régler une question pratique : quand et comment modifier la valeur des décalages dans BPLCON1 ? Comme cela vient d'être suggéré, il faut que l'écriture dans BPLCON1 soit synchronisée sur le cycle de lecture et d'affichage des groupes de pixels par le hardware.
Chacun sait qu'il est toujours possible d'attendre une position du faisceau d'électron (le raster) à l'écran, et de modifier le contenu d'un registre hardware à cet instant. Cette manœuvre peut être réalisée au CPU, mais s'il fallait réaliser le zoom hardware de cette manière, il serait impossible de rien faire d'autre durant une trame.
Fort heureusement, le Copper est là, qui exécute sa Copper list parallèlement. Pourquoi ne pas utiliser ses instructions WAIT et MOVE, détaillées ici, pour parvenir au résultat souhaité ?
Le programme zoom1.s en donne une illustration. L'idée va être d'attendre la position à laquelle on souhaite dissimuler un certain nombre des derniers pixels d'un groupe donné.
Mais où attendre ? A vrai dire, il y aurait deux solutions : utiliser un WAIT puis un MOVE pour modifier BPLCON1. Ou alors, comme dans le cas d'un effet plasma, enchaîner les MOVE en sachant qu'un MOVE prend 8 pixels en basse résolution pour être exécuté, dont un MOVE pour modifier BPLCON1. Dans les faits, seule la seconde solution est praticable.
Pourquoi ? Parce que c'est comme ça. Tous les coders qui utilisent le zoom horizontal hardware le font ainsi. Pourquoi, encore ? Parce qu'il faudrait vraiment, mais alors vraiment, se casser la tête pour parvenir à calculer précisément la position horizontale à spécifier dans le WAIT pour que le MOVE soit exécuté au bon moment - et encore, il n'est pas dit que ce serait possible. A ceux qui se lamenteraient de ce manque de rigueur, disons que pour aller au fond des choses, il faudrait savoir expliquer à tout instant le rapport entre la position horizontale du faisceau d'électron, telle qu'elle est formulée dans un WAIT du Copper, et la valeur horizontale telle qu'elle figure dans DDFSTRT. Or si c'est certainement possible, cela reste à faire...
Ainsi le Copper est programmé pour attendre le début de chaque ligne à la position horizontale $3D, empiriquement déterminée, et procéder à 40 MOVE dont certains vont modifier la valeur de BPLCON1 en réduisant le retard initial, eux aussi empiriquement déterminés.
Il faut noter qu'une telle sollicitation du Copper contraint le nombre de bitplanes, donc le nombre de couleurs à l'écran. La raison en a déjà été présentée ici : au-delà de 4 bitplanes, le hardware vole des cycles au Copper, si bien que ce dernier perd la possibilité d'exécuter autant de MOVE par ligne :
Dans zoom1.s, la partie de la Copper list concernant le zoom (après une initialisation de BPLCON1 à $00FF toutefois) se présente ainsi :
	;Zoom

	move.w #ZOOM_Y<<8,d0
	move.w #ZOOM_DY-1,d1
_zoomLines:

	;Attendre le début la ligne

	move.w d0,d2
	or.w #$00!$0001,d2
	move.w d2,(a0)+
	move.w #$8000!($7F<<8)!$FE,(a0)+

	;Initialiser BPLCON1 avec une retard de 15 pixels ($00FF)

	move.w #BPLCON1,(a0)+
	move.w #$00FF,(a0)+

	;Attendre la position sur la ligne correspondant au début de l'affichage (position horizontale $3D dans un WAIT)

	move.w d0,d2
	or.w #ZOOM_X!$0001,d2
	move.w d2,(a0)+
	move.w #$8000!($7F<<8)!$FE,(a0)+

	;Enchaîner des MOVE qui ne font rien jusqu'à celui qui doit passer le retard à ZOOM_BPLCON1

	IFNE ZOOM_MOVE	;Car ASM-One plante sur un REPT dont la valeur est 0...
	REPT ZOOM_MOVE
	move.l #ZOOM_NOP,(a0)+
	ENDR
	ENDC

	;Modifier BPLCON1 pour passer le retard à ZOOM_BPLCON1

	move.w #BPLCON1,(a0)+
	move.w #ZOOM_BPLCON1,(a0)+
	
	;Enchaîner des MOVE qui ne font rien jusqu'à la fin de la ligne

	IFNE 39-ZOOM_MOVE		;Car ASM-One plante sur un REPT dont la valeur est 0...
	REPT 39-ZOOM_MOVE
	move.l #ZOOM_NOP,(a0)+
	ENDR
	ENDC

	;Passer à la ligne suivante de la bande de lignes zoomées

	addi.w #$0100,d0
	dbf d1,_zoomLines

	;Réinitialiser BPLCON1 ($00FF) pour la fin de l'écran

	move.w #BPLCON1,(a0)+
	move.w #$00FF,(a0)+
La Copper list contient alors notamment un bloc suivant par ligne N, ici encore présenté en pseudo-code pour en faciliter la lecture :
	WAIT ($00, N)
	MOVE #$00FF, BPLCON1
	WAIT ($3D, N)
	REPT ZOOM_MOVE
	MOVE #$000, ZOOM_NOP
	ENDR
	MOVE #ZOOM_BPLCON1, BPLCON1
	REPT 39-ZOOM_MOVE
	MOVE #$000, ZOOM_NOP
	ENDR

Ajuster le zoom horizontal hardware, un vrai challenge

En modifiant ZOOM_MOVE dans le programme précédent, on indique l'indice du MOVE auquel on souhaite que le Copper écrive ZOOM_BPLCON1 dans BPLCON1, sachant que BPLCON1 est initialisé à $00FF. Il est alors possible d'observer le résultat à l'écran.
Pour que ce dernier soit bien visible, l'effet est répété sur une bande de ZOOM_DY de hauteur, en l'occurrence 20 pixels, précédée et suivie d'une bande de même hauteur sans effet. Par ailleurs, un motif blanc sur fond rouge est dessiné dans le bitplane : c'est une succession de groupes de 16 pixels : dans le 1er groupe, le dernier pixel est blanc ; dans le 2ème groupe, les deux derniers pixels sont blancs ; etc. Ce motif permet de réaliser un effet du genre : "si je me positionne au niveau du MOVE correspondant au groupe comportant 4 pixels blancs et que je réduis BPLCON1 de 4, est-ce que j'observe la disparition de ces 4 pixels ?"
Ce qui peut être observé, c'est que très généralement, les résultats sont décevants. Par exemple, passer BPLCON1 à $0022 au 18ème MOVE (ie : ZOOM_MOVE valant 17) produit cela :
Les aléas de la modification de BPLCON1 en cours de ligne
Toutefois, ils peuvent être satisfaisants. A titre de contre-exemple, passer BPLCON1 à $00EE au 4ème MOVE produit cela :
La dissimulation réussie d'une colonne par modification de BPLCON1 en cours de ligne
C'est en procédant de la sorte, mais à l'aide d'un programme plus élaboré qui permet de désigner plusieurs MOVE devant réduire le retard de 1 dans BPLCON1, qu'il est possible d'établir une liste des 40 MOVE permettant de réduire la largeur de l'image de 1 pixels, de 2 pixels, de 3 pixels et ainsi de suite jusqu'à 15 pixels, pour une valeur initiale de BPLCON1 donnée.
"Une valeur initiale de BPLCON1 donnée" ? Ce n'est pas $00FF ? Non, car il faut gérer une contrainte : lorsqu'une colonne est dissimulée, les colonnes qui suivent sont décalées à l'écran d'un pixel sur la gauche. A ce train, une image dont 15 colonnes sont dissimulées se trouve tassée de 15 pixels sur la gauche. Or ce n'est pas ce qui est attendu lors d'un zoom réduisant la largeur d'une image de 320 à 0 pixels : comme évoqué en présentant le zoom vertical hardware, l'image doit rester centrée à l'écran.
Pour y parvenir, il faut adopter le scénario suivant. Sur cette figure, chaque ligne représente une étape du zoom. A gauche, la valeur initiale de BPLCON1. A droite, sa valeur finale. Entre les deux, les 20 groupes de 16 pixels composant une ligne d'une image de 320 pixels de large :
  • les groupes dont les derniers pixels ont déjà dissimulés aux étapes précédentes sont en rouge clair ;
  • le groupe dont le dernier pixel doit être dissimulé à cette étape est en rouge foncé.
A chaque groupe rouge clair ou rouge foncé, la valeur de BPLCON1 est réduite de 1 pour dissimuler un pixel.
Scénario du zoom horizontal hardware
Comme il est possible de le constater, le scénario vise à compenser autant que possible le tassement sur la gauche de l'image en la décalant d'un pixel sur la droite chaque fois que deux pixels ont été dissimulés. C'est un scénario qui se construit en remontant les étapes depuis la dernière, car il faut que la valeur initiale de BPLCON1 soit alors $00FF pour pouvoir dissimuler 15 pixels.
Adopter ce scénario a une conséquence capitale. Pour le comprendre, il faut désormais bien distinguer quatre choses :
  • L'écran. C'est l'écran physique de l'ordinateur : s'agissant du repère de référence, il n'est jamais décalé, et sa résolution horizontale est toujours de 320 pixels.
  • L'image. C'est ce que le spectateur voit à l'écran : une colonne de couleur 0 sur la gauche créée par la valeur initiale de BPLCON1, puis les bitplanes dont la partie visible est amputée d'une colonne de même largeur sur la droite.
  • Les bitplanes. Ce sont des espaces en mémoire de 320 pixels de large dont une partie seulement est visible dans l'image à l'écran.
  • Le dessin. C'est ce que l'on souhaite donner à voir au spectateur, à savoir quelque chose dont la largeur se réduit progressivement tout en restant centré dans l'image.
C'est l'enchâssement de multiples repères (le dessin dans le bitplane, le bitplane dans l'image, l'image dans l'écran - mais on peut considérer que leurs repères sont confondus) qui fait toute la complexité de la gestion du zoom horizontal hardware.
Quand aucune colonne n'est dissimulée, il faut que le dessin soit centré à l'écran. Or BPLCON1 vaut alors $0077. Pour qu'un dessin décalé de 7 pixels sur la gauche apparaisse centré dans l'écran de 320 pixels de large, il faut que la largeur de ce dessin ne dépasse pas 306 pixels. En effet si 7 pixels sont inutilisables à gauche, il faut que 7 pixels le soient à droite : 14 pixels en tout, à soustraire de 320, ce qui donne 306.
C'est donc un dessin de 306 pixels de large, calé sur la gauche dans des bitplanes de 320 pixels de large, qui peut être au plus utilisé pour produire le zoom, la colonne de 14 pixels de large sur la droite devant être en couleur 0 :
Le dessin de 306 pixels de large dans un bitplane de 320 pixels de large
Le scénario débouche sur une liste qui donne la valeur de BPLCON1 en début de ligne et identifie les MOVE qui décrémentent cette valeur pour dissimuler la dernière colonne de N groupes de 16 pixels, réduisant d'autant de pixels la largeur de l'image :
La "magic list" du zoom horizontal hardware
Cette liste est totalement spécifique :
  • la série de 40 MOVE d'une ligne doit être précédée d'un WAIT à la position horizontale $3D ;
  • les groupes ont été déterminés en appliquant le scénario, qui n'est qu'un scénario parmi d'autres.
Reconnaissons cette liste comme une des "magic list" de l'Amiga. Une autre "magic list" est celle qui indique comment organiser la Copper list pour produire des boucles qui répètent, sur 8 lignes, des séries de 40 MOVE, et ce sur toute la hauteur d'un écran PAL, c'est-à-dire sur 256 lignes. Cette autre liste sera présentée est expliquée dans la seconde partie de cet article, une fois que la cracktro qui l'exploite aura été diffusée - ce n'est pas que personne n'a jamais élaboré cette liste ; c'est qu'il faut pouvoir proposer le code de cette cracktro au téléchargement pour que l'article soit intéressant à lire... et de nos jours, il faut attendre qu'un jeu sorte pour sortir une cracktro !
Retour à notre liste. Sa mise en œuvre dans le programme zoom2.s produit le résultat suivant, où un dessin de 306 pixels de large (lignes blanches sur fond gris) centré dans un bitplane de 320 pixels de large (fond rouge) est réduit progressivement en dissimulant les derniers pixels de 15 groupes de 16 pixels les uns après les autres. Pour que le zoom semble progressif s'il devait être animé, les dissimulations alternent entre la gauche et la droite du groupe de pixels du milieu. Le dessin est intact dans la moitié supérieure de l'écran ; il subit le zoom dans la moitié inférieure de l'écran, perdant un pixel toutes les 8 lignes :
Réduire progressivement la largeur d'une image de 15 pixels, tout en la centrant

Au-delà de 15 pixels, passer de la dissimulation à la suppression

BPLCON1 permet d'introduire un retard initial de 15 pixels au plus au début de chaque ligne. Partant, c'est au plus 15 pixels qu'il est possible de dissimuler sur cette ligne, ce qui est clairement insuffisant pour produire un zoom où la largeur de l'image passerait de 320 à 0 pixels.
Quand les possibilités du zoom horizontal hardware sont épuisées (BPLCON1 valant $00FF au début d'une ligne, et étant réduit progressivement jusqu'à $0000), nulle autre solution que de prendre le relai avec un zoom horizontal software. Il s'agit alors de reproduire dans les bitplanes ce que le spectateur voit à l'écran avant de réinitialiser le zoom hardware (BPLCON1 valant $0077 au début d'une ligne, et n'étant pas réduit). Cela revient à dire qu'il faut supprimer véritablement les colonnes de pixels dissimulées dans les bitplanes, et recentrer horizontalement le dessin ainsi réduit dans ces derniers.
Recentrer de combien de pixels ? Comme cela vient d'être rappelé, au moment où il faut prendre le relai du zoom hardware, la valeur de BPLCON1 est $00FF, ce qui correspond à un décalage de 15 pixels sur la droite. Par ailleurs, la valeur de BPLCON1 quand aucun pixel n'est dissimulé est de $0077, ce qui correspond à un décalage de 7 pixels sur la droite. En conséquence, il faut produire un dessin qui, lorsque les bitplanes qui le contiennent seront ainsi décalés, présentera les caractéristiques suivantes :
  • il fera 320 - 15 = 305 pixels de large ;
  • il apparaîtra décalé de 15 pixels sur la droite.
Bref, une fois réduit, le dessin doit être décalé de 15 - 7 = 8 pixels sur la droite dans les bitplanes. On parle bien du dessin et non des bitplanes. En effet, il est bien entendu que la largeur de ces derniers ne change pas ; c'est le dessin qu'ils contiennent qui est réduit, pas eux.
Supprimer des colonnes et décaler l’ensemble lors du zoom software
Comment supprimer ? Il est possible d'utiliser le Blitter, qui s'entend à merveille pour décaler des mots sur la droite (en mode ascendant) comme sur la gauche (en mode descendant) après les avoir éventuellement masqués, ou le CPU, voire les deux.
Une optimisation est possible – en fait, elle est même nécessaire. En effet, au fil du zoom software, la largeur du dessin dans les bitplanes se réduit, jusqu'au point où le dessin n'empiète plus sur certains groupes de 16 pixels qui se trouvent à sa gauche et à sa droite.
Partant, il devient inutile de supprimer des colonnes dans ces groupes. De même, ces colonnes n'ont plus à être dissimulées. Ces optimisations sont obligatoires, car le spectateur ne comprendrait pas que le zoom semble faire une pause parce qu'une étape est consacrée à la dissimulation d'une colonne qui est vide. Il faut donc déterminer quelles colonnes doivent être dissimulées durant un cycle de zoom hardware, et supprimées par zoom software au terme de ce cycle, en fonction de la largeur du dessin au début de ce cycle. C'est ce qui a été fait pour réaliser le programme zoom3.s à l'aide de la feuille Excel qui figure dans zoom.xlsx :
Calcul des groupes à traiter à chaque cycle de zoom horizontal hardware
Comme le nombre de colonnes à supprimer se réduit, en mode débogage (constante DEBUG passée à 1), il apparaît que le temps pris par la suppression diminue tandis que le dessin occupe de moins en moins de groupes dans les bitplanes. Au début, ce temps est assez considérable pour que la suppression ne tienne pas dans la trame sur Amiga 500. En fait, il faudrait simplement sortir la suppression de la boucle principale et l'utiliser avant pour précalculer les versions successives du dessin. Elles devraient toutes pouvoir tenir en mémoire.
Au final, le code de la suppression est assez complexe, et il serait trop long d'en présenter le détail ici. Qui veut en savoir plus peut se référer au source du programme indiqué. On y utilise le Blitter uniquement pour recopier en les décalant et en les masquant les colonnes de mots qui forment le dessin, et celles-là uniquement.

Le choix des lignes et des colonnes à dissimuler : un dilemme d'informaticien

Les techniques présentées pour dissimuler / supprimer des lignes et des colonnes fonctionnent bien, mais elles reposent sur des hypothèses dont il faut avoir conscience. Ces hypothèses portent sur la manière dont il convient d'identifier les lignes et les colonnes à dissimuler à une étape du zoom donnée. Elles ont notamment déterminé le scénario du zoom horizontal hardware présenté plus tôt.
S'il faut dissimuler N lignes et colonnes à une étape du zoom donnée, le premier réflexe est de chercher à répartir également ces lignes sur la hauteur et ces colonnes sur la largeur du dessin. A bien y réfléchir, cela repose sur l'hypothèse que le zoom sera plus réaliste si la dissimulation est diffuse, car le spectateur devrait toujours disposer d'assez d'informations en tout point du dessin pour reconnaître sinon le dessin initial, du moins le dessin de l'étape précédente.
Toutefois, ce n'est qu'une hypothèse. S'il s'agissait de zoomer un texte, il y a fort à parier que les caractères traités aussi arbitrairement deviendraient vite méconnaissables. En toute rigueur, la réduction d'un dessin, c'est le filtrage d'un message, lequel ne peut déboucher sur un résultat acceptable qu'à condition de tenir un minimum compte de la signification du message en question, quitte à reformuler ce dernier.
Cette considération sémantique peut paraître stratosphérique dans le cas du zoom en temps réel d'un dessin sur Amiga, tant il est vrai que la puissance de calcul disponible interdira d'en tenir compte rigoureusement - si tant est qu'il soit seulement possible d'en tenir compte ! Toutefois, il est bon de l'évoquer pour échapper à une remise en question sans issue de la pertinence de l'hypothèse adoptée. Par là, on veut dire qu'en cherchant à diffuser les dissimulations, on cherche à faire au mieux pour emballer le spectateur avec les moyens du bord. Ce qui fonde l'hypothèse, ce ne sont pas de savants calculs ; c'est que le résultat qui en découle est, pour le spectateur, assez convaincant.
Enfin, il faut tout de même questionner l'hypothèse du fait d'une contrainte technique qui limite sérieusement cette application. Cette contrainte porte sur le zoom horizontal hardware, dont on a vu qu'il n'est pas aussi souple que le zoom vertical hardware. En effet, il est impossible de choisir les colonnes à dissimuler aussi facilement que les lignes à traiter pareillement : c'est parmi les derniers pixels de groupes de 16 pixels qu'il faut choisir.
Par exemple, pour réduire un dessin de 320 pixels à 318 en dissimulant 2 colonnes, il pourrait paraître cohérent de dissimuler les colonnes 106 et 214, l'espace entre les colonnes dissimulés étant alors régulier :
  • 106 pixels entre les colonnes 0 et 105 ;
  • 107 pixels entre les colonnes 107 et 213 ;
  • 106 pixels entre les colonnes 215 et 320.
Or la colonne 106 correspond au 10ème pixel du 6ème groupe de 16 pixels d'une ligne. Autrement dit, elle tombe mal, car elle ne tombe pas sur le pixel d'un groupe qu'il est possible de dissimuler en réduisant le retard de 1 avant l'affichage de ce dernier. Bref, elle gêne car elle ne correspond pas au 16ème pixel d'un groupe de 16 pixels.
La distribution des colonnes de pixels qu'il est possible de dissimuler est contrainte. Qui veut réduire progressivement la largeur d'un dessin de 320 à 0 pixels en dissimulant une nouvelle colonne de pixels à chaque étape peut au mieux répartir les colonnes qu'il dissimule tous les 16 pixels. Et encore, ce n'est pas tous les 16 pixels du dessin, mais tous les 16 pixels des bitplanes qui contiennent le dessin, comme on l'a vu.
Enfin, quelle que soit l'hypothèse adoptée, il faut aussi tout de même la questionner sur un autre point. Continuons sur l'exemple du zoom horizontal. Indépendamment de la contrainte qui vient d'être évoquée, s'il faut enchaîner des dissimulations de colonnes, comment vaut-il mieux procéder :
  • en considérant qu'une colonne dissimulée ne doit pas réapparaître, et donc en dissimulant une nouvelle colonne quitte à ce que les colonnes supprimées ne soient pas également réparties sur toute la largeur du dessin ?
  • en considérant qu'une colonne dissimulée peut réapparaître, et donc en dissimulant de nouvelles colonnes, qui seront nécessairement différentes, mais qui présenteront l'intérêt d'être toujours également réparties sur toute la largeur du dessin ?
Ici encore, c'est un questionnement sur lequel on passerait un temps infini, car au regard des moyens disponibles, il est impossible de retenir l'une ou l'autre de ces hypothèses sans tenir compte de l'effet que le résultat qui en découle produit sur le spectateur.
Pour un esprit cartésien, cette histoire de zoom hardware, c'est un peu la déconvenue à chaque étape. L'identification des MOVE pour modifier BPLCON1 au bon moment sur une ligne ? A déterminer empiriquement. L'identification des colonnes et des lignes à dissimuler ? A déterminer empiriquement. Le choix de faire réapparaître ou non des colonnes et des lignes dissimulées ? A déterminer empiriquement.
Mais c'est que l'esprit cartésien s'est trompé de domaine. Rappelons que le zoom hardware est employé dans une démo. Or, ainsi que l'a fort bien expliqué le formidable Navis / ASD, le principe de toute démo est de faire illusion : "We have to cheat. And you know, everybody does this. This is why demos are different to making games or making offline films."
C'est parole de Maître, car on ne saurait mieux définir le genre. Or il est bon qu'on nous rappelle régulièrement à quoi nous sommes censés nous consacrer. C'est le meilleur moyen pour ne pas être victime d'une illusion, celle de la poursuite d'une perfection sans intérêt pour celui auquel nous destinons les fruits de notre labeur, à savoir le spectateur. Et dans une démo, il s'agit de faire du Hollywood pour attirer les foules, pas du cinéma français pour les tenir à distance... Si vous ne travaillez que pour vous-même, à moins d'être un génie - mais qui se pose cette question a déjà la réponse -, vous ne resterez pas dans l'Histoire : le génie subjugue ; le tiers doit convaincre.
Sans donc nous faire d'illusion sur la catégorie dont nous relevons, concluons benoîtement notre exploration des délices du zoom hardware.
Le programme zoom4.s rajoute le zoom vertical hardware à la combinaison des zooms horizontaux hardware et software. Dans le cas du zoom vertical, le scénario adopté est exactement le même que celui retenu pour la dissimulation des colonnes, imposé par les limitations du zoom horizontal hardware. Un outil en HTML5, réalisé pour l'occasion, a permis de générer les indices des lignes à dissimuler tandis que le zoom progresse :
Un outil réalisé en HTML pour tester un scénario de zoom vertical

Le zoom horizontal hardware, une trouvaille inutile ?

Il faut saluer la ténacité de ceux qui se sont lancés dans le zoom hardware pour en faire quelque chose, tant l'effet est pénible à maîtriser. Toutefois, il ne faut pas surestimer le gain qu'ils ont pu en tirer. Ce n'est pas parce qu'il y a zoom dans la trame bientôt en plein écran que ce dernier est hardware. En fait, quand le zoom horizontal est très réussi, il y a tout lieu de penser qu'il n'est pas hardware.
Ainsi, la fameuse démo Elysium de Sanity s'ouvre par zoom irréprochable d'une image sur 4 bitplanes. Mais le désassemblage de la Copper list ne donne à voir que des WAIT à chaque ligne qui débouchent sur des modifications de BPL1MOD et BPL2MOD. Autrement dit, le zoom horizontal hardware n'est pas utilisé ici, mais uniquement le zoom vertical hardware :
Zoom irréprochable d'une image en 4 bitplanes dans la démo Elysium de Sanity
On trouve un exemple de zoom horizontal hardware dans une autre production de Sanity, le music disk Jesterday. Ce type de zoom est utilisé pour produire le rouleau que l'utilisateur fait tourner pour sélectionner un morceau de musique à écouter :
Le rouleau de sélection dans le music disk Jesterday de Sanity
Encore plus original, le scrolling à la Star Wars dans The Fall par The Deadliners & Lemon. Le désassemblage de la Copper list montre que non seulement le zoom horizontal hardware est utilisé, mais que l'effet est combiné à des changements d'adresse de bitplanes pour effectuer des sauts dans ces derniers :
Le scrolling à la Star Wars dans The Fall de The Deadliners & Lemon
Ce scrolling doit être mentionné pour l'ingéniosité dont a fait preuve le codeur, mais aussi à l'inverse pour sa médiocre qualité visuelle, tant les lettres sont déformées1. On a vu des scollings de ce type bien plus agréables à contempler, tout particulièrement celui de l'invraisemblable jeu Stardust, qui n'utilise ni zoom horizontal hardware, ni zoom vertical hardware, en HiRes qui plus est ! :
Le scroll à la Star Wars de Stardust, totalement parfait
Il y a quelques leçons à tirer de tout cela :
  • D'abord, c'est que la focalisation sur les coprocesseurs peut conduire à faire perdre de vue le fait qu'il est parfaitement possible d'accomplir bien des effets au CPU, éventuellement beaucoup plus réussis. C'est comme toujours : on se focalise sur les accessoires au détriment du principal, parce qu'aspiré par la technique, on en vient à négliger l'algorithmique.
  • "Legends never die", comme dit l'autre. The Fall remonte à... avril 2018 ! Franchement, qui aurait dit que plus de 30 ans après sa sortie, il se trouverait encore des codeurs pour imaginer de nouvelles exploitations du hardware de l'Amiga 500 ? Ici encore, c'est comme toujours : épuise-t-on jamais les possibilités d'une vieille casserole, dont le proverbe dit qu'on y fait toujours la meilleure confiture ?
1 Cela n'enlève rien au mérite du codeur, qui a réussi une prouesse technique. Par ailleurs, la démo contient des effets visuellement très réussis, en particulier des enchaînements dont la réalisation a dû nécessiter un travail fou. Sur les enchaînements, lire d'ailleurs ici une interview de Chaos / Sanity, publiée dans The Jungle #2 en 1993. Codeur à juste titre très réputé, il prenait visiblement tout le monde de haut - lire "Chaos - Only superlatives please" publié dans R.A.W. #7 pour en rire -, mais son propos était pertinent.
Zoom hardware avec BPLxMOD et BPLCON1 sur Amiga