Obligement - L'Amiga au maximum

Vendredi 23 mai 2025 - 11:00  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : Assembleur - Programmer un défilement sinusoïdal ("sine scroll") sur Amiga (partie 1)
(Article écrit par Yragael et extrait de www.stashofcode.fr - juin 2017)


Introduction

Un des effets les plus mobilisés par les codeurs sur Amiga a été le défilement sinusoïdal ("sine scroll"), c'est-à-dire le défilé de texte déformé en modifiant l'ordonnée de colonnes de pixels successives selon un sinus, comme dans cette intro du groupe Falon :

Programmer un défilement sinusoïdal

Le meilleur est le "one pixel sine scroll", où chacune de ces colonnes est affichée à une ordonnée spécifique. Toutefois, produire un tel effet est très consommateur en temps de calcul, comme nous le montrerons en y procédant d'abord au seul moyen du processeur. Pour étendre l'effet, nous déchargerons le processeur en sollicitant deux coprocesseurs graphiques : le Blitter et le Copper.

Cet article peut être lu par n'importe qui, ayant été rédigé pour qui n'aurait jamais programmé en assembleur 68000, et encore moins pour attaquer le matériel de l'Amiga.

Cliquez ici pour télécharger l'archive contenant le code et les données du programme présenté dans cet article. Cette archive contient plusieurs sources :
  • sinescroll.s est la version de base dont il sera question jusqu'à ce que nous optimisions.
  • sinescroll_final.s est la version optimisée de la version de base.
  • sinescroll_star.s est la version enjolivée de la version optimisée.
Cet article est le premier d'une série de cinq. Nous allons voir comment installer en environnement de développement sur un Amiga émulé avec WinUAE et programmé la liste Copper de base pour afficher quelque chose à l'écran.

NB : cet article se lit mieux en écoutant l'excellent module composé par Nuke/Anarchy pour la partie magazine de Stolen Data #7, mais c'est affaire de goût personnel...
  • Mise à jour du 12/07/2017 : suppression du positionnement de BLTPRI dans DMACON pour améliorer les performances.
  • Mise à jour du 17/07/2017 : désactivation de la fonctionnalité "Save marks" dans ASM-One pour faciliter l'édition mixte PC/Amiga.
  • Mise à jour du 30/05/2018 : ceux qui utilisent Notepad++ peuvent cliquer ici pour récupérer une version améliorée de l'UDL 68K Assembly (v4).
  • Mise à jour du 16/06/2018 : StingRay/Scoopex a permis de corriger des co(q)uilles, d'ajuster du vocabulaire et d'ajouter des détails pour la comptabilité. Merci à lui !
Installer un environnement de développement

Précisons d'emblée que toutes les documentations mobilisées pour programmer le défilement sinusoïdal présenté ici se trouvent en ligne :
Il en est de même pour les outils, à commencer par l'émulateur Amiga. En effet, nul besoin de récupérer un Amiga sur eBay pour programmer de nos jours sur cette machine. Nous utiliserons l'excellent émulateur WinUAE auquel nous demanderons de simuler un disque dur à partir d'un répertoire du PC. Cela nous permettra d'éditer le code dans un éditeur de texte sous Windows et de le charger pour l'assembler et le tester avec ASM-One s'exécutant sur un Amiga 1200 émulé par WinUAE.

Après avoir téléchargé et installé WinUAE, nous devons récupérer la ROM et le système d'exploitation, à savoir le Kickstart et le Workbench dans leurs versions 3.1. Kickstart et Workbench sont encore soumis à des droits. Ils sont commercialisés pour quelques dizaine d'euros via Amiga Forever.

Dans WinUAE, commençons par recréer la configuration d'un Amiga 1200 dans "Hardware" :
  • Dans "CPU and FPU", sélectionnons un 68020.
  • Dans "Chipset", sélectionnons AGA.
  • Dans "ROM", sélectionnons le Kickstart 3.1.
  • Dans "RAM", optons pour 2 Mo de Chip pas de Slow.
  • Dans "CD & Hard drives", cliquons sur "Add Directory or Achives..." et ajoutons un périphérique logique nommé "DH0:" renvoyant à un répertoire de notre PC où nous trouverons les fichiers de cette simulation de disque dur.
Cette configuration étant créée, enregistrons-la. Pour cela, cliquons sur "Hardware", donnons-lui un nom et cliquons sur "Save". Par la suite, nous pourrons recharger à tout instant la configuration en double-cliquant dessus.

