Hello and welcome to part 3 of Abe's Demoschool. Last part was pretty messy so this time I'll show you how to split a demo up into several files and link them all together into one executable file. In this part we'll also cover sprite's and how to show a TGA picture. I think it's enough with the TGA format since most drawing programs can convert pictures into the TGA format. The TGA format is also one of the most simple formats.
To make the sprite routines as fast as possible I used 32-bit instructions (movsd) that works with 4 bytes at a time. Because of that, the number of pixels in the x-direction has to be divisible by 4 (ie 4,8,16,20,32,64). The sprites can have any number of pixels in the y-direction. As usual we are in mode 13h (MCGA) which means there are 256 colors and that every pixel in a sprite is one byte in memory.
This is the way the sprites are stored in memory: First 2 bytes (one integer) telling the height of the sprite followed by 2 bytes telling the width of the sprite. After that comes all the pixels in the sprites from left to right, top to bottom. Every pixel is one byte. This is called a raw-format. Some raw formats have the width first but I have the height first because it fits the sprite routines better.
EX: A sprite with height 20 pixels and width 16 pixels (16 is divisble by 4)
20, 0, 16, 0 //first the height and width //then the raw data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 Row 1 16 bytes 0,0,0,0,0,34,56,6,6,56,34,0,0,0,0,0 Row 2 16 bytes And So On for 18 more rows.
It is too much work to enter all the sprite-data by hand. You have to use some kind of drawing program to draw the sprites. With the drawing program save the sprite in TGA-format. Be shure to save it in 256-color mode TGA-format. Then convert it from 256-color-TGA-format to raw-sprite-format with tga2spr which comes with abedemo3.zip. I wrote the tga2spr program a long time ago and it isn't very nice but it works. If you want to make the code faster and clearer, please do, and mail me a copy of the result.
Here are the sprite routines that comes with the source code:
loadsprite( Name, Sprite); //loads a sprite from disk show32spr( x, y, Sprite, Destination); //Shows a sprite (using fast 32-bit instructions) //at (x,y) on the Destination showtrans( x, y, Sprite, Destination); //As show32spr but with color 0 transparent erase32bit( x, y, Width, Height, Destination); //erases a rectangular area on the Destination get32spr(x, y, source, sprite, width, height); //gets a sprite from the source //Source is either the visible screen or a virtual screen
Loadsprite loads a sprite to Sprite. Sprite is an array that has to be at
least as big as the sprite it will contain.
Ex:
char Sprite[2 + MAXWIDTH*MAXHEIGHT]; loadsprite("mysprite.spr", Sprite);
show32spr copies the sprite to (x,y) at the destination with the help of the
32-bit instruction movsd
Ex:
screen=0x0a0000000; show32spr(10,20, Sprite, screen);
screen=0x0a0000000 makes screen a pointer to the screen (A000:0000), this also gives a warning when you compile the program but who cares, it's works (on my compiler). Then Sprite is drawn with the top left corner at (10,20) to the screen.
Showtrans shows a sprite with color 0 transparent. Wherever the sprite has the color 0 the background will shine through the sprite. Showtrans is slower than show32spr, so if the background is black use show32spr instead.
Erase32bit draws a black rectangle with the upper left corner at (x,y) on the Destination with width Width and height Height :-). It is used to erase a sprite if the background is black. If the background is not black you have to save the background in a temporary backgroundsprite before you draw a sprite to the screen. To erase it you just draw the saved background over the sprite.
Get32spr is used to save the background as described above.
!CAUTION These routines all suppose that the screen is 320 bytes in width.
!CAUTION If you use a virtual screen, it has to be 320 bytes in width.
All the spriteroutines are in the file asmdemo3.asm and they contain comments.
The easiest way is to draw all the sprites and pictures width the same program and the same palette. But if you want to use a scanned picture or make sprites in ie 3d Studio then the sprites and pictures will use one palette each. Because of this problem I wrote a program that takes one palette and one tga-picture and then transformes the tga-picture to use the closest colors in the palette. I also wrote a program that takes one palette and one sprite with it's palette and transformes the sprite to use the colors in the first palette. To use the programs first make a palette that contains as many colors as possible of the colors in the sprites and pictures that will use the palette. Then transform the sprites and pictures with the programs mentioned above (and below). These programs are called fittga and fitspr and also comes with abedemo3.zip. They are even worse coded and really needs a facelift. If anyone does this, please send me a copy. I'm sure there must be shareware programs that can accomplish the same task much better, If you should stumble over one please mail the adress to me.
To use fittga and fitspr you have to manually change the filenames in the beginning of the main function. You could change this to make the program take the filenames as parameters to the program instead.
Look at the file tga.txt for more information on the TGA-format.
To combine a file written in assembler with a file written in C: assemble the assembly-file, compile the c-file and link the .obj files to an .exe file.
A function written in assembler is called exactly like a function written in C from the C-program. Just tell the compiler that there are functions written outside of the C-file whit the command extern.
Ex, suppose the wtsync() function is written in assembler in another file. Then the C-program will call it like this:
#includeAnd the assembler file will look like:/* Don't include stdio.h if you don't have to */ /* it'll only increase the size of the .exe */ extern void wtsync(void); /* tell the compiler that the wtsync */ /* code is somewhere else */ void main(void) { . . . wtsync(); /* wtsync is called the same way as usual */ . . . }
P386N ;allow 386-instructions IDEAL ;use tasm's ideal mode MODEL small ;(data < 64k) and (code < 64k) CODESEG PUBLIC _wtsync ;make wtsync public so that ;it can be called from the c-program PROC _wtsync NEAR ;NEAR means that the PROCess is ;NEAR callable, that is that the code . ;for the PROCess is in the same segment . ;as the caller's code. This is always . ;the case if you use model small ret ;don't forget the ret or else the ;program will crash ENDP _wtsync ENDNotice the _ before wtsync in the assemblerfile. You have to add an _ before the function name in the assembler file to make it callable from c. Don't write the _ in the c-file. Suppose wtsync is written in the file asmtest.asm and the c-file is ctest.c. There are a number of ways to build the .exe. I usually compile the assembler file/files with tasm and use Borland's command line compiler, bcc for the c-source:
With Borland C++ write: tasm /ml asmtest bcc -c ctest bcc -ms ctest.obj asmtest.obj With Turbo C write: tasm /ml asmtest tcc -c ctest tcc -ms ctest.obj asmtest.objtasm /ml makes the .obj of the assemblerfile. /ml tells tasm to turn on case sensitivity, this is because C uses case sensitivity.
bcc -c makes the .obj of the cfile. -c means only compile, no linking.
bcc -ms links the objectfiles to an executable .exe-file. -ms tells bcc to use memory model small.
The result is a file called ctest.exe because ctest.obj was befor asmtest.obj at the time of the linking.
That's it. There are lots of advantages writing some of the functions in pure assembler. One advantage is that we can use 386 specific instructions like movsd. That isn't possible with inline assembler like we used in part 2. The C-file will also be a lot smaller, which makes it easier to read and understand. The disadvantage is the writing of the assembler-funktions, that can be hard if you're not used to it (I give you the assembler-functions for free to use and enhance). I have learned to master turbo assembler with the help of the book: mastering turbo assembler by Tom Swan.
ARG xpos:word, . . . ;put all arguments here push bp ;this has to do with the stack mov bp,sp ;don't bother too much about it right now . ;just put push bp and mov bp,sp first in . ;the routine and pop bp last . ;if you're using arguments . mov ax,[xpos] ;use the argument (YES IT IS THIS SIMPLE) . ;remember the braces [] . . pop bp ret
* Sending returnvalues back to the C-program:
Returnvalues are sent in ax and dx depending on the size
SIZE RETURNREGISTER EXAMPLE ON DATATYPE Byte al char Word (2 byte) ax int Dword (4 byte) dx:ax char far*
* Always save ds, si and di on the stack if you change them in the routine
Or else the program will probably chrash because C uses ds,si and di. You are allowed to change AX, BX, CX, DX and ES without saving them. It doesn't matter for the C-program.
* Avoid local variables in the assembler routines. Use registers instead, they are much faster. If you have to use local directve LOCAL which works like the ARG directive.
You may have noticed that I used ideal mode in the wtsync example. Ideal mode only works with TASM. There are only small differencies in IDEAL mode. The differences makes IDEAL mode easier to read and understand. I think of the differences as improvements. Ideal mode is getting pretty big on the internet too, many of the newer assembly-sources you find on internet are in ideal mode.
EX: some differences in Ideal mode
Normal Ideal mode .386 p386n .MODEL small MODEL small .DATA DATASEG .CODE CODESEG name PROC NEAR PROC name NEAR name ENDP ENDP name
A GREAT advantage with Ideal mode is the ARG directive which makes parameters to assembly routines as easy to handle as parameters to a C-function. Like I mentioned there is also the LOCAL directive for local variables.
After the dissolve effekt comes the main loop where a transparent sprite bounces across the screen. All the drawing and erasing is made on the virtual screen, when one frame is done the virtual screen is flipped over to the visible screen and the next frame is calculated . . . The main loop follows these steps:
1. Save the background where the sprite will be drawn 2. Draw the sprite 3. Flip virt over to the screen 4. Erase by drawing the saved background over the sprite on the virtual screen 5. calculate new coordinates for the sprite, GoTo 1
Before the program is finished the allocated memory must be released using the free function which also is declared in asmdemo3.asm.
The Dissolve effekt uses an interesting algoithm which gives all numbers between 1 - FFFFh in random order, no number comes up more than once before all numbers have come up !!!
PSEUDOCODE:
set the offset to any number but zero (ie 1) for(i=0;i<0x0FFFF;i++) { Do something with the offset (like flip one point to the) . (screen using the offset) . . Shift the offset right 1 bit (like dividing by 2) if the shifted bit was 1 (if carry is set) then offset = offset xor 0x0b400 }
This way offset will take on all values between 1 - FFFFh. Don't ask me how, but it really works. I got the idea from Graphic Gems I side 221.
See the file targa.txt for a description of the TGA-format.
The next part will be about 2d and 3d vectorgraphics. I'm currently optimizing the routine that draws a filled triangle to the screen, hopefully it'll get fast. You lucky weezers will get the routines served on a silverplate, but hey, it's the 90's.
Most computers today have hardwaresupport for drawing sprites (Already the Commodore 64 had it). It's usually called something like bitblt and that means Bit Blast. On the PC, most graphic cards also do have bitblt, but there's no standard so you can't use the BitBlt. Because it'll only work on your own computer and the computers that have similar graphiccard to your one. But if you want to try, there's information about various cards in the book "Programming the Ega, Vga and SuperVga Cards" by Richard Ferraro.
Download abedemo3.zip
Sprit'em Says: Sir Abe elecronic mail: dat94avi@bilbo.mdh.se snail mail: Albert Veli Spisringsg. 9 724 76 V�ster�s
Back to the Demoschool.