Först och främst vill jag berätta att från och med detta avsnitt byter assemblerskolan namn till demoskolan.Det är dock bra att gå igenom assemblerskolan först eftersom en grundläggande kunskap i assembler förutsätts i demoskolan. I demoskolan kommer jag att gå igenom olika demoeffekter som palettrotation, plasma, 3d-vektorgrafik, fire, sprites osv. de flesta programmen kommer att vara skrivna för grafikläge 13h. Detta ger direkt den första frågan:
(0,0) (1,0) (2,0) (3,0) 0 1 2 3 . . . 319 (319,0) 320 321 322 323 . . . 639 (319,1) 640 641 642 643 . . . 959 (319,2) . . . . . . . . . . . . . . . . . . . . . . . . 63680 63681 63682 63683 . . . 63999 (319,199) (0,199) (1,199) (2,199) (3,199)Siffrorna inom parenteserna är koordinaterna (x,y) och siffrorna i "tabellen" är offseterna. Prova t ex med (3,2) detta ger enligt formeln offsetten 320*2 + 3 = 643 vilket ju stämmer enligt tabellen. Att rita ut färg 3 vid (3,2) görs alltså genom att flyt ta värdet 3 till adressen 0a000h:643.
void putpixel(int x,int y,char color) { int offs; offs=320*y + x; asm{ mov ax,0a000h //går ej att flytta värde direkt till es mov es,ax //segmentadr till es mov di,offs //den beräknade offsetten till di mov al,color //färgen till al stosb //flytta en byte från al till es:di } }Detta exempel ritar ut en punkt med färgen color vid (x,y) på skärmen. Exemplet förutsätter att man först befinner sig i läge 13h. Detta görs enklast via interrupt 10h. Fundera inte så mycket på detta nu, skriv bara:
mov ax,13h ;flytta numret på grafikläget till ax int 10h ;och utför interrupt 10hi början av ditt assemblerprogram. För att växla tillbaka till textläge igen skriv:
mov ax,3 ;vanlig dos-textmod har nummer 3 int 10hi slutet på assemblerprogrammet.
void setmode(int mode) { asm{ mov ax,mode int 10h } }I main skriver man sedan setmode(0x0013) för att växla till grafikläge 13h och setmode(3) för att växla tillbaka till texläge 3.
Som jag nämnde går det att optimera putpixel-rutinen, detta görs genom att ersätta y*320 med shift och add instruktioner (se koden) som är mycket snabbare, 1 shiftinstruktion tar en klockcykel medan en mul instruktion kan ta ända upp till 219 klockcykler. Tips: y*320 = 256*y + 64*y. Att multiplicera med en jämn 2-potens görs snabbast genom att shifta åt vänster (shl).
Vilken färg som punkten man ritar ut har beror av två saker, dels vilket nummer (värde) man skriver till skärmminnet (0-255), dels av vilken färg det numret motsvarar. Om vi skriver putpixel(40,1,4) kommer färg nummer 4 att sättas vid punkten (40,1). Vilk en färg som färg 4 är bestäms av paletten. Paletten innehåller 256 färger och vilken färg var och en av de färgerna motsvarar. En färg byggs upp av tre variabler:
Rött Grönt Blått Färg 0 0 0 =SVART 63 0 0 =RÖTT 20 0 0 =MÖRKRÖTT 0 63 0 =GRÖNT 0 0 63 =BLÅTT 63 63 0 =GULT 63 32 0 =ORANGE 63 0 63 =VIOLETT 0 63 63 =CYAN 63 63 63 =VITT 32 32 32 =MELLANGRÅTT 10 10 10 =MÖRKGRÅTT Och Så VidareMod 13h startas alltid med samma palett. Det går att ändra på den själv. Detta görs genom att skriva direkt till portarna 3C8h och 3C9h på grafikkortet. Låter det komplicerat? Det är det inte!. Låt säga att palletten som vi vill göra aktiv ligger lagrad i arrayen pal. Eftersom det är 256 färger och varje färg består av 3 bytes (R,G,B) blir pal 3*256 bytes lång. Om färg 0 är svart, färg 1 är blått och färg 2 grått, kommer pal att börja så här: 0, 0,0, 0,0,63, 32,32,32 . . . Tre bytes för varje färg alltså. Här kommer en C-funktion för att sätta paletten pal:
void setpal(char*pal) { int i; outp(0x3C8,0); //skicka värdet 0 till port 3c8h for(i=0;i<256*3;i++) outp(0x3C9,pal[i]); //skicka palettdata till port 3c9h }Att skicka 0 till port 3c8h betyder att vi ska skriva till paletten och att vi ska börja med färg 0. Efter att man skickat ett färgnummer till port 3c8h, är det bara att skicka 3 st bytes till port 3c9h, en för rött, en för grönt och en för blått, i den o rdningen. Om man skickar mer än 3 värden räknas pallettnumret automatiskt upp.
outp(0x3c8,5); outp(0x3c9,32); //sätter färg 5, Rött till 32 outp(0x3c9,32); //sätter färg 5, Grönt till 32 outp(0x3c9,32); // 5, Blått 32 outp(0x3c9,0); //sätter färg 6, Rött till 0 outp(0x3c9,63); //sätter färg 6, Grönt till 63 outp(0x3c9,0); //sätter färg 6, Blått till 0 outp(0x3c9,10); //sätter färg 7, Rött till 10 Och Så VidareDet smartaste är att skriva ett program som man gör en hel palett med, och sparar den i en fil. Sen kan man bara ladda in paletten och sätta den med ovanstående funktion. Det är ganska vanligt att det följer med palett-filer med olika demoexempel och prog ram, man kan känna igen dem genom att de är 3*256 = 768 bytes stora. Dessutom brukar dom ha ändelsen .pal eller .col.
I assembler kan man utnyttja en mycket snabb instruktion när man sätter paletten, rep outsb. Den skickar ut CX antal bytes till porten DX från DS:SI och räknar upp si för varje gång.
Assemblerrutin för att sätta paletten:
mov dx,03c8h ;portnummer till dx xor al,al ;al = 0 out dx,al ;samma som outp(0x3c8,0) i C lds si,pal ;låt ds:si peka på paletten som ska sättas mov cx,3*256 ;antal bytes till CX inc dx ;dx = 3c9h rep outsb ;skicka 768 bytes från pal till port 3c9hEn rolig effekt är att upprepade gånger rotera arrayen där palettdata är lagrade och sätta paletten. Detta är en mycket användbar och mycket använd teknik. Ett exempel på användningsområde är att göra en sk statisk plasma. Statisk plasma görs genom att ri ta ut en plasmabild på skärmen, sätta en palett med mjuka färgövergångar och sedan rotera och sätta paletten upprepade gånger.
Bilden behöver inte vara en plasma-bild, det går bra med vilken bild som helst. Kod exemplet ritar ut en "3-dimensionell" sin/cos kurva på skärmen och roterar sedan paletten. De 3 dimensionerna består av Bredd, Höjd och Färg. Alltså egentligen bara 2-dime
nsionellt med färg som 3:dje dimension. Man kan säga att exemplet är halvvägs påväg mot en plasma effekt, jag täker gå igenom en äkta plasma i ett senare avsnitt i demoskolan.
Här är koden för att rotera paletten i C:
void rotpal(char*pal,int first, int last) { char r,g,b; int i; r=pal[first*3 + 0]; //sätt r,g,b till första färgen (färg 0) g=pal[first*3 + 1]; b=pal[first*3 + 2]; for(i=first*3;i<(last+1)*3;i++) //flytta ner alla färger ett steg pal[i]=pal[i+3]; pal[last*3+0]=r; //sätt sista färgen (255) till r,g,b pal[last*3+1]=g; pal[last*3+2]=b; wtsync(); setpal(pal); }Funktionen sparar första färgen som ska roteras, flyttar ner alla färger ett steg och sätter in den sparade på sista platsen. Sen väntar den på skärmsynk vilket gör att skärmen inte flimrar och till sist sätts den roterade paletten. Man behöver ej skicka first och last som parametrar, det går utmärkt att skriva en funktion som roterar alla färgerna t ex. Det har dock vissa fördelar att kunna ange vilka färger som ska roteras. Till exempel kan man rotera alla färger utom färg 0 vilket gör att bakgrunden in te ändrar färg eller man kan använda en större palett än 256 färger och varje gång sätta de 256 första färgerna. just det har jag gjort i den medföljande koden. Där använder jag en palett på 512 färger och roterar färg 1 - 511 varje skärmuppdatering. Det gör att det tar längre tid innan färgerna upprepas.
Det finns inte så mycket mer att säga, kommentarerna i koden borde förklara resten. Experimentera gärna med koden och se vad som händer, det är det bästa sättet att lära sig på.
Ladda hem demoskolan del 1. Innehåller c-koden, denna text och exe-fil ifall kompileringen av någon anledning skulle gå snett.
Abe-raham Breadfan s-mail: Albert Veli Spisringsg. 9 724 76 Västeråsmail:dat94avi@bilbo.mdh.se.