Dans la même rubrique, rendons-nous dans "Floppy Drives" pour simuler l'insertion dans le lecteur de disquette DF0: de la première disquette du Workbench - celle libellée "Install 3.1". A cette occasion, réglons la vitesse d'émulation du lecteur de disquettes au maximum (glissière tout à gauche, sur "Turbo") pour ne plus perdre de temps.

Nous pouvons alors cliquer sur "Reset" pour lancer l'émulation.

Une fois le Workbench chargé à partir de la disquette, il s'agit de l'installer sur le disque dur pour s'épargner de long temps de chargement. Double-cliquons sur l'icône de la disquette Install 3.1, puis sur celle de son répertoire "Install", et enfin sur celle de la version de l'installation que nous souhaitons. Laissons-nous ensuite guider dans le processus d'installation du Workbench sur le disque dur :

Programmer un défilement sinusoïdal

Une fois le système d'exploitation installé sur disque dur, nous devons installer l'environnement de développement. Pour assembler le source et le lier avec les données au sein d'un exécutable, nous utiliserons ASM-One. Comme tous les fichiers évoqués par la suite, il nous suffit d'en télécharger l'archive sur votre PC et de déposer le contenu de cette dernière dans un sous-répertoire du répertoire servant à émuler le disque dur. Souvenons-nous que dans le Workbench, les seuls répertoires visibles sont ceux dotés d'un fichier ".info". La solution la plus simple consiste donc à créer le répertoire depuis le Workbench - cliquer du bouton droit sur la barre des tâches en haut de l'écran, vous vous souvenez ?

Pour utiliser ASM-One, nous devons :
  • Cliquer ici pour télécharger reqtools.library et copier ce fichier dans le répertoire "Libs". Cette bibliothèque de fonctions est utilisée par ASM-One pour nous proposer une boîte de dialogue qui facilite la navigation dans le système de fichiers.
  • Utiliser une commande du Shell (que nous trouvons dans le répertoire "System") pour assigner "SOURCES:" au répertoire contenant le code et les données (par exemple : "assign SOURCES: DH0:sinescroll"). Pour nous éviter cela, nous pouvons écrire cette ligne dans un fichier user-startup à stocker dans le répertoire "S".
Après avoir lancé ASM-One, allouons un espace de travail en mémoire quelconque (Chip ou Fast) de 100 ko, par exemple. Dans le menu "Assembler", rendons-nous dans "Processor" et sélectionnons "68000", car nous allons programmer pour Amiga 500. Saisissons la commande "R" (Read) pour charger le source.

Pour assembler et tester, deux solutions :
  • Si nous souhaitons déboguer, pressons les touches Amiga (droite) + Maj (droite) + D, la touche Amiga (droite) étant émulée à l'aide de la touche Windows (droite). Nous accédons ainsi au débogueur, d'où nous pouvons exécuter ligne par ligne en pressant la touche de direction vers le bas ou exécuter globalement en pressant les touches Amiga (droite) + R.
  • Si nous ne souhaitons pas déboguer, nous pouvons toujours presser les touches Amiga (droite) + Maj (droite) + A ou saisir la commande "A" (Assemble) pour assembler, puis saisir la commande "J" (Jump) pour lancer l'exécution.
Nous n'utiliserons pas plus de fonctionnalités d'ASM-One par la suite, sinon pour générer un exécutable. C'est que pour écrire du code, nous pouvons nous dispenser d'ASM-One : utilisons un éditeur de texte sous Windows qui permet d'enregistrer un fichier texte encodé en ANSI, comme l'excellent Notepad++, et chargeons le fichier dans ASM-One pour l'assembler et l'exécuter quand nous le souhaitons.

Malgré tout, pour écrire du code aussi bien dans ASM-One que dans Notepad++, car cela peut se révéler ponctuellement pratique, désactivons la sauvegarde des marques qui introduit des caractères spéciaux en tête de fichier. Dans le menu "Project" d'ASM-One, rendons-nous dans "Preferences" et désélectionnons "Save marks".

Notez que pour accéder encore plus vite à l'environnement de développement ainsi mis en place, nous pouvons presser la touche "F12" après avoir chargé ASM-One et alloué un espace de travail en mémoire. Dans l'interface de WinUAE qui apparaît, cliquons sur "Miscellaneous" dans "Host", puis sur "Save State..." afin de sauvegarder l'état. Par la suite, quand nous aurons démarré WinUAE, il nous suffira de charger votre configuration d'Amiga 1200, de cliquer sur "Load State..." pour charger l'état, puis de cliquer sur "OK" pour retrouver l'Amiga 1200 dans l'état en question - nous pouvons même associer le chargement de l'état à celui de la configuration. Pratique !

