Table des matières

VGA Avancée - DAC - Part 3

CopperList avancée

Dans nos deux derniers articles, nous avons appris à faire des dégradés et ensuite à programmer un changement de couleur suivant l'index de la ligne en cours. Maintenant il faut mixer les deux.

Comme je l'ai dit dans un article précédent, la carte VGA n'est pas aussi performante que un processeur dédié comme en disposait l'amiga (voir même le C64 dans une moindre mesure).

Nous allons donc re-définir nos instructions et en ajouter une nouvelle.

Instruction CopperList

Nous allons corrigé un problème qui peut se présenter avec certains chipset VGA, la limite des intensités qui varie de 0 à 63. Pour éviter les ennuis, nous allons tout simplement ramener les intensité de notre copperlist à 6 bits (via un shr).

Wait: 0x10

Aucune modification n'est apportée a cette instruction.

SetColor: 0x20

Cette instruction est modifiée dans son comportement, les intensités de rouge, vert et bleu sont divisées par 4 avant d'être envoyées au DAC.

GradiantTo: 0x30

Cette instruction prends les valeurs du dernier “SetColor” (y compris la couleur) pour faire varier les intensités jusqu'aux nouvelles valeurs paramètres, et ce depuis la ligne courant jusqu'à la ligne cible.

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5
0x30 LineH LineL Red Green Blue

Les bytes “LineH” et “LineL” sont les octets de poids fort et faible du mot qui défini la ligne “cible”, celle-ci DOIT être inférieur à copper_maxrow, sans quoi l’exécution des copper s’arrêtent avec un code d'erreur.

EOC: 0xFF

Cette instruction n'est pas modifiée.

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 copperlistv2[] =
  {
    0x20,       0x00, 0x00, 0x00, 0x00,         // setColor   0x00 0x00,0x00,0x00  : Black
    0x30, 0x00, 0x32, 0xFF, 0xFF, 0x00,         // GradiantTo 0x32 0xFF,0xFF,0x00  : Yellow
    0x30, 0x00, 0x64, 0xFF, 0x00, 0x00,         // GradiantTo 0x64 0x09,0x0f,0x34  : Red
    0x30, 0x00, 0x96, 0x00, 0x00, 0x00,         // GradiantTo 0x96 0x00,0x00,0x00  : Black
    0x30, 0x00, 0xC8, 0xFF, 0xFF, 0x00,         // GradiantTo 0x32 0xFF,0xFF,0x00  : Yellow
    0x30, 0x00, 0xFA, 0xFF, 0x00, 0x00,         // GradiantTo 0x64 0x09,0x0f,0x34  : Red
    0x30, 0x01, 0x2C, 0x00, 0x00, 0x00,         // GradiantTo 0x96 0x00,0x00,0x00  : Black
    0xFF                                        // EOC
 
  };
 
