Groupe 12 — Blitter Officiel
12,1 Blitter Busy · 12,2 Simple Blit Copy · 12,3 Complex Blit Copy · 12,4 Blit Image
Le blitter copie des blocs de mémoire côté RP2040, sans passer par le 65C02. Une adresse y a 24 bits : une page et 16 bits d'adresse. Pages : $00 la mémoire du 6502, $80 et $81 la mémoire vidéo (en mode 0, un octet par pixel, 320 par ligne : le pixel (x, y) est à y × 320 + x, au-delà de 65535 en page $81), $90 la mémoire graphique, $A0 et suivantes les banques de mémoire en flash (lecture seule).
Les exemples copient vers l'écran effacé, puis relisent les pixels avec 5,33.
12,1 Blitter Busy Officiel
Dit si le blitter est en train de copier.
Entrées : aucune.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | non nul si une copie est en cours ; toujours 0 aujourd'hui |
Le firmware fait chaque copie en entier avant de rendre la main : l'appel rend toujours 0, avant comme juste après une copie. Il reste utile pour un programme qui doit aussi tourner sur un firmware où la copie serait faite en tâche de fond. NeoBASIC n'a pas de mot-clé pour cet appel.
Texte de la documentation du firmware (anglais)
Returns a non zero value in Parameter:0 if the blitter/DMA system is currently transferring data, used to check availability and transfer completion.
C (llvm-mos)
/* Savoir si le blitter est occupé (groupe 12, fonction 1). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>
/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
ControlPort.function = fonction;
ControlPort.command = groupe;
while (ControlPort.command) /* attendre la fin */
;
}
static void libre(void) { /* attendre que la boîte aux lettres soit libre */
while (ControlPort.command)
;
}
/* 12,2 : copie de n octets de page:adresse vers page:adresse ; rend l'indicateur d'erreur. */
static uint8_t copier(uint8_t ps, uint16_t as, uint8_t pd, uint16_t ad, uint16_t n) {
libre();
ControlPort.params[0] = ps; /* P0 : page source ($00 RAM du 6502, $80-$81 vidéo, $90 graphique) */
ControlPort.params[1] = as & 0xFF; /* P1-P2 : adresse source */
ControlPort.params[2] = as >> 8;
ControlPort.params[3] = pd; /* P3 : page destination */
ControlPort.params[4] = ad & 0xFF; /* P4-P5 : adresse destination */
ControlPort.params[5] = ad >> 8;
ControlPort.params[6] = n & 0xFF; /* P6-P7 : nombre d'octets */
ControlPort.params[7] = n >> 8;
api(12, 2);
return ControlPort.error;
}
static uint8_t occupe(void) { /* Blitter Busy */
libre();
api(12, 1);
return ControlPort.params[0];
}
static char src[] = "ABCD", dst[5];
int main(void) {
uint8_t avant = occupe();
copier(0x00, (uint16_t)src, 0x00, (uint16_t)dst, 4);
uint8_t apres = occupe(); /* la copie est déjà finie */
printf("Occupe avant : %u, apres une copie : %u\n", avant, apres);
return 0;
}Occupe avant : 0, apres une copie : 0Mad Pascal
// Savoir si le blitter est occupé (groupe 12, fonction 1).
program blit1;
uses crt, neo6502;
var src, dst: array[0..3] of byte;
avant, apres: boolean;
begin
avant := NeoBlitterBusy; // Blitter Busy
NeoBlitterCopy(word(@src), word(@dst), 4); // 12,2 : page $00 (RAM du 6502) des deux côtés
apres := NeoBlitterBusy; // la copie est déjà finie
writeln('Occupe avant : ', ord(avant), ', apres une copie : ', ord(apres));
end.Occupe avant : 0, apres une copie : 0NeoBASIC
10 ' Savoir si le blitter est occupe (groupe 12, fonction 1)
20 gosub 900 : avant = o
30 s = alloc(4) : d = alloc(4) : ps = 0 : as = s : pd = 0 : ad = d : n = 4 : gosub 1000
40 gosub 900 : apres = o : ' la copie est deja finie
50 print "Occupe avant : ";avant;", apres une copie : ";apres
60 end
900 ' Blitter Busy : o
910 while peek($ff00) : wend : poke $ff01, 1 : poke $ff00, 12
920 while peek($ff00) : wend : o = peek($ff04)
930 return
1000 ' 12,2 : copie de n octets de ps:as vers pd:ad ; erreur dans e
1010 while peek($ff00) : wend
1020 poke $ff04, ps : doke $ff05, as : poke $ff07, pd : doke $ff08, ad : doke $ff0a, n
1030 poke $ff01, 2 : poke $ff00, 12
1040 while peek($ff00) : wend : e = peek($ff02)
1050 returnOccupe avant : 0, apres une copie : 0Assembleur 65C02 (64tass)
; Savoir si le blitter est occupé (groupe 12, fonction 1).
.cpu "65c02"
* = $0800
ControlPort = $FF00 ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage = $FFF4 ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1 ; affiche le caractère de A
start: lda #<occupe
ldx #>occupe
jsr envoi
lda ControlPort+4
sta avant
lda #<copie
ldx #>copie
jsr envoi
lda #<occupe
ldx #>occupe
jsr envoi
lda ControlPort+4 ; la copie est déjà finie
sta apres
lda #<t0
ldx #>t0
jsr ecrire
lda avant
jsr decimal
lda #<t1
ldx #>t1
jsr ecrire
lda apres
jsr decimal
lda #13
jsr WriteCharacter
rts
occupe: .byte 12, 1, 0 ; 12,1 : Blitter Busy
copie: .byte 12, 2, 8, 0 ; 12,2 : de la page $00 (RAM du 6502)...
.word src
.byte 0 ; ... vers la page $00
.word dst, 4
src: .text "ABCD"
dst: .fill 4
t0: .text "Occupe avant : ", 0
t1: .text ", apres une copie : ", 0
avant: .byte 0
apres: .byte 0
; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi: sta blk
stx blk+1
jsr WaitMessage
ldy #2
lda (blk),y ; nombre de paramètres
clc
adc #3
sta fin
ldy #3
_copie: cpy fin
beq _lance
lda (blk),y
sta ControlPort+1,y ; octet 3 du bloc -> P0 ($FF04)...
iny
bra _copie
_lance: ldy #1
lda (blk),y
tax
lda (blk)
jmp appel
; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
stx ptr+1
ldy #0
_e: lda (ptr),y
beq _f
jsr WriteCharacter
iny
bne _e
_f: rts
; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
phx
phy
ldx #0
ldy #100
jsr _chiffre
ldy #10
jsr _chiffre
ora #'0'
jsr WriteCharacter
ply
plx
rts
_chiffre:
sty diviseur
ldy #0
_div: cmp diviseur
bcc _fini
sbc diviseur
iny
bne _div
_fini: pha
tya
bne _ecrit
cpx #0
beq _saute
_ecrit: ora #'0'
jsr WriteCharacter
ldx #1
_saute: pla
rts
; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel: stx ControlPort+1
sta ControlPort ; écrit en dernier : lance l'appel
jmp WaitMessage
ptr = $80
blk = $82
diviseur: .byte 0
fin: .byte 0Occupe avant : 0, apres une copie : 012,2 Simple Blit Copy Officiel
Copie une suite d'octets d'une page de mémoire à une autre (mémoire du 6502, vidéo, graphique).
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | page source |
P1-P2 | addr | adresse source dans la page |
P3 | uint8 | page destination |
P4-P5 | addr | adresse destination dans la page |
P6-P7 | uint16 | nombre d'octets |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 si le début ou la fin d'une des deux zones n'existe pas, ou si la destination est une banque |
Les zones peuvent se chevaucher. Une zone ne passe pas d'une page à l'autre : en mémoire vidéo, 65536 octets en page $80, le reste en page $81. L'exemple copie un texte en mémoire du 6502, huit pixels de couleur 5 à l'écran (ligne 100, x 100 à 107 ; le pixel suivant reste à 0), puis montre le refus d'une page qui n'existe pas. NeoBASIC n'a pas de mot-clé pour cet appel.
Texte de la documentation du firmware (anglais)
Copy Parameters:6,7 bytes of internal memory from Parameter:0:Parameters:1,2 to Parameter:3:Parameters:4,5. Sets error flag if the transfer is not possible (e.g. illegal write addresses). The upper 8 bits of the address are : 6502 RAM (00) VideoRAM (80,81) Graphics RAM(90)
C (llvm-mos)
/* Copier des octets en mémoire et vers l'écran (groupe 12, fonction 2). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>
/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
ControlPort.function = fonction;
ControlPort.command = groupe;
while (ControlPort.command) /* attendre la fin */
;
}
static void libre(void) { /* attendre que la boîte aux lettres soit libre */
while (ControlPort.command)
;
}
/* 12,2 : copie de n octets de page:adresse vers page:adresse ; rend l'indicateur d'erreur. */
static uint8_t copier(uint8_t ps, uint16_t as, uint8_t pd, uint16_t ad, uint16_t n) {
libre();
ControlPort.params[0] = ps; /* P0 : page source ($00 RAM du 6502, $80-$81 vidéo, $90 graphique) */
ControlPort.params[1] = as & 0xFF; /* P1-P2 : adresse source */
ControlPort.params[2] = as >> 8;
ControlPort.params[3] = pd; /* P3 : page destination */
ControlPort.params[4] = ad & 0xFF; /* P4-P5 : adresse destination */
ControlPort.params[5] = ad >> 8;
ControlPort.params[6] = n & 0xFF; /* P6-P7 : nombre d'octets */
ControlPort.params[7] = n >> 8;
api(12, 2);
return ControlPort.error;
}
static char texte[] = "ABCDEFGH", copie[9];
static uint8_t couleur[8] = {5, 5, 5, 5, 5, 5, 5, 5};
int main(void) {
putchar(12); /* écran effacé : on relit des pixels */
copier(0x00, (uint16_t)texte, 0x00, (uint16_t)copie, 8); /* RAM du 6502 → RAM du 6502 */
copier(0x00, (uint16_t)couleur, 0x80, 100 * 320 + 100, 8); /* → écran, ligne 100, x 100 à 107 */
uint8_t a = neo_graphics_read_pixel(100, 100), b = neo_graphics_read_pixel(107, 100);
uint8_t c = neo_graphics_read_pixel(108, 100);
uint8_t e = copier(0x70, 0, 0x00, (uint16_t)copie, 8); /* page $70 : n'existe pas */
printf("En memoire : %s\n", copie);
printf("A l'ecran : %u %u, apres : %u\n", a, b, c);
printf("Page $70 : erreur %u\n", e);
return 0;
}En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1Mad Pascal
// Copier des octets en mémoire et vers l'écran (groupe 12, fonction 2).
program blit2;
uses crt, neo6502;
var texte, copie: string[8];
couleur: array[0..7] of byte;
a, b, c, e: byte;
begin
write(chr(12)); // écran effacé : on relit des pixels
texte := 'ABCDEFGH';
fillchar(@couleur, 8, 5);
copie := '';
NeoBlitterCopy(word(@texte) + 1, word(@copie) + 1, 8); // Simple Blit Copy : RAM du 6502 → RAM du 6502
copie[0] := chr(8);
NeoBlitterCopy(word(@couleur), $800000 + 100 * 320 + 100, 8); // → écran (page $80), ligne 100, x 100 à 107
a := NeoGetPixel(100, 100);
b := NeoGetPixel(107, 100);
c := NeoGetPixel(108, 100);
NeoBlitterCopy($700000, word(@copie) + 1, 8); // page $70 : n'existe pas
NeoWaitMessage;
e := NeoMessage.error;
writeln('En memoire : ', copie);
writeln('A l''ecran : ', a, ' ', b, ', apres : ', c);
writeln('Page $70 : erreur ', e);
end.En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1NeoBASIC
10 ' Copier des octets en memoire et vers l'ecran (groupe 12, fonction 2)
20 cls : ' ecran efface : on relit des pixels
30 t = alloc(8) : c = alloc(9) : k = alloc(8)
40 for i = 0 to 7 : poke t + i, 65 + i : poke k + i, 5 : next i : ' ABCDEFGH ; 8 octets de couleur 5
50 ps = 0 : as = t : pd = 0 : ad = c : n = 8 : gosub 1000 : ' Simple Blit Copy : RAM du 6502 vers RAM du 6502
60 ps = 0 : as = k : pd = $80 : ad = 32100 : gosub 1000 : ' vers l'ecran (page $80) : 100 * 320 + 100, ligne 100, x 100 a 107
70 a = point(100,100) : b = point(107,100) : d = point(108,100)
80 ps = $70 : as = 0 : pd = 0 : ad = c : gosub 1000 : ' page $70 : n'existe pas
90 s$ = "" : for i = 0 to 7 : s$ = s$ + chr$(peek(c + i)) : next i
100 print "En memoire : ";s$
110 print "A l'ecran : ";a;" ";b;", apres : ";d
120 print "Page $70 : erreur ";e
130 end
1000 ' 12,2 : copie de n octets de ps:as vers pd:ad ; erreur dans e
1010 while peek($ff00) : wend
1020 poke $ff04, ps : doke $ff05, as : poke $ff07, pd : doke $ff08, ad : doke $ff0a, n
1030 poke $ff01, 2 : poke $ff00, 12
1040 while peek($ff00) : wend : e = peek($ff02)
1050 returnEn memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1Assembleur 65C02 (64tass)
; Copier des octets en mémoire et vers l'écran (groupe 12, fonction 2).
.cpu "65c02"
* = $0800
ControlPort = $FF00 ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage = $FFF4 ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1 ; affiche le caractère de A
start: lda #12 ; écran effacé : on relit des pixels
jsr WriteCharacter
lda #<c1
ldx #>c1
jsr envoi
lda #<c2
ldx #>c2
jsr envoi
lda #100
ldy #100
jsr pixel
sta a
lda #107
ldy #100
jsr pixel
sta b
lda #108
ldy #100
jsr pixel
sta c
lda #<c3
ldx #>c3
jsr envoi
lda ControlPort+2
sta e
lda #<t0
ldx #>t0
jsr ecrire
lda #<copie
ldx #>copie
jsr ecrire
lda #13
jsr WriteCharacter
lda #<t1
ldx #>t1
jsr ecrire
lda a
jsr decimal
lda #' '
jsr WriteCharacter
lda b
jsr decimal
lda #<t2
ldx #>t2
jsr ecrire
lda c
jsr decimal
lda #13
jsr WriteCharacter
lda #<t3
ldx #>t3
jsr ecrire
lda e
jsr decimal
lda #13
jsr WriteCharacter
rts
; Couleur du fond (5,33) en (A, Y), X et Y inférieurs à 256, rendue dans A.
pixel: pha
jsr WaitMessage
pla
sta ControlPort+4 ; P0-P1 : X
stz ControlPort+5
sty ControlPort+6 ; P2-P3 : Y
stz ControlPort+7
lda #5
ldx #33 ; 5,33 : Read Pixel
jsr appel
lda ControlPort+4
rts
c1: .byte 12, 2, 8, 0 ; 12,2 : RAM du 6502 → RAM du 6502
.word texte
.byte 0
.word copie, 8
c2: .byte 12, 2, 8, 0 ; → écran : page $80, ligne 100, x 100 à 107
.word couleur
.byte $80
.word 100*320+100, 8
c3: .byte 12, 2, 8, $70 ; page $70 : n'existe pas
.word 0
.byte 0
.word copie, 8
texte: .text "ABCDEFGH"
copie: .fill 9 ; zéro final : chaîne terminée
couleur: .byte 5, 5, 5, 5, 5, 5, 5, 5
t0: .text "En memoire : ", 0
t1: .text "A l'ecran : ", 0
t2: .text ", apres : ", 0
t3: .text "Page $70 : erreur ", 0
a: .byte 0
b: .byte 0
c: .byte 0
e: .byte 0
; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi: sta blk
stx blk+1
jsr WaitMessage
ldy #2
lda (blk),y ; nombre de paramètres
clc
adc #3
sta fin
ldy #3
_copie: cpy fin
beq _lance
lda (blk),y
sta ControlPort+1,y ; octet 3 du bloc -> P0 ($FF04)...
iny
bra _copie
_lance: ldy #1
lda (blk),y
tax
lda (blk)
jmp appel
; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
stx ptr+1
ldy #0
_e: lda (ptr),y
beq _f
jsr WriteCharacter
iny
bne _e
_f: rts
; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
phx
phy
ldx #0
ldy #100
jsr _chiffre
ldy #10
jsr _chiffre
ora #'0'
jsr WriteCharacter
ply
plx
rts
_chiffre:
sty diviseur
ldy #0
_div: cmp diviseur
bcc _fini
sbc diviseur
iny
bne _div
_fini: pha
tya
bne _ecrit
cpx #0
beq _saute
_ecrit: ora #'0'
jsr WriteCharacter
ldx #1
_saute: pla
rts
; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel: stx ControlPort+1
sta ControlPort ; écrit en dernier : lance l'appel
jmp WaitMessage
ptr = $80
blk = $82
diviseur: .byte 0
fin: .byte 0En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 112,3 Complex Blit Copy Modifié par Trinity
Copie un rectangle vers un autre, en convertissant le format des pixels au passage, avec transparence possible.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | action : 0 copie, 1 copie sauf la valeur transparente, 2 couleur unie là où la source n'est pas transparente |
P1-P2 | addr | adresse du descripteur de la zone source (12 octets) |
P3-P4 | addr | adresse du descripteur de la zone cible (12 octets) |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | non nul si la copie est impossible (format, adresse, cible en banque) |
Descripteur de zone, dans la mémoire du 6502 : octets 0-1 adresse et 2 page ; 3 à 0 ; 4-5 pas (ajouté à l'adresse d'une ligne à la suivante ; 0 répète la même ligne, négatif remonte) ; 6 format (0 octets ; 1 paires de 4 bits et 2 bits isolés, en source seulement ; 3 et 4 moitié haute ou basse de l'octet, en cible seulement ; depuis Trinity 0.16.69, 5 et 6 deux pixels par octet en cible, le premier pixel dans la moitié haute (x pair) ou basse (x impair), pour les pages 16 couleurs du mode 2 : selon la documentation, non essayés au banc) ; puis, en source seulement, 7 valeur transparente, 8 couleur unie, 9 hauteur, 10-11 largeur en pixels. La copie travaille par octets source : la largeur est arrondie à l'octet inférieur. L'exemple déplie 4 octets en 8 pixels (1 à 8) sur deux lignes, grâce à un pas source nul. NeoBASIC n'a pas de mot-clé pour cet appel.
Texte de la documentation du firmware (anglais)
Copy a source rectangular area to a destination rectangular area. It's oriented toward copying graphics data, but can be used as a more general-purpose memory mover. The source and target areas may be different formats, and the copy will convert the data on the fly. For example, you can expand 4bpp source graphics (two pixels per byte) into the 1 pixel per byte framebuffer. However, the blitting is byte-oriented. So the source width is always rounded down to the nearest full byte.
Parameter (0) is the blit action:
0 = copy 1 = copymasked - copy, but only where src is not the transparent value. 2 = solidmasked - set target to constant solid value, but only where src is not the transparent value. See below for transparent/solid values.
Parameters (1,2) address of the source rectangle data.
Parameters (3,4) address of the target rectangle data.
The source and target rectangle data is laid out in memory as follows:
0-2 24 bit address to copy from/to (address is address:page:0)
3 pad byte (must be zero)
4-5 Stride, in bytes. This is the value to add to the address to get from one line to the next. Used for both source and target. For example: - if blitting to the screen, a stride of screen width (320) would get to the next line. - a zero source stride would repeat a single line for the whole copy. - A negative target stride would draw from the bottom upward.
6 data format 0: bytes. Supported for both source and target. 1: pairs of 4-bit values (nibbles). Source only. 2: 8 single-bit values. Source only. 3: high nibble. Target only. 4: low nibble. Target only. 5: two pixels per byte, left pixel in the high nibble, the first pixel going to the HIGH nibble of the first byte (even x). Target only (Trinity T-90, the 16 colour pages of mode 2). 6: the same, the first pixel going to the LOW nibble of the first byte (odd x). Target only. With 5 and 6 every source format and action is supported ; each value keeps its low 4 bits (the solid value too) and a nibble that is not written is left unchanged.
7 A constant to use as the "transparent" value for BLTACT_MASK and BLTACT_SOLID. Source only. Not used in target.
8 A constant to use as the "solid" value for BLTACT_SOLID. Source only. Not used in target.
9 Height. The number of lines to copy. Source only. Not used in target. The copy is driven by the source height.
10-11 Width. The number of values to copy for each line. Source only. Not used in target. The copy is driven by the source width.
C (llvm-mos)
/* Déplier des pixels de 4 bits sur deux lignes de l'écran (groupe 12, fonction 3). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>
/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
ControlPort.function = fonction;
ControlPort.command = groupe;
while (ControlPort.command) /* attendre la fin */
;
}
static void libre(void) { /* attendre que la boîte aux lettres soit libre */
while (ControlPort.command)
;
}
/* Descripteurs de zone (voir la fiche) : adresse sur 24 bits, 0, pas, format, transparent, uni, hauteur, largeur. */
static uint8_t pixels[4] = {0x12, 0x34, 0x56, 0x78}; /* 8 pixels de 4 bits : 1 à 8 */
static uint8_t source[12], cible[12];
int main(void) {
putchar(12);
source[0] = (uint16_t)pixels & 0xFF; /* adresse en RAM du 6502 (page $00) */
source[1] = (uint16_t)pixels >> 8;
source[4] = 0; /* pas 0 : la même ligne resservira */
source[6] = 1; /* format 1 : paires de 4 bits */
source[9] = 2; /* hauteur : 2 lignes */
source[10] = 8; /* largeur : 8 pixels */
uint16_t ecran = 120 * 320 + 100; /* ligne 120, x 100 */
cible[0] = ecran & 0xFF;
cible[1] = ecran >> 8;
cible[2] = 0x80; /* page $80 : mémoire vidéo */
cible[4] = 320 & 0xFF; /* pas : une ligne d'écran */
cible[5] = 320 >> 8;
cible[6] = 0; /* format 0 : un octet par pixel */
libre();
ControlPort.params[0] = 0; /* P0 : action 0, copie */
ControlPort.params[1] = (uint16_t)source & 0xFF; /* P1-P2 : zone source */
ControlPort.params[2] = (uint16_t)source >> 8;
ControlPort.params[3] = (uint16_t)cible & 0xFF; /* P3-P4 : zone cible */
ControlPort.params[4] = (uint16_t)cible >> 8;
api(12, 3); /* Complex Blit Copy */
uint8_t l[2][8];
for (uint8_t y = 0; y < 2; y++)
for (uint8_t x = 0; x < 8; x++)
l[y][x] = neo_graphics_read_pixel(100 + x, 120 + y);
for (uint8_t y = 0; y < 2; y++) {
printf("Ligne %u :", y + 1);
for (uint8_t x = 0; x < 8; x++)
printf(" %u", l[y][x]);
printf("\n");
}
return 0;
}Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8Mad Pascal
// Déplier des pixels de 4 bits sur deux lignes de l'écran (groupe 12, fonction 3).
program blit3;
uses crt, neo6502;
const pixels: array[0..3] of byte = ($12, $34, $56, $78); // 8 pixels de 4 bits : 1 à 8
var source, cible: array[0..11] of byte; // descripteurs de zone (voir la fiche)
l: array[0..15] of byte;
x, y: byte;
begin
write(chr(12));
fillchar(@source, 12, 0);
fillchar(@cible, 12, 0);
source[0] := lo(word(@pixels)); // adresse en RAM du 6502 (page $00)
source[1] := hi(word(@pixels));
source[6] := 1; // format 1 : paires de 4 bits ; pas 0 : la même ligne resservira
source[9] := 2; // hauteur : 2 lignes
source[10] := 8; // largeur : 8 pixels
cible[0] := lo(120 * 320 + 100); // ligne 120, x 100
cible[1] := hi(120 * 320 + 100);
cible[2] := $80; // page $80 : mémoire vidéo
cible[4] := lo(320); // pas : une ligne d'écran
cible[5] := hi(320); // format 0 : un octet par pixel
NeoBlitterXCopy(0, @source, @cible); // Complex Blit Copy, action 0 : copie
for y := 0 to 1 do
for x := 0 to 7 do
l[y * 8 + x] := NeoGetPixel(100 + x, 120 + y);
for y := 0 to 1 do begin
write('Ligne ', y + 1, ' :');
for x := 0 to 7 do write(' ', l[y * 8 + x]);
writeln;
end;
end.Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8NeoBASIC
10 ' Deplier des pixels de 4 bits sur deux lignes de l'ecran (groupe 12, fonction 3)
20 cls
30 p = alloc(4) : poke p, $12 : poke p + 1, $34 : poke p + 2, $56 : poke p + 3, $78 : ' 8 pixels de 4 bits : 1 a 8
40 s = alloc(12) : c = alloc(12) : for i = 0 to 11 : poke s + i, 0 : poke c + i, 0 : next i
50 doke s, p : ' zone source : adresse en RAM du 6502 (page $00), pas 0 (la meme ligne resservira)
60 poke s + 6, 1 : poke s + 9, 2 : poke s + 10, 8 : ' format 1 (paires de 4 bits), hauteur 2, largeur 8
70 doke c, 38500 : poke c + 2, $80 : ' zone cible : 120 * 320 + 100 (ligne 120, x 100), page $80 (video)
80 doke c + 4, 320 : ' pas : une ligne d'ecran ; format 0, un octet par pixel
90 while peek($ff00) : wend
100 poke $ff04, 0 : doke $ff05, s : doke $ff07, c : poke $ff01, 3 : poke $ff00, 12 : ' Complex Blit Copy, action 0 : copie
110 while peek($ff00) : wend
120 for y = 0 to 1 : print "Ligne ";y + 1;" :"; : for x = 0 to 7 : print " ";point(100 + x, 120 + y); : next x : print : next yLigne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8Assembleur 65C02 (64tass)
; Déplier des pixels de 4 bits sur deux lignes de l'écran (groupe 12, fonction 3).
.cpu "65c02"
* = $0800
ControlPort = $FF00 ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage = $FFF4 ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1 ; affiche le caractère de A
start: lda #12 ; écran effacé : on relit des pixels
jsr WriteCharacter
lda #<blit
ldx #>blit
jsr envoi
ldx #0
_ll1: phx
txa
clc
adc #100
ldy #120
jsr pixel
plx
sta l1,x
inx
cpx #8
bne _ll1
ldx #0
_ll2: phx
txa
clc
adc #100
ldy #121
jsr pixel
plx
sta l2,x
inx
cpx #8
bne _ll2
lda #<t1
ldx #>t1
jsr ecrire
ldx #0
_al1: lda #' '
jsr WriteCharacter
lda l1,x
jsr decimal
inx
cpx #8
bne _al1
lda #13
jsr WriteCharacter
lda #<t2
ldx #>t2
jsr ecrire
ldx #0
_al2: lda #' '
jsr WriteCharacter
lda l2,x
jsr decimal
inx
cpx #8
bne _al2
lda #13
jsr WriteCharacter
rts
; Couleur du fond (5,33) en (A, Y), X et Y inférieurs à 256, rendue dans A.
pixel: pha
jsr WaitMessage
pla
sta ControlPort+4 ; P0-P1 : X
stz ControlPort+5
sty ControlPort+6 ; P2-P3 : Y
stz ControlPort+7
lda #5
ldx #33 ; 5,33 : Read Pixel
jsr appel
lda ControlPort+4
rts
blit: .byte 12, 3, 5, 0 ; 12,3 : Complex Blit Copy, action 0 (copie)
.word source, cible ; zones source et cible
pixels: .byte $12, $34, $56, $78 ; 8 pixels de 4 bits : 1 à 8
source: .word pixels ; adresse en RAM du 6502...
.byte 0, 0 ; ... page $00, octet de bourrage
.word 0 ; pas 0 : la même ligne resservira
.byte 1, 0, 0, 2 ; format 1 (paires de 4 bits), transparent, uni, hauteur 2
.word 8 ; largeur : 8 pixels
cible: .word 120*320+100 ; ligne 120, x 100...
.byte $80, 0 ; ... page $80 (mémoire vidéo)
.word 320 ; pas : une ligne d'écran
.byte 0, 0, 0, 0 ; format 0 : un octet par pixel
.word 0
t1: .text "Ligne 1 :", 0
t2: .text "Ligne 2 :", 0
l1: .fill 8
l2: .fill 8
; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi: sta blk
stx blk+1
jsr WaitMessage
ldy #2
lda (blk),y ; nombre de paramètres
clc
adc #3
sta fin
ldy #3
_copie: cpy fin
beq _lance
lda (blk),y
sta ControlPort+1,y ; octet 3 du bloc -> P0 ($FF04)...
iny
bra _copie
_lance: ldy #1
lda (blk),y
tax
lda (blk)
jmp appel
; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
stx ptr+1
ldy #0
_e: lda (ptr),y
beq _f
jsr WriteCharacter
iny
bne _e
_f: rts
; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
phx
phy
ldx #0
ldy #100
jsr _chiffre
ldy #10
jsr _chiffre
ora #'0'
jsr WriteCharacter
ply
plx
rts
_chiffre:
sty diviseur
ldy #0
_div: cmp diviseur
bcc _fini
sbc diviseur
iny
bne _div
_fini: pha
tya
bne _ecrit
cpx #0
beq _saute
_ecrit: ora #'0'
jsr WriteCharacter
ldx #1
_saute: pla
rts
; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel: stx ControlPort+1
sta ControlPort ; écrit en dernier : lance l'appel
jmp WaitMessage
ptr = $80
blk = $82
diviseur: .byte 0
fin: .byte 0Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 812,4 Blit Image Modifié par Trinity
Pose une image (décrite comme la source de 12,3) à l'écran, en la rognant sur les bords.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | action, comme 12,3 |
P1-P2 | addr | adresse du descripteur de la zone source |
P3-P4 | int16 | X à l'écran (signé) |
P5-P6 | int16 | Y à l'écran (signé) |
P7 | uint8 | écriture : 0 octet entier, 3 moitié haute, 4 moitié basse |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | non nul en mode 1 ou 2, ou pour un format source inconnu |
Le rognage se fait par octet source : une image d'un bit par pixel posée en x = -2 perd tout son premier octet (8 pixels), si bien que les pixels 0 à 5 restent vides et que l'image commence en x = 6. C'est ce que montre l'exemple (action 2, couleur 6). Trinity refuse l'appel dans les modes compacts 1 et 2, où l'image serait écrite au mauvais endroit ; 12,2 et 12,3 y copient des octets bruts. NeoBASIC n'a pas de mot-clé pour cet appel.
Texte de la documentation du firmware (anglais)
Blits an image from memory onto the screen. The image will be clipped, so it's safe to blit partly (or fully) offscreen-images. Mode 0 only : in the packed modes (1 and 2) the error flag is set and nothing is drawn (Trinity T-90). Functions 12,2 and 12,3 copy raw bytes into video RAM in every mode ; in the packed modes a byte holds 2 (mode 2) or 8 (mode 1) pixels, and pages $80/$81 address the video RAM from its start, not the draw page of 5,11.
Parameter (0) is the blit action (see function 3, Complex Blit):
Parameters (1,2) address of the source rectangle data.
Parameters (3,4) x pixel coordinate on screen (signed 16 bit)
Parameters (5,6) y pixel coordinate on screen (signed 16 bit)
Parameter (7) destination format, determines how framebuffer will be written: 0: write to whole byte. 1: unsupported 2: unsupported 3: write to high nibble only. 4: write to low nibble only.
NOTE: clipping operates at byte resolution on the source data. So, for example, if you blit a 1-bit image (format 2) to an x-position of -2, then the whole first byte will be skipped leaving 6 empty pixels on the left. Same happens on the right - either the whole source byte is used, or it'll be skipped.
C (llvm-mos)
/* Poser une image d'un bit par pixel, rognée à gauche (groupe 12, fonction 4). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>
/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
ControlPort.function = fonction;
ControlPort.command = groupe;
while (ControlPort.command) /* attendre la fin */
;
}
static void libre(void) { /* attendre que la boîte aux lettres soit libre */
while (ControlPort.command)
;
}
static uint8_t bits[2] = {0xFF, 0xFF}; /* une ligne de 16 pixels allumés */
static uint8_t source[12];
int main(void) {
putchar(12);
source[0] = (uint16_t)bits & 0xFF;
source[1] = (uint16_t)bits >> 8;
source[4] = 0; /* pas 0 : la même ligne sur toute la hauteur */
source[6] = 2; /* format 2 : 8 pixels d'un bit par octet */
source[7] = 0; /* valeur transparente */
source[8] = 6; /* couleur des pixels à 1 (action 2) */
source[9] = 4; /* hauteur */
source[10] = 16; /* largeur */
libre();
ControlPort.params[0] = 2; /* P0 : action 2, couleur unie là où la source n'est pas transparente */
ControlPort.params[1] = (uint16_t)source & 0xFF; /* P1-P2 : zone source */
ControlPort.params[2] = (uint16_t)source >> 8;
ControlPort.params[3] = (uint8_t)-2; /* P3-P4 : x = -2 (signé) */
ControlPort.params[4] = 0xFF;
ControlPort.params[5] = 100; /* P5-P6 : y = 100 */
ControlPort.params[6] = 0;
ControlPort.params[7] = 0; /* P7 : écrit l'octet entier */
api(12, 4); /* Blit Image */
uint8_t e = ControlPort.error;
uint8_t p[16];
for (uint8_t x = 0; x < 16; x++)
p[x] = neo_graphics_read_pixel(x, 100);
printf("Erreur %u ; x 0 a 15 :", e);
for (uint8_t x = 0; x < 16; x++)
printf(" %u", p[x]);
printf("\n");
return 0;
}Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0Mad Pascal
// Poser une image d'un bit par pixel, rognée à gauche (groupe 12, fonction 4).
program blit4;
uses crt, neo6502;
const bits: array[0..1] of byte = ($FF, $FF); // une ligne de 16 pixels allumés
var source: array[0..11] of byte;
p: array[0..15] of byte;
x, e: byte;
begin
write(chr(12));
fillchar(@source, 12, 0);
source[0] := lo(word(@bits));
source[1] := hi(word(@bits));
source[6] := 2; // format 2 : 8 pixels d'un bit par octet ; pas 0
source[7] := 0; // valeur transparente
source[8] := 6; // couleur des pixels à 1 (action 2)
source[9] := 4; // hauteur
source[10] := 16; // largeur
NeoBlitImage(2, @source, word(-2), 100, 0); // Blit Image : action 2, x = -2, y = 100, octet entier
NeoWaitMessage;
e := NeoMessage.error;
for x := 0 to 15 do p[x] := NeoGetPixel(x, 100);
write('Erreur ', e, ' ; x 0 a 15 :');
for x := 0 to 15 do write(' ', p[x]);
writeln;
end.Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0NeoBASIC
10 ' Poser une image d'un bit par pixel, rognee a gauche (groupe 12, fonction 4)
20 cls
30 b = alloc(2) : poke b, $ff : poke b + 1, $ff : ' une ligne de 16 pixels allumes
40 s = alloc(12) : for i = 0 to 11 : poke s + i, 0 : next i
50 doke s, b : poke s + 6, 2 : poke s + 8, 6 : ' format 2 (8 pixels d'un bit par octet), pas 0, couleur 6 pour les 1
60 poke s + 9, 4 : poke s + 10, 16 : ' hauteur 4, largeur 16
70 while peek($ff00) : wend
80 poke $ff04, 2 : doke $ff05, s : doke $ff07, $fffe : doke $ff09, 100 : poke $ff0b, 0 : ' Blit Image : action 2, x = -2 ($fffe), y = 100
90 poke $ff01, 4 : poke $ff00, 12
100 while peek($ff00) : wend : e = peek($ff02)
110 print "Erreur ";e;" ; x 0 a 15 :"; : for x = 0 to 15 : print " ";point(x, 100); : next x : printErreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0Assembleur 65C02 (64tass)
; Poser une image d'un bit par pixel, rognée à gauche (groupe 12, fonction 4).
.cpu "65c02"
* = $0800
ControlPort = $FF00 ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage = $FFF4 ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1 ; affiche le caractère de A
start: lda #12 ; écran effacé : on relit des pixels
jsr WriteCharacter
lda #<image
ldx #>image
jsr envoi
lda ControlPort+2
sta e
ldx #0
_lp: phx
txa
ldy #100
jsr pixel
plx
sta p,x
inx
cpx #16
bne _lp
lda #<t0
ldx #>t0
jsr ecrire
lda e
jsr decimal
lda #<t1
ldx #>t1
jsr ecrire
ldx #0
_ap: lda #' '
jsr WriteCharacter
lda p,x
jsr decimal
inx
cpx #16
bne _ap
lda #13
jsr WriteCharacter
rts
; Couleur du fond (5,33) en (A, Y), X et Y inférieurs à 256, rendue dans A.
pixel: pha
jsr WaitMessage
pla
sta ControlPort+4 ; P0-P1 : X
stz ControlPort+5
sty ControlPort+6 ; P2-P3 : Y
stz ControlPort+7
lda #5
ldx #33 ; 5,33 : Read Pixel
jsr appel
lda ControlPort+4
rts
image: .byte 12, 4, 8, 2 ; 12,4 : Blit Image, action 2 (couleur unie)
.word source
.word 65536-2, 100 ; x = -2 (signé), y = 100
.byte 0 ; octet entier
bits: .byte $FF, $FF ; une ligne de 16 pixels allumés
source: .word bits
.byte 0, 0
.word 0 ; pas 0 : la même ligne sur toute la hauteur
.byte 2, 0, 6, 4 ; format 2 (bits), transparent 0, couleur 6, hauteur 4
.word 16 ; largeur
t0: .text "Erreur ", 0
t1: .text " ; x 0 a 15 :", 0
e: .byte 0
p: .fill 16
; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi: sta blk
stx blk+1
jsr WaitMessage
ldy #2
lda (blk),y ; nombre de paramètres
clc
adc #3
sta fin
ldy #3
_copie: cpy fin
beq _lance
lda (blk),y
sta ControlPort+1,y ; octet 3 du bloc -> P0 ($FF04)...
iny
bra _copie
_lance: ldy #1
lda (blk),y
tax
lda (blk)
jmp appel
; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
stx ptr+1
ldy #0
_e: lda (ptr),y
beq _f
jsr WriteCharacter
iny
bne _e
_f: rts
; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
phx
phy
ldx #0
ldy #100
jsr _chiffre
ldy #10
jsr _chiffre
ora #'0'
jsr WriteCharacter
ply
plx
rts
_chiffre:
sty diviseur
ldy #0
_div: cmp diviseur
bcc _fini
sbc diviseur
iny
bne _div
_fini: pha
tya
bne _ecrit
cpx #0
beq _saute
_ecrit: ora #'0'
jsr WriteCharacter
ldx #1
_saute: pla
rts
; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel: stx ControlPort+1
sta ControlPort ; écrit en dernier : lance l'appel
jmp WaitMessage
ptr = $80
blk = $82
diviseur: .byte 0
fin: .byte 0Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0