Se familiariser avec l'assembleur 68000

Le 68000 comporte huit registres de données (D0 à D7) et autant de registres d'adresses (A0 à A7, ce dernier servant toutefois de pointeur de pile). Son jeu d'instructions est tout à fait étendu, mais nous n'en utiliserons qu'un tout petit ensemble, notre malheureuse ignorance pouvant opportunément passer ici pour une bienheureuse simplicité.

Les instructions du 68000 peuvent connaître de multiples variantes. Plutôt qu'un fastidieux passage en revue de toutes les variantes des instructions de notre jeu pourtant limité, ces quelques exemples devraient vous suffire pour vous y retrouver :

Instructions Description
Stockage
MOVE.W $1234,A0 Stocke dans A0 la valeur 16 bits contenue à l'adresse $1234
MOVE.W $1234,D0 Idem avec D0
MOVE.W #$1234,A0 Stocke dans A0 la valeur 16 bits $1234
MOVE.W #$1234,A0 Idem avec D0
LEA $4,A0Stocke dans A0 la valeur 32 bits $4
LEA variable,A0 Stocke dans A0 l'adresse de l'octet précédé du libellé "variable"
LEA 31(A0),A1 Stocke dans A1 le résultat de l'addition de 31 au contenu de A0
LEA 31(A0,DO.W),A1 Stocke dans A1 le résultat de l'addition la valeur 32 bits contenue dans A0, de la valeur 16 bits contenue dans D0 et enfin de 31
MOVE.L variable,A0 Stocke dans A0 la valeur 32 bits qui suit le libellé "variable"
MOVE.L variable,D0 Idem avec D0
CLR.W D0 Stocke dans D0 la valeur 16 bits 0
MOVEQ #-7,D0 Stocke dans D0 la valeur 8 bits -7 étendue sur 32 bits
MOVE.W D0,D1 Stocke dans D1 la valeur 16 bits contenue dans D0
MOVE.B (A0),D0 Stocke dans D0 la valeur 8 bits contenue à l'adresse contenue dans A0
MOVE.L (A0)+,D0 Stocke dans D0 la valeur 32 bits contenue à l'adresse contenue dans A0, puis ajoute 4 à la valeur contenue dans A0 pour adresser la valeur 32 bits suivante
MOVE.B (A0,D0.W)+,D1 Stocke dans D1 la valeur 32 bits contenue à l'adresse résultant de l'addition de l'adresse contenue dans A0 et de la valeur 16 bits contenue dans D0, puis ajoute 1 à la valeur contenue dans A0 pour adresser la valeur 8 bits suivante
Sauts
JMP destination Saute à l'instruction précédée du libellé "destination" sans possibilité de retour
BRA destination Comme JMP (pour faire simple)
BNE destination Comme JMP, mais uniquement si le drapeau Z (zéro) du registre de conditions interne du processeur n'est pas positionné
BEQ destination Comme JMP, mais uniquement si Z est positionné
BGE destination Comme JMP, mais uniquement si Z ou C (retenue) est positionné
BLE destination Comme JMP, mais uniquement si Z est positionné ou C ne l'est pas
BGT destination Comme JMP, mais uniquement si C est positionné
DBF D0,destination Soustrait 1 à valeur contenue dans D0 et saute à l'instruction précédée du libellé "destination" si jamais le résultat n'est pas -1
JSR destination Comme JMP, mais avec possibilité de retour
RTS Saute à l'instruction suivant le dernier JSR exécuté
Calculs
BTST #4,D0 Teste la valeur du bit 4 de valeur contenue dans D0
BCLR #6,D0 Passe le bit 6 de la valeur contenue dans D0 à 0
LSL.W #1,D0 Décale de 1 bit vers la gauche la valeur 16 bits contenue dans D0 (multiplication non signée par 2^1=2)
LSR.B #4,D0 Décale de 4 bits vers la gauche la valeur 8 bits contenue dans D0 (division non signée par 2^4=16)
ASL.W #1,d0 Comme LSL, mais en préservant le bit de signe (multiplication signée par 2^1=2)
ASR.B #4,D0 Comme LSR, mais en préservant le bit de signe (division signée par 2^4=16)
SWAP D0 Intervertit la valeur 16 bits de poids fort (bits 31 à 16) et la valeur 16 bits de poids faible (bits 15 à 0) contenues dans D0
CMP.W D0,D1 Compare la valeur 16 bits contenue dans D1 à la valeur 16 bits contenue dans D0
ADDQ.W 2,D0 Additionne la valeur 3 bits 2 à la valeur 16 bits contenue dans D0
ADD.B D0,D1 Additionne la valeur 8 bits contenue dans D0 à la valeur contenue dans D1
SUB.L D0,D1 Soustrait la valeur 32 bits contenue dans D0 à la valeur contenue dans D1