void DrawCopperListv2(char *copperlist)
{
  BYTE  color=0;
  BYTE  red_prec=0, green_prec=0, blue_prec=0;    // color set or starting of gradient
  BYTE  red_step=0, green_step=0, blue_step=0;    // steps (end color - start color)
  BYTE  red_end=0, green_end=0, blue_end=0;       // end color
  WORD  line_end=0, line_start=0;                 // line end line start
  WORD  line_delta=0;                             // delta between end and start
 
  if (copper_error!=0)
    return;
 
    asm {
        push  ds
        push  si
        lds   si,copperlist
        xor    cx,cx                  // reset cx : line counter
        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 ?
        jb    start2                  // go
        jmp    eocl }                 // exit
 
start2: asm {
        lodsb                         // load copper list operand
        cmp   al,0xFF                 // eocl ?
        jne   start3
        xor    al,al
        jmp    eocl }
 
start3: asm {
        cmp   al,0x10                 // wait ?
        jne    start4
        jmp    wait_line }
 
start4:asm {
        cmp   al,0x20                 // setcolor ?
        jne    start5
        jmp    set_color }
 
start5:asm {
        cmp   al,0x30                 // gradient ?
        je    gradient
 
        mov   al,0xFF                 // unknown command
        jmp   eocl }
 
// ------------------------------------- GRADIENT
gradient: asm {
        mov    line_start,cx          // preserve line start
        lodsw
        mov    bh,al                  // reverse endian
        mov   bl,ah
        mov   line_end,bx             // preserve line end
 
        // calculate the number of line between line_start and line_end
        sub    bx,cx
        mov    line_delta,bx          // pct_max = line count between line_start and end
 
        lodsb                         // load red_end
        shr    al,2                   // reduce to 6 bits only
        mov    red_end,al             // preserve red target
        mov    ah,al
 
        mov    bl,red_prec            // bl = red start
        sub    al,bl                  // end - start
        mov   red_step,al             // calculate Red Stepping
        mov    red_prec,ah            //
 
        lodsb                         // load green_end
        shr    al,2
        mov    green_end,al
        mov    ah,al
        mov    bl,green_prec          // get green start
        sub    al,bl
        mov   green_step,al           // calculate green stepping
        mov    green_prec,ah
 
        lodsb                         // load blue_end
        shr    al,2
        mov    blue_end,al
        mov    ah,al
        mov   bl,blue_prec
        sub    al,bl
        mov   blue_step,al
        mov    blue_prec,ah }         // calculate Blue Stepping
 
gr_start: asm {
        inc    cx                     // cx = cx+1
        cmp   cx,line_end             // gradient complet ?
        jb    gradient_hbl
        jmp    start  }               // next operant
 
gradient_hbl:asm {
        mov    dx,0x3da }             // read input state
gr_in_retrace:asm  {
        in    al,dx                   // test if we are redrawing
        test  al,1
        jne    gr_in_retrace }
 
gr_in_display:asm  {
        in    al,dx
        test  al,1                    // wait for hbl (horizontal return)
        je    gr_in_display
 
        mov    ax,cx
        sub    ax,line_start          // pct_current(ax) = currentline(cx) - line_start
        mov    bx,line_delta          // bx = line_start - line_end
 
        xor   dx,dx
        shl   ax,PRECIS               // increase precision
        div   bx                      // pct_current / pct_max
        mov    bx,ax                  // bx = percentage 0..100
 
        cli
        mov   al,color
        mov    dx,0x3c8
        out   dx,al                   // select color index
        inc   dx
 
        xor   ax,ax
        mov   al,red_step
        imul  bl                      // must be signed multiplication
        shr   ax,PRECIS
        add   al,red_prec
        out    dx,al                  // set RED to dac
        mov    red_end,al
 
        xor   ax,ax
        mov   al,green_step
        imul  bl                      // must be signed multiplication
        shr   ax,PRECIS
        add   al,green_prec
        out    dx,al                  // set GREEN to dac
        mov    green_end,al
 
        xor   ax,ax
        mov   al,blue_step
        imul  bl                      // must be signed multiplication
        shr   ax,PRECIS
        add   al,blue_prec
        out    dx,al                  // set BLUE to dac
        mov    blue_end,al
        sti
 
        jmp   gr_start }              // new line
 
// ------------------------------------- WAIT
wait_line:asm  {
        lodsw
        mov    bh,al                  // swap byte endian encoding craps
        mov   bl,ah                   // bx = line word
        mov    dx,0x3da }             // input state
 
wait_next: asm {
        inc    cx                     // cx = cx+1
        cmp   cx,bx                   // current line>= wait_line ?
        jae   wait_end }              // YES : next operand please
 
in_retrace:asm  {
        in    al,dx                   // read input state, 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
 
wait_end:asm {
        jmp    start }
 
// ------------------------------------- SETCOLOR
set_color: asm {
        cli
        lodsb                         // get color index
        mov   color,al
        mov    dx,0x3c8
        out   dx,al                   // select color index
        inc   dx                      // mov   dx,0x3c9
 
        lodsb                         // get RED level
        shr    al,2
        mov   red_prec,al
        out    dx,al                  // set RED to dac
 
        lodsb                         // get GREEN level
        shr    al,2
        mov   green_prec,al
        out    dx,al                  // set GREEN to dac
 
        lodsb                         // get BLUE level
        shr    al,2
        mov   blue_prec,al
        out    dx,al                  // set BLUE to dac
        sti
        jmp   start }                 // get next 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");
    DrawCopperListv2(copperlistv2);
  }
 
  printf("\n error: %i\n",copper_error);
}
Commentaire sur les sources

