Albert's demoskola

Del 1,mod 13h


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:

Vad är läge 13h?

Svaret är enkelt. Läge 13h är det grafikläge som de flesta pc-demos och spel är skrivna i (Ja ja, jag vet att det finns demos och spel i svga men jag sa ju de flesta). Skärmen i läge 13h har dimensionerna

320*200

Detta innebär att skärmen är 320 p unkter (pixels) bred och 200 punkter hög. Varje punkt kan anta någon utav

256 olika färger.

Som ni vet är 256 exakt det antal värden en byte kan anta. Detta innebär att varje punkt i mod 13h kan representeras av en byte, och det gör dom också. By tarna ligger linjärt i minnet precis som i textläge 3. Som ni kanske kommer ihåg startar skärmminnet på segmentadress 08b00h i läge 3, men dock ej i läge 13h. I läge 13h startar skärmminnet på segmentadress:

0A000h.

Denna segmentadress gäller för d e flesta grafiklägen inte bara 13h. För att rita ut en punkt på skärmen behövs även en offset. Offseten beräknas enligt:

offset=320*y +x

Där x är x-koordinaten för punkten och y är y-koordinaten för punkten. Observera att (0,0) (= offset 320*0 + 0= 0) är längst uppe i vänstra hörnet på skärmen. Sen ökar det från vänster till höger precis som bokstäverna i en bok. När en rad är slut fortsätter man längst till vänster på nästa rad när man läser en bok, så fungerar det här också.
Så här är skärmen uppbyggd i läge 13h:
(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.
o-optimerat exempel i c som finns i optimerad version i koden:
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 10h
i 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	10h
i slutet på assemblerprogrammet.
I C kan man göra så här:
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 och Blått

Var och en av dessa kan variera mellan

0 - 63

Siffran anger färgens intensitet, 0=inget alls, 63=starkt. Detta ger 64^3 möjliga färger.
Exempel på några färger
	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å	Vidare
Mod 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.
Ex:
 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å Vidare
Det 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 3c9h
En 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ås
mail:dat94avi@bilbo.mdh.se.

Tillbaka till min hemsida.