Ainsi, il est possible de limiter les opérations sur les données à une valeur 8 bits, 16 bits ou 32 bits. Quand un registre est impliqué dans une telle opération, c'est alors l'octet de poids faible, le mot de poids faible ou l'intégralité de la valeur qu'il contient qui est manipulée. Par exemple :

	move.l #$01234567,d0		;D0=$01234567
	moveq #-1,d1				;D1=$FFFFFFFF
	move.b d0,d1				;D1=$FFFFFF67
	move.w d1,d0				;D0=$0123FF67

L'exécution d'une instruction débouche sur une mise à jour des drapeaux du registre de conditions interne du processeur. C'est assez intuitif. Par exemple :

	move.b value,d0		;D0=[valeur]
	beq _valueZero			;Sauter en _valueZero si [valeur] vaut 0
	btst #2,d0				;Tester le bit 2 de [valeur]
	bne _bit2NotZero		;Sauter en _bit2NotZero si le bit vaut 0
	;...
_valueZero:
	;...
_bit2NotZero:
	;...

La seule subtilité que nous nous autoriserons, ce sera de limiter le temps de calcul en recourant à des opérations binaires plutôt qu'à des multiplications ou des divisions. Par exemple :

	move.l #157,d0		;D0=157
	move.l d0,d1		;D1=157
	lsl.w #5,d0		;D0=157*2^5 donc D0=157*32
	lsl.w #3,d1		;D1=157*2^3 donc D1=157*8
	add.w d0,d1		;D0=157*2^5+157*2^3 donc D0=157*40

Nous utiliserons très peu de variables. Ces dernières sont déclarées à la fin du code sur le modèle suivant :

value8:		DC.B $12
	EVEN					;Pour que l'adresse qui suit soit paire
value16:	DC.W $1234
value32:	DC.L $12345678

Comme indiqué, EVEN indique à ASM-One qu'il doit ajouter un octet ("padding") entre $12 et $1234 pour que cette dernière valeur se trouve à une adresse paire. Pourquoi ? Car le 68000 ne peut lire de valeurs 16 bits ou 32 bits qu'à des adresses paires.

Dire au revoir au système d'exploitation

ASM-One permet de générer un exécutable destiné à être exécuté dans le contexte du système d'exploitation. Toutefois, notre code ne va pas s'appuyer sur le système d'exploitation. En fait, nous allons même chercher à l'écarter pour nous accaparer le contrôle total du matériel. Notre seule concession au système d'exploitation sera de ne pas taper n'importe où en mémoire, et donc de lui demander de nous allouer les espaces qui nous sont nécessaires, espaces que nous lui demanderons de libérer à la fin. Entretemps, le système d'exploitation sera complètement court-circuité.

;Empiler les registres

	movem.l d0-d7/a0-a6,-(sp)

;Allouer de la mémoire en Chip mise à 0 pour la liste Copper

	move.l #COPSIZE,d0
	move.l #$10002,d1
	movea.l $4,a6
	jsr -198(a6)
	move.l d0,copperlist

;Idem pour les plans de bits

	move.l #(DISPLAY_DX*DISPLAY_DY)>>3,d0
	move.l #$10002,d1
	movea.l $4,a6
	jsr -198(a6)
	move.l d0,bitplaneA

	move.l #(DISPLAY_DX*DISPLAY_DY)>>3,d0
	move.l #$10002,d1
	movea.l $4,a6
	jsr -198(a6)
	move.l d0,bitplaneB

	move.l #(DISPLAY_DX*DISPLAY_DY)>>3,d0
	move.l #$10002,d1
	movea.l $4,a6
	jsr -198(a6)
	move.l d0,bitplaneC

