Revenir au plan du site
Structure de l'écran d'un Amstrad CPC/Plus.
Nous allons voir comment il est possible d'afficher un point à l'écran grâce à ses coordonnées. L'écran par défaut du CPC est en mode 1 et en 320 x 200
Il n'est hélas pas possible de considérer l'écran du CPC comme un tableau de 320 colonnes et 200 lignes. Déjà parce que le CPC stocke plusieurs pixels par octet, mais
aussi car la structure écran du CPC n'est pas totalement linéaire.
Le générateur d'adresse utilise des blocs de 8 lignes, probablement hérité du mode caractère qui n'existe pas sur CPC (contrairement au Spectrum ou au commodore 64).
Chaque ligne d'adresse à l'intérieur du bloc est limitée à 2048 octets, c'est pourquoi à chaque fois que l'on change de ligne, l'adresse augmente de 2048 octets. Je vous perds? Regardez le tableau ci-dessous.
ligne 0 (bloc 0 ligne 0) : adresse=#C000
ligne 1 (bloc 1 ligne 0) : adresse=#C800 ; +2048
ligne 2 (bloc 2 ligne 0) : adresse=#D000 ; +2048
ligne 3 (bloc 3 ligne 0) : adresse=#D800 ; +2048
ligne 4 (bloc 4 ligne 0) : adresse=#E000 ; +2048
ligne 5 (bloc 5 ligne 0) : adresse=#E800 ; +2048
ligne 6 (bloc 6 ligne 0) : adresse=#F000 ; +2048
ligne 7 (bloc 7 ligne 0) : adresse=#F800 ; +2048
ligne 8 (bloc 0 ligne 1) : adresse=#C050 ; -16384 + largeur écran (80 ou #50)
ligne 9 (bloc 1 ligne 1) : adresse=#C850 ; +2048
...
|
Première remarque : L'adresse générée par le CRTC reste cloisonnée dans sa page de 16K.
Deuxième remarque, la mémoire serait linéaire si il n'y avait qu'un seul bloc au lieu de 8, mais ça, on ne peut pas le changer. Regardons quand même ce qui se passe quand on parcourt l'écran de 8 lignes en lignes.
ligne 00 (bloc 0 ligne 0) : adresse=#C000 ;
ligne 08 (bloc 0 ligne 1) : adresse=#C050 ; +largeur en octet d'une ligne
ligne 16 (bloc 0 ligne 2) : adresse=#C0A0 ; +largeur en octet d'une ligne
ligne 24 (bloc 0 ligne 3) : adresse=#C0F0 ; +largeur en octet d'une ligne
ligne 32 (bloc 0 ligne 4) : adresse=#C140 ; +largeur en octet d'une ligne
...
|
On devine quelle formule nous permettrait de calculer l'adresse de début de n'importe quelle ligne écran :
adresse = #C000 + largeur x (numeroLigne / 8) + (numeroLigne % 8) x #800 + colonne
Heureusement pour nous, 8 est une valeur remarquable (2^3) donc décaler 3 fois notre numéro de ligne le divisera par 8. Pour le modulo, nous pouvons utiliser un simple AND, là encore car 8 est une valeur remarquable (voir définition de l'instruction AND).
La multiplication par #800 peut se faire en décalant 12 fois vers la gauche. Et comme on va décaler au moins du nombre de bits d'un octet, on peut précharger la valeur dans le poids fort pour réaliser un premier décalage immédiat de 8 bits correspondant à une multiplication par 256.
Enfin, dernières précisions, ayant 4 pixels par octet dans le mode 1, il faut diviser la colonne par 4 avant de l'ajouter! La largeur standard d'un écran est de 80 octets soit 64+16!
Note : À cause des blocs qui ne se mélangent pas, notre écran 320 x 200 ne fait pas 16000 octets mais réellement 16336. En effet, nos 25 lignes de chaque bloc occupent 25 x 80 = 2000 octets sur les 2048 octets d'un bloc. Les 48 derniers octets de chaque bloc sont inutilisés pour l'affichage d'un écran standard. La taille totale de notre écran est donc de 16384-48 octets soit 16336 octets.
; BC=coordonnée X (0-319)
; E=coordonnée Y (0-199)
ld a,e ; on copie le Y dans A
srl a : srl a : srl a ; décaler 3 fois à droite = division par 8 (résultat entre 0 et 24)
ld h,0 : ld l,a ; pour multiplier par 64 on va avoir besoin d'un registre 16 bits (24 x 64 = 1536)
ld a,e ; on copie à nouveau le Y dans A
add hl,hl : add hl,hl : add hl,hl : add hl,hl : ld de,hl ; HL = DE = Y x 16
add hl,hl : add hl,hl ; HL = Y x 64
add hl,de ; HL = Y x 64 + Y x 16 = Y x 80
and 7 ; A = Y modulo 8
; on doit multiplier le numéro de bloc par 2048 / #800
ld d,a : ld e,0 ; DE = A x 256
ex hl,de ; permuter HL et DE
add hl,hl : add hl,hl : add hl,hl ; HL = A x 2048
add hl,de ; HL = (Y % 8) x 2048 + (Y / 8) x 80
srl bc : srl bc ; diviser le X par 4 pour avoir l'octet en mode 1
add hl,bc
ret ; HL=adresse écran aux coordonnées X/Y données
|
Note : L'instruction
SRL BC n'existe pas, c'est une combinaison de
SRL B et RR C mais elle est plus compréhensible et facile à lire.
On les affiche nos pixels oui ou non?
Ok, ok, voici le source complet, assemblable par rasm et exécutable dans n'importe quel émulateur.
BUILDSNA : BANKSET 0
ORG #100
RUN #100
debut
ld bc,160 : ld e,100 ; en plein milieu de l'écran
call CalculeAdressePixel
ld (hl),#FF ; remplir l'octet
jr $ ; boucle infinie
CalculeAdressePixel
; BC=coordonnée X (0-319)
; E=coordonnée Y (0-199)
ld a,e ; on copie le Y dans A
srl a : srl a : srl a ; décaler 3 fois à droite = division par 8 (résultat entre 0 et 24)
ld h,0 : ld l,a ; pour multiplier par 64 on va avoir besoin d'un registre 16 bits (24 x 64 = 1536)
ld a,e ; on copie à nouveau le Y dans A
add hl,hl : add hl,hl : add hl,hl : add hl,hl : ld de,hl ; HL = DE = Y x 16
add hl,hl : add hl,hl ; HL = Y x 64
add hl,de ; HL = Y x 64 + Y x 16 = Y x 80
and 7 ; A = Y modulo 8
; on doit multiplier le numéro de bloc par 2048 / #800
ld d,a : ld e,0 ; DE = A x 256
ex hl,de ; permuter HL et DE
add hl,hl : add hl,hl : add hl,hl ; HL = A x 2048
add hl,de ; HL = (Y % 8) x 2048 + (Y / 8) x 80
srl bc : srl bc ; diviser le X par 4 pour avoir l'octet en mode 1
add hl,bc
ld bc,#C000 ; adresse de la page écran
add hl,bc
ret ; HL=adresse écran aux coordonnées X/Y données
|
Et là, sous vos yeux ébahis, vous avez 4 pixels rouges au milieu de l'écran... Et mon pixel alors il est où? Ben quelque part sur le pâté...
Afficher un point à l'écran
Pour afficher un point, il va falloir tenir compte des subtilités d'encodage des pixels. Les bits ont une organisation très "électronique", c'est à dire que leur stockage entremêlé est destiné à simplifier la logique interne du Gate Array, oups! Il n'y a qu'en mode 2 que la représentation des bits semble naturelle pour nous. Allez, je vous présente l'organisation pour tous les modes graphiques.
La haute-résolution (ou mode 2) est la plus simple. Sa résolution système est de 640 x 200 en 2 couleurs. Comme il n'y a que deux couleurs possibles, chaque pixel peut être allumé (encre 1) ou éteint (encre 0).
Pour chaque octet, 8 pixels sont définis, le bit 0 correspond à l'encre du pixel 0
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Mémoire | P7 | P6 | P5 | P4 | P3 | P2 | P1 | P0 |
La moyenne résolution (le mode par défaut d'un snapShot Rasm et du système quand on démarre un CPC/Plus) est 320 x 200 avec 4 couleurs possibles par pixel. 4 couleurs nécessite 2 bits, c'est pour ça qu'il n'y a plus que 4 pixels par octet, l'organisation s'entremêle un peu. Le Gate Array affiche les pixels P3, P2, P1, P0 de gauche à droite. Tous les bits les moins significatifs sont dans le quartet... ...supérieur! Attention! Et les bits les plus significatifs sont dans le quartet inférieur.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Mémoire | P3.0 | P2.0 | P1.0 | P0.0 | P3.1 | P2.1 | P1.1 | P0.1 |
La basse résolution n'offre qu'une résolution de 160 x 200 en standard, avec 16 couleurs possibles par pixel. 16 couleurs requiert 4 bits par pixel, donc seulement 2 pixels par octet. Attention, cette fois on mélange tout! Notez qu'avec cette organisation comme celle du mode 1, dès lors que vous avez la valeur d'un pixel pour le pixel le plus à gauche ou à droite, vous n'avez qu'à faire un décalage pour avoir la valeur des pixels situés à côté.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Mémoire | P1.0 | P0.0 | P1.2 | P0.2 | P1.1 | P0.1 | P1.3 | P0.3 |
On modifie notre routine pour tenir compte du X et afficher un seul pixel? Les modifications sont en gras
BUILDSNA : BANKSET 0
ORG #100
RUN #100
debut
ld bc,160 : ld e,100 ; en plein milieu de l'écran
call CalculeAdressePixel
ld (hl),c ; écrire le pixel
jr $ ; boucle infinie
CalculeAdressePixel
; BC=coordonnée X (0-319)
; E=coordonnée Y (0-199)
ld a,e ; on copie le Y dans A
srl a : srl a : srl a ; décaler 3 fois à droite = division par 8 (résultat entre 0 et 24)
ld h,0 : ld l,a ; pour multiplier par 64 on va avoir besoin d'un registre 16 bits (24 x 64 = 1536)
ld a,e ; on copie à nouveau le Y dans A
add hl,hl : add hl,hl : add hl,hl : add hl,hl : ld de,hl ; HL = DE = Y x 16
add hl,hl : add hl,hl ; HL = Y x 64
add hl,de ; HL = Y x 64 + Y x 16 = Y x 80
and 7 ; A = Y modulo 8
; on doit multiplier le numéro de bloc par 2048 / #800
ld d,a : ld e,0 ; DE = A x 256
ex hl,de ; permuter HL et DE
add hl,hl : add hl,hl : add hl,hl ; HL = A x 2048
add hl,de ; HL = (Y % 8) x 2048 + (Y / 8) x 80
ld a,c ; on sauvegarde le X avant de diviser par 4
srl bc : srl bc ; diviser le X par 4 pour avoir l'octet en mode 1
add hl,bc
ld bc,#C000 ; adresse de la page écran
add hl,bc
ld c,%10000000 ; encre 1 pour le pixel mode 1 le plus à gauche dans l'octet
and 3 ; avec le modulo 4 on va savoir quel est le pixel en partant de la gauche
jr z,.noShift
.Shift
srl c
dec a
jr nz,.Shift
.noShift
ret ; HL=adresse écran aux coordonnées X/Y données et C est le pixel d'encre 1
|
Et voilà, notre routine est "parfaite"!
Et si on l'améliorait quand même? La suite
[ici]