Welcome to part I of the Demoschool. In the demoschool I'll guide you through different demoeffects such as paletterotation, plasma, fire, sprites, 3d-vector-graphics, shading, warping, mode-X, Soundblaster and whatever else that might show up. Most parts of the demoschool will be in mode 13h or MCGA-mode as it is also called. That immediatly brings up the first question:
Offset = 320*y + x
where x is the x-coordinate and y is the y-coordinate. (0,0) is at the upper left corner of the screen and (319,199) is at the bottom right corner of the screen. Here is a table of the coordinates and their respective offsets.
(0,0) (1,0) (2,0) (3,0) . . . (319,0) 0 0 1 2 3 . . . 319 X-axis 1 320 321 322 323 . . . 639 (319,1) 2 640 641 642 643 . . . 959 (319,2) . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 63680 63681 63682 63683 . . . 63999 (0,199) (1,199) (2,199) (3,199) . . . (319,199) Y-axisMode 13h is said to be in a linear memory model that means that the bytes follow each other one after another.
Here comes an unoptimized example in C:
void putpixel(int x,int y,char color) { int offs; offs=320*y + x; //calculate the offset using the given formula asm mov ax,0a000h //you cant move a value directly to es asm mov es,ax //move the screenadress to es asm mov di,offs //and the calculated offset to di asm mov al,color //and the colorbyte to al asm mov [es:di],al //finally move the pixel to the screen }Before putting a pixel to the screen you've gotta be in the MCGA-mode 13h. Switching into mode 13h is done like this:
mov ax,13h ;move the number of the mode to ax int 10h ;and enter the mode using the videointerrupt 10hSwitching back to textmode 3 is done like this
mov ax,3 ;mode 3 is the dos standard mode 80*25 16 color textmode int 10h ;enter textmode 3Using inline assembly in C you could write a function to change the mode like this:
void setmode(int mode) { asm mov ax,mode asm int 10h }To switch into mode 13h you would write setmode(0x0013); at the beginning of main and to switch back into textmode write setmode(3);
To optimize the putpixel routine you could replace the multiplication, which is very slow, with shifts and adds. As you should know, shifting left 1 bit is the same as multiplicating by 2, shiftleft 2 bits is the same as multiplicating by 4 and so on. The trick is to split 320 up into powers of 2, 320 is the same as (256 + 64). Y*320 is the same as (y leftshift 8) + (y leftshift 6). Look at the sourcecode for an implementation of the optimized putpixelroutine using inline-assembler in C.
What colour the pixel will have depends on two things: the colourvalue you write to the screen (0-255) and
what colour that value represents in the palette. The palette has 256 entrys (0 to 255). Each colour in the palette
is represented by three bytes, one each for the Red, Green and Blue component of the colour. Each value can
vary between 0 and 63. The value gives the intensity of the colour, 0 no intensity, 63 strong intensity. This
gives 64^3 possible colours.
Examples of common colours
Red Green Blue colour ______________________________ 0 0 0 BLACK 63 0 0 RED 20 0 0 DARK RED 0 63 0 GREEN 0 0 63 BLUE 63 63 0 YELLOW 63 32 0 ORANGE 63 0 63 PURPLE 0 63 63 CYAN 63 63 63 WHITE 32 32 32 GRAY 10 10 10 DARKGRAY And So OnThe MCGA mode 13h is always started with the same palett (0 black, 1 blue . . .). But it is easy to change the palett by your own. It is done by writing to ports 3c8h and 3c9h. This is not as hard as it sounds. You just write the indexnumber of the first color you intend to change to port 3c8h. And then write the Red Green and Blue values to port 3c9h. After writing 3 bytes to port 3c9h the index automatically increments itself.
If the palett is stored in an array pal (3*256 bytes long). Let's say that colour 0 is black, colour 1 is blue and
colour 2 is gray, then pal will start like this: 0,0,0, 0,0,63, 32,32,32, . . .
Here is a C-function which sets the palette to the values of the array pal:
void setpal(char*pal) { int i; outp(0x3C8,0); //begin with colour 0 for(i=0;i<256*3;i++) outp(0x3C9,pal[i]); //send the palette-data to port 3c9h }The most common way of handling the palette is to make the array pal in a specially written program for the purpose and save the palette to a file on disk. This palette file is easy to recognise because it is 3*256 = 768 bytes big. In the demo you just load the palette file from disk to an array and sets it with the function above.
In assembler you can use a very fast instruction, rep outsb, to set the palette, rep outsb sends CX number of bytes from DS:SI and sends them to port DX. SI is incremented after each byte. Here comes an assembler routine to set the entire palette.
mov dx,03c8h ;move portnumber to dx xor al,al ;reset ax out dx,al ;send 0 to port 3c8h lds si,pal ;setup ds:si to point to pal mov cx,3*256 ;move number of bytes to cx inc dx ;dx = 3c9h rep outsb ;send 768 bytes from pal to port 3c9hA common demoeffect is to repeatidly rotate the array pal and calling setpal(pal). Among other effects you could use this technique to make a static plasma effect. First setup the array pal to contain smooth colour runs between some nice colours. Then draw a nice picture on the screen using the sine and cosine functions and at last rotate the palette and set it over and over again.
The picture doesn't have to be a plasma picture you could draw just any picture and watch how it looks when the palette starts rotating. To rotate the array pal just save the first colour (the first three bytes) and then shift all colours down one step in the array (three bytes) and at last set the last colour to the saved colour. Here's the C-code for rotating the palette. (it actually rotates colours first to last in the palette)
void rotpal(char*pal,int first, int last) { char r,g,b; int i; r=pal[first*3 + 0]; //set r,g,b to the first colour g=pal[first*3 + 1]; b=pal[first*3 + 2]; for(i=first*3;i<(last+1)*3;i++) //move all colours down one step pal[i]=pal[i+3]; pal[last*3+0]=r; //set the last colour to the saved first colour pal[last*3+1]=g; pal[last*3+2]=b; wtsync(); //wait until the electron beam finishes this frame setpal(pal); //set the rotated pal }You don't have to send first and last as parameters to the function but it has some advantages. In the example program I use an array that has more than 256 colours and each time I rotate the entire palette, except colour 0, but I only set the 256 first colours. I don't rotate colour 0 because I dont want the backgroundcolour to change. Try including colour 0 in the rotation to see what I mean. The wtsync function should always be called before changing the palette to avoid "snow" due to bad syncronization with the electron beam. Wtsync waits for the vertical retrace which is when the electron beam has finished one frame and jumps back to draw the next. You could say that the example program is halfway to a real plasma effect, I'll show you a real plasma in a later part of the demoschool.
That sould be all for now, look at the C-source for more comments and the implementation.
Download PART I of the demoschool.
951213 Abe Raham s-mail: Albert Veli Spisringsg. 9 724 76 Västerås SWEDENmail:dat94avi@bilbo.mdh.se.