;Idem pour la police de caractères

	move.l #256<<5,d0
	move.l #$10002,d1
	movea.l $4,a6
	jsr -198(a6)
	move.l d0,font16

;Couper le système

	movea.l $4,a6
	jsr -132(a6)

Remarque de StingRay/Scoopex : "Veillez à appeler LoadView(0) suivi de deux appels à WaitTOF() avant de désactiver les DMA/interruptions et autre, car cela permettra de faire fonctionner votre code sur des écrans non-natifs (par exemple, RTG). C'est plutôt ennuyeux pour les utilisateurs qui ont des cartes graphiques d'avoir à désactiver leur carte pour seulement faire tourner une démo. :)" Faites attention !

AllocMem() et Forbid() sont les deux fonctions de la bibliothèque Exec du système d'exploitation utilisées ici. Pour appeler une fonction d'Exec, nous devons renseigner un certain nombre de registres où cette fonction s'attend à pouvoir lire des paramètres, puis effectuer un saut au bon décalage d'une table d'indirections - une table de JMP -, table dont l'adresse réside à l'adresse $4. La fonction retourne ses résultats dans un certain nombre de registres. Ainsi, AllocMem() retourne l'adresse du bloc de mémoire alloué dans D0.

AllocMem() sert ici à allouer de la mémoire pour la liste Copper, pour trois plans de bits - nous allons faire du triple tampon mémoire -, et pour la police de caractères - nous allons fabriquer un police 16x16 à partir d'une police 8x8. Tous ces espaces sont demandés en mémoire Chip, la seule mémoire à laquelle le Copper et le Blitter ont accès, par opposition à la mémoire Fast.

Il ne suffit pas d'appeler Forbid() pour couper le système d'exploitation. En effet, ce dernier pourrait parfois avoir installé ou permis d'installer du code exécuté lorsqu'un événement matériel survient. Par exemple, quand le faisceau d'électrons a terminé de balayer l'écran, le matériel génère un événement matériel VERTB. Cet événement se traduit par une interruption de niveau 3 du processeur. Le processeur suspend ses travaux pour exécuter le code dont l'adresse est spécifiée à l'entrée numéro 27 de sa table de vecteurs d'interruption - le vecteur d'interruption 27 -, soit l'adresse $6C :

Programmer un défilement sinusoïdal

Si notre code devait utiliser de telles interruptions matérielles, il faudrait d'abord détourner les vecteurs, c'est-à-dire s'assurer qu'ils pointent sur une instruction RTE :

;Détourner les vecteurs d'interruption (code). Les interruptions matérielles génèrent des interruptions
;de niveau 1 à 6 du processeur correspondant aux vecteurs 25 à 30 pointant sur les adresses $64 à $78

	REPT 6
	lea vectors,a1
	REPT 6
	move.l (a0),(a1)+
	move.l #_rte,(a0)+
	ENDR

;...

;Détourner les vecteurs d'interruption (données)

_rte:
	rte

vectors:	BLK.L 6		;Pour s'épargner une allocation mémoire

Pour être encore plus brutal, il serait possible de faire pointer tous les vecteurs d'interruptions du processeur sur une instruction RTE. C'est que le processeur ne dispose pas que des vecteurs des interruptions de niveau N (de 0 à 7), mais de 255 vecteurs, par exemple, le vecteur 5 - le code sur lequel il pointe est appelé en cas de division par zéro. Toutefois, ce serait superflu.

Remarque de StingRay/Scoopex : "Les processeurs 68010+ permettent de relocaliser l'adresse de base du vecteur utilisé par le VBR, ce qui signifie que ce code ne fonctionnera pas sur les machines aussitôt que le VBR aura été relocalisé" Faites attention !

Dans le cas présent, nous n'utiliserons pas ces interruptions, si bien qu'il suffit de les inhiber. Pour cela, nous devons lire dans INTENAR pour récupérer l'état des interruptions activées, sauvegarder cet état, puis inhiber les interruptions en écrivant dans INTENA.
Les registres du matériel

C'est l'occasion de préciser la manière dont notre code va dialoguer avec le matériel. Ce sera via des registres 16 bits résidant à l'adresse $DFF000 plus un décalage pair. Par exemple, INTENAR se trouve à l'adresse $DFF01C. Nous appliquerons une recommandation du manuel pour limiter les erreurs de saisie et faciliter la lecture. Nous stockerons $DFF000 dans un registre d'adresse quelconque - ce sera A5 - et nous adresserons les registres à l'aide de constantes dont les valeurs sont les décalages. Par exemple :

