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 méthodes d'affichage, et même le nombre de bits d'affichage…
Mais cela pouvait bien plus, car on pouvait lui faire exécuter des instructions en même 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 faisceau 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” contrôleurs simplifiés (notamment 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 à émuler une copperlist (le but de cet article).
Le copper ne comprends que 3 instructions, mais c'est amplement suffisant pour “contrôler” le faisceau 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”.
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.
Similaire à wait, mais avec un bit de différence…
Cette instruction permet de charger un registre du copper avec une valeur… Comme je l'ai dis dans l'introduction, les registres du copper était nombreux, et contrôlaient avec précision ce qui était afficher.
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 coûterait très certainement 100% du CPU (voir plus).
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.
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 contrôle 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.
Cette instruction fini l'execution du copperlist, la couleur 0 (fond d'écran) est forcée à (0,0,0).
Byte 0 |
---|
0XFF |
#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); }