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.
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).
Aucune modification n'est apportée a cette instruction.
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.
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.
Cette instruction n'est pas modifiée.
#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); }
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.
.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
#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
Tutoriaux disponible