INTENA=$09A

Chaque registre est très spécifique. La signification de chacun de ses bits est détaillée dans l'Amiga Hardware Reference Manual, un vrai manuel, fort bien rédigé par des auteurs maîtrisant complètement leur sujet. Le défi de cet article est de ne pas recopier le contenu de ce manuel indispensable. Il est donc recommandé de se reporter à l'annexe A de ce dernier pour consulter ce qui est dit du registre, puis de continuer la lecture.

Il est aussi possible que des interruptions matérielles soient pendantes. En effet, qu'il dispose de la possibilité d'interrompre le processeur ou non, le matériel signale les raisons pour lesquels il souhaiterait l'interrompre dans INTREQ - qui forme avec INTREQR un couple similaire à celui vu à l'instant. Toujours pour ménager un jour la possibilité d'utiliser des interruptions - ce qui, répétons-le, ne sera pas le cas ici -, nous devons lire dans INTREQR l'état des requêtes d'interruption, puis acquitter ces requêtes en écrivant dans INTREQ pour ne pas les confondre avec celles que le matériel présente par la suite.
Un dernier registre doit être lu : c'est DMACONR. Sur Amiga, les coprocesseurs disposent d'accès directs à la mémoire, ou DMA. Ici encore, c'est quelque chose que nous entendons contrôler pour limiter les accès DMA à ceux qui nous seront utiles. Nous devons donc lire l'état des canaux dans DMACONR pour récupérer l'état des canaux DMA activés, sauvegarder cet état, puis couper les canaux en écrivant dans DMACON - pour commencer, nous les coupons tous.

INTENA, INTREQ et DMACON fonctionnent sur le même modèle : pour inhiber une interruption, acquitter une interruption ou couper un canal DMA, nous devons écrire un mot dont le bit 15 est à 0 et le bit correspondant à l'interruption ou au canal est à 1.

Tout cela conduit à écrire :
  • $7FFF dans INTENA. Nous aurions pu nous contenter de désactiver le bit INTEN, mais si dans le futur vous souhaitez utiliser des interruptions, vous ne voudrez pas avoir à y revenir pour désactiver celles qui ne vous intéressent pas avant de réactiver INTEN et celles qui vous intéressent.
  • $7FFF dans INTREQ. Ce registre contient lui aussi un bit INTEN.
  • $07FF dans DMACON. La remarque qui vaut pour INTEN vaut ici pour le bit DMAEN. Par paresse, nous aurions pu écrire $7FFF, mais les bits 11 à 14 ne servent à rien en écriture.
;Couper les interruptions hardware et les DMA

	lea $dff000,a5
	move.w INTENAR(a5),intena
	move.w #$7FFF,INTENA(a5)
	move.w INTREQR(a5),intreq
	move.w #$7FFF,INTREQ(a5)
	move.w DMACONR(a5),dmacon
	move.w #$07FF,DMACON(a5)

Tout cela est-il bien propre ? Certainement pas. Il n'y a pas de manière propre pour couper rapidement le système d'exploitation. C'est aussi pour cela qu'on parle de "metal bashing". Enfin, voilà : nous avons désormais le contrôle total du matériel. Commençons par configurer l'affichage.

Configurer l'affichage

Dans un précédent article, nous avons présenté les coprocesseurs graphiques de l'Amiga, dont le Copper, qui contrôle l'affichage. Nous avons expliqué que le Copper se programme via une liste d'instructions, la liste Copper, à fournir sous la forme d'une séquence de codes opérations, des longs (32 bits) écrits en hexadécimal. Le Copper comprend trois instructions (WAIT, MOVE et SKIP), mais c'est uniquement MOVE que nous utiliserons pour l'heure pour demander au Copper d'écrire certaines valeurs dans les registres qui contrôlent l'affichage.

Justement, nous avions aussi expliqué comment cet affichage fonctionne. Il est à base de plans de bits superposés tels que la lecture du bit aux coordonnées (x, y) dans le plan de bits N donne le bit N-1 de l'index de la couleur du pixel correspondant dans la palette, etc. Le nombre de couleurs est donc déterminé par le nombre de plans de bits : N plans de bits pour 2^N couleurs. Ici, nous allons afficher un plan de bits, donc en deux couleurs - couleur de fond comprise.

Pour nous y retrouver facilement, définissons quelques constantes :

