Les CopperList
Sur mon Amiga, il y avait un truc qui était terrible: le processus "Copper"... c'est une partie du "processeur" graphique de l'Amiga, en modifiant ses registres, on changeait la résolution, les methode d'affichage, et meme le nombre de bits d'affichage...
Mais cela pouvait bien plus, car on pouvait lui faire executé des instructions en meme temps qu'il "dessinait" l'image sur l'écran.
Une Copperlist, c'est tout simplement la liste des instructions que le Copper doit exécuter lors d'un balayage écran, ce qui correspond au trajet du faiseau du coin en haut à gauche (0,0) au coin en bas à droite (maxx,maxy).
Malheureusement la carte VGA ne dispose pas de ce type de processeur, même si elle dispose de pas mal de "petits" controleurs simplifiés (notament un motorola CRTC 6845), il n'existe rien d'aussi puissant sur PC, il faudra donc que le x86 se charge de la tache si l'on désire arriver à émuller une copperlist (le but de cet article).
Les instructions du CopperList
Le copper ne comprends que 3 instructions, mais c'est amplement suffisant pour "controler" le faiseau lumineux.
Je ne vais pas entrer trop dans les détails techniques de l'Amiga, ce n'est pas le but de l'article, mais il est important de comprendre le fonctionnement de la copperlist de ce dernier si nous voulons arriver à faire quelque chose sur le PC qui soit "un peu spéciale".
WAIT
Cette instruction permet d'attendre une position précise à l'écran; toutefois cette précision est "réduite" pour ce qui est de la position horizontale. Cette restriction horizontale fait que l'on peut seulement "pointer" un modulo de 4 pixels.
Les raisons de ce modulo sont assez évidentes: aller au pixel près aurait demandé un trop grande puissance CPU, ce qui à l'époque n'était pas possible, et aurait également demandé plus de bytes dans l'instruction.
SKIP
Similaire à wait, mais avec un bit de différence...
MOVE
Cette instruction permet de charger un registre du copper avec une valeur... Comme je l'ai dis dans l'introduction, les registres du copper etait nombreux, et controlaient avec précision ce qui était afficher.
Notre CopperList
Pour notre copperlist, je vais garder le principe de peu d'instructions, mais je ne vais pas optimiser ces listes d'instruction pour le moment.
Vu que tout devra être traiter par le x86 et qu'il faut garder du temps CPU, je ne vais pas essayer de me "caler" autrement que sur la verticale, l'horizontale serait sans doute possible, mais couterait très certainement 100% du CPU (voir plus).
WAIT: 0x10
Cette instruction va faire "compter" notre routine le nombre de "retour de balayage", aucune autre instruction de notre programme ne sera lue avant que le compteur de ligne n'ait atteint la valeur précisée. Les "interruptions" sont possible, ce qui fait que le "compteur" peut rater un ou plusieurs retour de balayage.
Dès que le compteur atteints (ou dépasse) la valeur, le copperlist se remet à lire les instructions du programme.
Byte 0 | Byte 1 | Byte 2 |
0x10 | LineH | LineL |
Une protection "overflow" existe si le compteur dépasse plus de 400 comptages. Cette valeur est précisée dans la variables copper_maxrow du programme. Dans ce cas, une erreur sera émise et notre fonction cessera de s'executer, on peut connaitre l'état du copperlist en regardant la variable copper_error.
SetColor: 0x20
Cette instruction va charger le DAC avec 4 octets, le 1er étant l'index de la couleur a changer, suivit des valeur de rouge, vert, bleu. Aaprès l'écriture dans le DAC, l'instruction suivante du copperlist est lue.
Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
0x20 | Color | Red | Green | Blue |
On n'oublie pas qu'une carte VGA ne peux choisir les intensités de rouge, vert, bleu que sur une plage de valeur de 0 à 63.
Aucun controle n'est fait sur les paramètres, si ceux-ci dépassent 63 par exemple, la valeur sera transmise au DAC directement, les réactions de ce derniers ne sont pas connues, aucune erreur ne sera détectée.
EOC: 0xFF
Cette instruction fini l'execution du copperlist, la couleur 0 (fond d'écran) est forcée à (0,0,0).
Byte 0 |
0XFF |
Le code
#include "dos.h"
#include "conio.h"
typedef unsigned char BYTE;
typedef unsigned int WORD;
// copper_maxrow is a parameter to avoid copper/raster to turn forever
// copper_error must be set to 0 to run
int copper_maxrow = 400;
BYTE copper_error = 0;
#define PRECIS 8
BYTE copperlist[] =
{
0x10, 0x00, 0x64, // waitLine 100
0x20, 0x00, 0x0D, 0x00, 0x0b, // setColor 0x00 0x0d,0x00,0x0b
0x10, 0x00, 0x96, // waitLine 150
0x20, 0x00, 0x2F, 0x12, 0x17, // setColor 0x00 0x2f,0x12,0x17
0x10, 0x00, 0xFA, // waitLine 255
0x20, 0x00, 0x09, 0x0F, 0x34, // setColor 0x00 0x09,0x0f,0x34
0xFF // EOC
};
void DrawCopperList(char *copperlist)
{
if (copper_error!=0)
return;
asm {
push ds
push si
lds si,copperlist
xor cx,cx // reset cx
xor bx,bx
mov dx,0x3DA } // wait for stable know pos (0)
w1: asm {
in al,dx
test al,0x08
jne w1 }
w2: asm {
in al,dx
test al,0x08
je w2 }
start:asm {
// protection
mov al,0x05 // error 1
cmp cx,copper_maxrow // line counter copper > max ?
jae eocl
lodsb // get copper list operand
cmp al,0xFF // eocl ?
je clean_eocl
cmp al,0x10 // wait ?
je wait_line
cmp al,0x20 // set color ?
je set_color
mov al,0xFF // unknown command
jmp eocl }
// ------------------------------------- Wait Line
wait_line:asm {
lodsw
mov bh,al
mov bl,ah
mov bh,al }
wait_next: asm {
inc cx // cx = cx+1
cmp cx,bx // current line >= wait_line ?
ja start } // YES : next operand please
wait_hbl:asm {
mov dx,0x3da } // read input state
in_retrace:asm {
in al,dx // test if we are redrawing
test al,1
jne in_retrace }
in_display:asm {
in al,dx
test al,1 // wait for hbl (horizontal return)
je in_display
jmp wait_next } // new line
// ------------------------------------- Set Color
set_color: asm {
cli
lodsb // get Color
mov dx,0x3c8
out dx,al // select color index
mov dx,0x3c9
lodsb // get RED level
out dx,al // set RED to dac
lodsb // get GREEN level
out dx,al // set GREEN to dac
lodsb // get BLUE level
out dx,al // set BLUE to dac
jmp start } // get next operand
// ------------------------------------- End CopperList
clean_eocl: asm {
xor al,al } // clear error operand
eocl:asm {
sti
pop si
pop ds
mov copper_error, al // set error (if any)
xor al,al // normally we should restore whole DAC's status
mov dx,0x3c8 // but we only reset color 0 to black
out dx,al
inc dx
out dx,al // turn to RGB 0,0,0
out dx,al
out dx,al }
}
void main()
{
unsigned char running=1;
textmode(3);
clrscr();
while (running)
{
running=(copper_error?0:1);
if (kbhit())
{
running=0;
}
// do some stuffs
printf("T h i s I s T h e T e s t\n");
DrawCopperList(copperlist);
}
printf("\n error: %i\n",copper_error);
}