A cause des limitations du x86 concernant les jump conditionnels (near), il a fallut mettre en place un structure de type switch case; car l'oppérande n'est que de 2 octets et n'est pas capable de faire de sauts conditionnel au delà d'un delta de -126 +127.

Le build-in assembler de borland C++ utilise des instruction 'near' pour son assemblage, ce qui fait que certains sauts sont hors limite; pour contourner ce problème il aurait fallut écrire le code assembleur .386 dans un fichier .asm et ensuite le linker au code C, ce qui aurait permis de réduire la “boucle” comme ci-dessous.

        mov   al,0x05                 // error 1
        cmp   cx,copper_maxrow        // line counter copper> max ?
        jae   eocl                    // exit
 
        lodsb                         // load copper list operand
        cmp   al,0xFF                 // eocl ?
        je    eocl
        cmp   al,0x10                 // wait ?
        je    wait_line
        cmp   al,0x20                 // setcolor ?
        je    set_color
        cmp   al,0x30                 // gradient ?
        je    gradient
        jmp   eocl                    // unknown command

Ce qui se révèle plus simple et plus rapide (pas de re-fetch), mais dans le cadre de ce tuto cela n'a pas grande importance.

Source Code asm

          .model   TCHUGE
          jumps
          locals
          .386
 
PRECIS              EQU       8
 
CODESEG
 
; public variables
 
_copper_error        db        0                       ; error reporting
_copper_maxrow      dw        400                      ; max linescan
 
public _copper_error
public _copper_maxrow
 
; private variables
 
;color               db        0                   ; color set or starting of gradient
;red_prec            db        0
;green_prec          db        0
;blue_prec           db        0
 
;red_step            db        0                   ; steps (end color - start color)
;green_step          db        0
;blue_step           db        0
 
;red_end             db        0                   ; end color
;green_end           db        0
;blue_end            db        0
 
;line_end            dw        0                   ; line end line start
;line_start          dw        0
;line_delta          dw        0                   ; delta between end and start
 
public    _DrawCopperList
 
_DrawCopperList PROC C FAR
        ARG      CopperList:DWORD
local   color:byte
local   red_prec:byte, green_prec:byte, blue_prec:byte
local   red_step:byte, green_step:byte, blue_step:byte
local   red_end:byte , green_end:byte , blue_end:byte
local   line_end: word, line_start: word, line_delta: word
 
        push  ds
        push  si
 
        lds   si,copperlist
 
        cmp    cs:_copper_error,0
        jne    clean_eocl
 
        xor    ecx,ecx                 ; reset cx : line counter
        xor    ebx,ebx
 
        mov   dx,03DAh                 ; wait for stable know pos (0)
w1:     in    al,dx
        test  al,08h
        jne   w1
w2:     in    al,dx
        test  al,08h
        je    w2
 
start:  mov   al,01h                  ; error 1
        cmp   cx,CS:_copper_maxrow    ; line counter copper> max ?
        jae   eocl                    ; exit
 
        lodsb                         ; load copper list operand
        cmp   al,0FFh                 ; eocl ?
        je    clean_eocl
 
        cmp   al,010h                 ; wait ?
        je    wait_line
 
        cmp   al,020h                 ; setcolor ?
        je    set_color
 
        cmp   al,030h                 ; gradient ?
        je    gradient
 
        mov   al,002h                 ; unknown command
        jmp   eocl
 
; ------------------------------------- GRADIENT
gradient:
        mov    line_start,cx          ; preserve line start
        lodsw
        mov    bh,al                  ; reverse endian
        mov    bl,ah
        mov    line_end,bx            ; preserve line end
 
        ; calculate the number of line between line_start and line_end
        sub    bx,cx
        mov    line_delta,bx          ; pct_max = line count between line_start and end
 
        lodsb                         ; load red_end
        shr    al,2                   ; reduce to 6 bits only
        mov    red_end,al             ; preserve red target
        mov    ah,al
 
        mov    bl,red_prec            ; bl = red start
        sub    al,bl                  ; end - start
        mov    red_step,al            ; calculate Red Stepping
        mov    red_prec,ah
 
        lodsb                         ; load green_end
        shr    al,2
        mov    green_end,al
        mov    ah,al
        mov    bl,green_prec          ; get green start
        sub    al,bl
        mov    green_step,al          ; calculate green stepping
        mov    green_prec,ah
 
        lodsb                         ; load blue_end
        shr    al,2
        mov    blue_end,al
        mov    ah,al
        mov    bl,blue_prec
        sub    al,bl
        mov    blue_step,al
        mov    blue_prec,ah           ; calculate Blue Stepping
 