DISPLAY_DEPTH=1
DISPLAY_DX=320
DISPLAY_DY=256
DISPLAY_X=$81
DISPLAY_Y=$2C

Les paramètres d'affichage suivants doivent être spécifiés :
  • La résolution. Les pixels seront affichés en basse résolution, ce qui ne requiert aucun positionnement de bit particulier dans un quelconque registre.
  • Le nombre de plans de bits. Les bits BPUx de BPLCON0 doivent donner ce nombre, c'est-à-dire DISPLAY_DEPTH.
  • L'affichage en couleurs. Le bit COLOR de BPLCON0 doit être positionné.
  • La surface vidéo de pixels à balayer. DIWSTRT doit contenir les coordonnées de son angle supérieur gauche, et DIWSTOP celles de son angle inférieur droit. Ces coordonnées sont exprimées en pixels dans un repère très particulier, celui du tube cathodique. Généralement, la surface démarre en ($81, $2C) et s'étend sur DISPLAY_DX pixels horizontalement et DISPLAY_DY pixels verticalement. Noter qu'en raison du nombre de bits limités dans DIWSTOP, nous devons soustraire 256 aux coordonnées qu'on y écrit.
  • Les coordonnées horizontale à partir desquelles commencer et cesser de lire les données des pixels à afficher. Ces abscisses sont exprimées dans le même repère que celles des angles de la surface vidéo dans DIWSTRT et DIWSTOP. Le matériel lit les données des pixels par paquets de 16 pixels. Par ailleurs, il s'écoule un peu de temps entre le moment où le hardware commencer à lire ces données et celui où les pixels correspondants commencent à être affichés. C'est pourquoi, sous condition que DISPLAY_DX soit multiple de 16, la lecture des données doit débuter en (DISPLAY_X-17)>>1.
Le tracé des pixels

Ce qu'il faut comprendre, c'est qu'une fois la résolution décidée - basse ou haute résolution horizontale, avec ou sans entrelacement vertical - le débit du faisceau d'électrons est constant. En effet, il balaie toujours toute la surface du tube cathodique, traçant les pixels en frappant avec plus ou moins d'intensité les régions rouge, verte et bleue des luminophores sur une certaine longueur, une succession de luminophores formant un pixel. Tout ce que l'Amiga peut faire, c'est de demander au faisceau de ne frapper que les luminophores d'une certaine surface du tube, en frappant avec différentes intensités les points rouge, vert et bleu qui composent ces luminophores.

DIWSTRT et DIWSTOP permettent de contrôler la position et les dimensions de la surface en question. DDFSTRT et DDFSTOP permettent de contrôler les positions à partir desquelles l'Amiga décide de continuer et de cesser de lire les données des plans de bits pour en déduire les intensités de rouge, de vert et de bleu à communiquer au faisceau d'électrons.

Autrement dit, il ne faut pas se faire d'illusions : il n'est pas possible d'afficher toute une ligne d'un plan de bits sur une ligne plus ou moins large de l'écran et de procéder pareillement en vertical - une forme de zoom vidéo. Une fois décidées via des bits de BPLCON0, les résolutions horizontale et verticale sont bel et bien figées : quoiqu'il arrive le faisceau d'électron trace un pixel en 140 ns et il met 1/50e de seconde pour tracer tout l'écran sur une certaine largeur et une certaine hauteur.

L'Amiga interagit avec un peintre qui balaie toujours la même surface à la même vitesse, acceptant seulement qu'on modifie à tout instant - enfin, au minimum le temps qu'il trace un pixel - ce qu'il puise dans ses pots de peinture rouge, verte et bleue.
Tous les autres paramètres doivent être désactivés. Par exemple, il n'est pas question de retarder l'affichage des plans de bits impairs de quelques pixels horizontalement, si bien que les bits PF1Hx de BPLCON1 sont passés à 0. Ou encore, il n'est pas question d'afficher en haute résolution, si bien que le bit HRES de BPLCON0 est passé à 0.

