Tutoriaux: VGA Avancée - DAC - Part 2

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);
}

Littératures:

  • Liste Copper et registres du Copper de  Roméo Rapido (lien)
  • WAIT, SKIP et COPJMPx : un usage avancé du Copper sur Amiga (lien)