gr_start:
        inc    cx                     ; cx = cx+1
        cmp    cx,line_end            ; gradient complet ?
        jb     gradient_hbl
        jmp    start                  ; next operant
 
gradient_hbl:
        mov    dx,03DAh               ; read input state
 
gr_in_retrace:
        in     al,dx                  ; test if we are redrawing
        test   al,1
        jne    gr_in_retrace
 
gr_in_display:
        in     al,dx
        test   al,1                   ; wait for hbl (horizontal return)
        je     gr_in_display
 
        xor    eax,eax
        mov    ax,cx
        sub    ax,line_start          ; pct_current(ax) = currentline(cx) - line_start
        mov    bx,line_delta          ; bx = line_start - line_end
 
        xor    dx,dx
        shl    eax,PRECIS             ; increase precision
        div    bx                     ; pct_current / pct_max
        mov    bx,ax                  ; bx = percentage 0..100
 
        cli
        mov    al,color
        mov    dx,03C8h
        out    dx,al                   ; select color index
        inc    dx
 
        xor    eax,eax
        mov    al,red_step
        imul   bl                     ; must be signed multiplication
        shr    ax,PRECIS
        add    al,red_prec
        out    dx,al                  ; set RED to dac
        mov    red_end,al
 
        xor    ax,ax
        mov    al,green_step
        imul   bl                     ; must be signed multiplication
        shr    ax,PRECIS
        add    al,green_prec
        out    dx,al                  ; set GREEN to dac
        mov    green_end,al
 
        xor    ax,ax
        mov    al,blue_step
        imul   bl                     ; must be signed multiplication
        shr    ax,PRECIS
        add    al,blue_prec
        out    dx,al                  ; set BLUE to dac
        mov    blue_end,al
        sti
 
        jmp    gr_start               ; new line
 
; ------------------------------------- WAIT
wait_line:
        lodsw
        mov    bh,al                   ; swap byte endian encoding craps
        mov    bl,ah                   ; bx = line word
        mov    dx,03DAh                ; input state
 
wait_next:
        inc    cx                      ; cx = cx+1
        cmp    cx,bx                   ; current line>= wait_line ?
        jae    wait_end                ; YES : next operand please
 
in_retrace:
        in     al,dx                   ; read input state, test if we are redrawing
        test   al,1
        jne    in_retrace
in_display:
        in     al,dx
        test   al,1                    ; wait for hbl (horizontal return)
        je     in_display
        jmp    wait_next               ; new line
 
wait_end:
        jmp    start
 
; ------------------------------------- SETCOLOR
set_color:
        cli
        lodsb                          ; get color index
        mov    color,al
        mov    dx,03C8h
        out    dx,al                   ; select color index
        inc    dx                      ; mov   dx,0x3c9
 
        lodsb                          ; get RED level
        shr    al,2
        mov    red_prec,al
        out    dx,al                   ; set RED to dac
 
        lodsb                          ; get GREEN level
        shr    al,2
        mov    green_prec,al
        out    dx,al                   ; set GREEN to dac
 
        lodsb                          ; get BLUE level
        shr    al,2
        mov    blue_prec,al
        out    dx,al                   ; set BLUE to dac
        sti
        jmp    start                   ; get next operand
 
clean_eocl:
        xor    al,al
eocl:
        mov   _copper_error, al        ; set error (if any)
        pop    si
        pop    ds
 
        xor    al,al                   ; normally we should restore whole DAC's status
        mov    dx,03C8h                ; 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
        ret
_DrawCopperList endp
 
      END

le header .h

#ifndef __COPPERL_H__
#define __COPPERL_H__
 
extern unsigned char copper_error;                    // error reporting
extern unsigned int  copper_maxrow;                   // max linescan
 
extern void DrawCopperList(unsigned char *CopperList);
 
#endif