CopperList avancée
Dans nos deux derniers articles, nous avons appris à faire des dégradés et ensuite a 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 meme 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ée 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ée de rouge, vert et bleu sont divisée 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ée 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'excécution des copper s'arretent avec un code d'erreur.
EOC: 0xFF
Cette instruction n'est pas modifiée.
Le Code
#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.
Le build-in assembler de borland C++ n'est pas capable de faire de sauts conditionnel au delà d'un delta de -126 +127; car l'oppérande n'est que de 2 octets, 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 ceci:
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
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
#define __COPPERL_H__
extern unsigned char copper_error; // error reporting
extern unsigned int copper_maxrow; // max linescan
extern void DrawCopperList(unsigned char *CopperList);
#endif
Littératures:
TBC