Ce qui donne :

	move.w #DIWSTRT,(a0)+
	move.w #(DISPLAY_Y<<8)!DISPLAY_X,(a0)+
	move.w #DIWSTOP,(a0)+
	move.w #((DISPLAY_Y+DISPLAY_DY-256)<<8)!(DISPLAY_X+DISPLAY_DX-256),(a0)+
	move.w #BPLCON0,(a0)+
	move.w #(DISPLAY_DEPTH<<12)!$0200,(a0)+
	move.w #BPLCON1,(a0)+
	move.w #0,(a0)+
	move.w #BPLCON2,(a0)+
	move.w #0,(a0)+
	move.w #DDFSTRT,(a0)+
	move.w #((DISPLAY_X-17)>>1)&$00FC,(a0)+
	move.w #DDFSTOP,(a0)+
	move.w #((DISPLAY_X-17+(((DISPLAY_DX>>4)-1)<alt;4))>>1)&$00FC,(a0)+

Les données des pixels résident dans le plan de bits. Nous devons donc préciser où ils se trouvent en mémoire en écrivant leurs adresses dans des couples de registres BPLxPTH (16 bits de poids fort de l'adresse) et BPLxPTL (16 bits de poids fort de l'adresse) :

	move.l bitplaneA,d0
	move.w #BPL1PTL,(a0)+
	move.w d0,(a0)+
	swap d0
	move.w #BPL1PTH,(a0)+
	move.w d0,(a0)+

Le matériel incrémente le contenu de BPLxPTH et BPLxPTL tandis qu'il lit les données du plan de bits lors de l'affichage d'une ligne. Arrivé à la fin de cette ligne, le matériel ajoute un certain nombre d'octets à ces registres pour adresser les premiers pixels de la ligne suivante : c'est le modulo. BPL1MOD permet de spécifier le modulo des plans de bits impairs, et BPL2MOD celui des plans de bits pairs. Pour l'heure, seul BPL1MOD est utilisé car il n'y a qu'un plan de bits, le plan de bits 1 qui est donc un plan de bits impair. Ce modulo est à 0, car le plan de bits fait DISPLAY_DX pixels de large et nous souhaitons afficher DISPLAY_DX pixels par ligne :

	move.w #BPL1MOD,(a0)+
	move.w #0,(a0)+

Ayant récupéré les bits du pixel courant dans les plans de bits, le matériel peut en déduire l'indice dans une palette de la couleur du pixel en question. Nous spécifions les deux couleurs de notre palette en écrivant leurs valeurs dans COLOR00 et COLOR01 :

	move.w #COLOR00,(a0)+
	move.w #$0000,(a0)+
	move.w #COLOR01,(a0)+
	move.w #SCROLL_COLOR,(a0)+

L'Amiga émulé est un Amiga 1200 doté du jeu de puces AGA, mais le code que nous écrivons est destiné à fonctionner sur un Amiga 500 doté du jeu de puces OCS. En ce qui concerne la vidéo, la compatibilité ascendante de l'AGA avec l'OCS est presque parfaite. Nous devons simplement ne pas oublier d'écrire 0 dans FMODE :

	move.w #FMODE,(a0)+
	move.w #$0000,(a0)+

Enfin, le Copper détecte la fin de la liste Copper quand il rencontre une instruction WAIT impossible :

	move.l #$FFFFFFFE,(a0)

Nous reviendrons sur l'écriture d'un WAIT lorsque nous chercherons à rajouter les effets d'ombre et de miroir.

La liste Copper étant rédigée, nous pouvons demander au Copper de l'exécuter. Cela s'effectue en deux temps :
  • Fournir l'adresse de la liste Copper via COP1LCH et COP1LCL, ce qui peut s'effectuer d'un MOVE.L car comme BPLxPTH et BPLxPTL, ces registres sont contigus.
  • Écrire n'importe quelle valeur dans COPJMP1, car c'est un strobe, c'est-à-dire un registre qui déclenche une action dès qu'on en modifie la valeur.
	move.l copperlist,COP1LCH(a5)
	clr.w COPJMP1(a5)

Encore faut-il que le Copper puisse accéder à la mémoire par DMA. C'est l'occasion de rouvrir son canal DMA, mais aussi celui permettant au matériel de lire les données des plans de bits et celui du Blitter. On en profite pour protéger les cycles d'accès à la mémoire du Blitter pour qu'il ne se les fasse pas voler par le processeur (l'Amiga Hardware Reference Manual ne donne pas beaucoup d'explications sur ce sujet...) :

	move.w #$83C0,DMACON(a5)	;DMAEN=1, BPLEN=1, COPEN=1, BLTEN=1

Le matériel étant configuré, nous pouvons maintenant rentrer dans le code qui génère ce qu'il doit afficher...


[Retour en haut] / [Retour aux articles] [Article suivant]