Listing 1. A 256-Color Sprite /* ======================================================================== */ /* COMPLBMP.C - Routine to compile a 256-color bitmap image for */ /* Mode X or Mode 13h. */ /* Author: Matt Pritchard for Game Developer Magazine. */ /* Adapted from MODEX108 */ /* ======================================================================== */ /* This stuff could go into a .h file */ /* Macro Definitions needed by Compile_Bitmap */ #define ucharf unsigned char far #define uchar unsigned char #define uint unsigned int #define hi_word( x ) (unsigned char) (x >> 8) #define lo_word( x ) (unsigned char) (x & 0x00FF) /* Prototypes for Compiled Bitmap Routines */ ucharf * Compile_Bitmap (ucharf *, int, int, int, int); void far pascal draw_compiled_bitmap (uchar far *, int, ints); void far pascal draw_compiled_bitmap_13h (uchar far *, int, ints); /* ееееееееееееееееееееееееееееееееееееее*/ /* This function takes a Sprite that is stored in a two-dimensional array, such as char ImageData[32][32], and creates a buffer that contains the machine language code to quickly draw that image in Mode 13h or Mode X. The sprite data is stored line by line, from top to bottom. Each line is stored from left to right. A transparant color value is used to indicate which pixels are not part of the image and should not be drawn. Because Mode X supports various screen sizes, we must know the width of the screen a sprite will be displayed on in advance. For Mode 13h, that width is normally 320. When possible, two adjacent pixels will be drawn with one 16-bit MOV instruction. This results in smaller and faster code. This function allocates a buffer to hold the compiled code and returns a far pointer to it. The pointer need only be a char type pointer, since our assembly language routine does the actual calling of it. If the sprite is too big or the program has run out of memory, a null pointer is returned, otherwise a pointer to the compiled code is returned. */ ucharf * Compile_Bitmap (ucharf * theImage, /* Far Ptr to the Sprite */ int X_width, /* Width of the Sprite */ int Y_width, /* Height of the Sprite */ int Trans_Color, /* Transparent Color */ int Screen_Width) /* Width of the screen */ { int x, y, p; /* Loop counters for X, Y, and plane */ int Words, Bytes; /* Count of each type of instruction */ int b1, b2; /* Valid pixel flags */ uint VidOffset, Offset; /* Offsets for memory calculations */ int BytesPerLine; /* Width of display in address bytes */ long CompiledBufferSize; /* The size of the compiled sprite code*/ long c; /* Counter for the code writing loop */ ucharf * theBuffer; /* Pointer to the compiled code buffe */ int Num_Planes; /* The number of video planes (4 or 1)*/ int Next_Pixel; /* The number of bytes between adjacent pixel*/ int Code_Overhead; /* Size of any overhead code needed */ /* The variable Mode_X controls if we are compiling a sprite for Mode 13h or Mode X. For Mode X, we must split the image into four separate planes and add plane switching code to the compiled sprite. If the value of Mode_X is 0, we compile for Mode 13h, otherwise we compile for Mode X. */ int Mode_X = -1; /* -1 = Mode X, 0 = Mode 13h */ if (Mode_X) { Num_Planes = 4; Next_Pixel = 4; Code_Overhead = 20; } else { Num_Planes = 1; Next_Pixel = 1; Code_Overhead = 5; } BytesPerLine = Screen_Width / Num_Planes; /* First, we pass through the bitmap and count up the number of adjacent pixel pairs and the number of single pixels. With this information, we will know how big a buffer to allocate. */ Words = Bytes = 0; for (p = 0; p < Num_Planes; p++) { for (y = 0; y < Y_width; y++) { Offset = y * X_width; for (x = p; x < X_width; x+=Next_Pixel) { /* Check the current pixel to see if it should be displayed */ b1 = (theImage[Offset+x] != Trans_Color) ? -1 : 0; /* Check the next adjacent pixel (if there is one), and see if it should also be displayed */ if ((x + Next_Pixel) < X_width) { b2 = (theImage[Offset+x+Next_Pixel] != Trans_Color) ? -1 : 0; } else { b2 = 0; } /* Check for a pair of adjacent pixels, or a lone single pixel */ if (b1) { if (b2) { Words++; /* Another adjacent pixel pair */ x+=Next_Pixel; /* Skip over the next pixel */ } else { Bytes++; /* One more lone pixel */ } } } } } /* Determine how big a buffer we need for the compiled code, allocate it, and get a far pointer to it. */ CompiledBufferSize = Code_Overhead + (6 * Words) + (5 * Bytes); /* Here is where the users can insert their own error handling code */ if (CompiledBufferSize > 65535) { /* Error; compiled sprite would be too large (greater than 64K). */ return (0); } if ( (theBuffer = (ucharf *) malloc( (size_t) CompiledBufferSize)) == 0) { /* Error allocating buffer; out of memory. */ return (0); } /* Now, we go through the image again, this time creating the code to write into the compile code buffer. */ c = 0; for (p = 0; p < Num_Planes ; p++) { for (y = 0; y < Y_width; y++) { Offset = y * X_width; for (x = p; x < X_width; x+=Next_Pixel) { /* Check the current pixel to see if it should be displayed */ b1 = (theImage[Offset+x] != Trans_Color) ? -1 : 0; /* Check the next adjacent pixel (if there is one), and see if it should also be displayed */ if ((x + Next_Pixel) < X_width) { b2 = (theImage[Offset+x+Next_Pixel] != Trans_Color) ? -1 : 0; } else { b2 = 0; } /* Generate code for a pair of pixels, or for a single pixel. */ if (b1) { VidOffset = (BytesPerLine * y) + ((x-p) / Num_Planes); if (b2) { /* Create Code to write Word Constant */ theBuffer[c++] = 0xC7; /* MOV word ptr */ theBuffer[c++] = 0x87; theBuffer[c++] = lo_word( VidOffset ); /* BX+VidOffset */ theBuffer[c++] = hi_word( VidOffset ); theBuffer[c++] = (uchar) theImage[Offset+x]; theBuffer[c++] = (uchar) theImage[Offset+x+Next_Pixel]; x+=Next_Pixel; /* Skip over second pixel*/ } else { /* Create Code to write Byte Constant*/ theBuffer[c++] = 0xC6; /* MOV byte ptr */ theBuffer[c++] = 0x87; theBuffer[c++] = lo_word( VidOffset ); /* BX+VidOffset */ theBuffer[c++] = hi_word( VidOffset ); theBuffer[c++] = (uchar) theImage[Offset+x]; } } } } if ( (Mode_X) && (p < 3) ) { /* Generate plane switching code*/ theBuffer[c++] = 0xD0; /* ROL AL, 1 ; Get New mask*/ theBuffer[c++] = 0xC0; theBuffer[c++] = 0x13; /* ADC BX, CX ; Add in Addr wrap*/ theBuffer[c++] = 0xD9; theBuffer[c++] = 0xEE; /* OUT DX, AL ; Select new Plane*/ } } /* Create exit code to return to the calling program */ theBuffer[c++] = 0x5D; /* POP BP ; Restore BP */ theBuffer[c++] = 0x1F; /* POP DS ; Restore DS */ theBuffer[c++] = 0xCA; /* RETF 8 ; Exit & Clean Up Stack*/ theBuffer[c++] = 0x08; theBuffer[c++] = 0x00; /* Return a pointer to the Buffer containing the Compiled Code */ return (theBuffer); } Listing 2. Compiled Sprite Setup and Call Routine ; =========================================================; ; COMPLBMP.ASM - Compiled Sprite Setup & Call Routines for; ; Mode X or Mode 13h. ; ; Author; Matt Pritchard for Game Developer Magazine. ; ; Adapted from MODEX108 ; ; Assembler Used; MASM 5.10a ; ; =========================================================; .MODEL Medium .286 .CODE ; ===== General Constants & Macros ===== wp EQU WORD PTR dp EQU DWORD PTR fp EQU FAR PTR ; ===== VGA Register Values & Constants ===== VGA_Segment EQU 0A000h ;Vga Memory Segment SC_Index EQU 03C4h ; VGA Sequencer Controller SC_Data EQU 03C5h ; VGA Sequencer Data Port MAP_MASK_PLANE2 EQU 01102h ; Map Register + Plane 1 PLANE_BITS EQU 03h ; Bits 0-1 of Xpos = Plane# ;========================================================== ; DRAW_COMPILED_BITMAP (CompiledImage, X_pos, Y_Pos) ;========================================================== ; ; Sets up a call to a compiled bitmap in Mode X. ; ; ENTRY; Image = Far Pointer to Compiled Bitmap Data ; Xpos = X position to Place Upper Left pixel at ; Ypos = Y position to Place Upper Left pixel at ; ; EXIT; No meaningful values returned ; DCB_STACK STRUC DW ?,? ; DS, BP DD ? ; Caller DCB_Ypos DW ? ; Y position to Draw Bitmap at DCB_Xpos DW ? ; X position to Draw Bitmap at DCB_Image DD ? ; Far Pointer to Graphics Bitmap DCB_STACK ENDS PUBLIC DRAW_COMPILED_BITMAP DRAW_COMPILED_BITMAP PROC FAR Push DS ; Save DS Push BP ; AX-DX are destroyed Mov BP, SP ; Set up Stack Frame ; Get DS;BX to point to (Xpos,Ypos) on the current ; display page in VGA memory ; ***** USER NOTE ***** MODIFY AS NEEDED ***** ; ; Line_Offset is lookup table containing the start ; offset for each line in VGA display memory. ; Here, I assume it to be a table of word values ; which are stored in the current code segment. Mov BX, [BP].DCB_Ypos ; BX = Ypos Add BX, BX ; Scale BX to Word Offset Mov BX, wp CS;Line_Offset[BX] ; Get Offset of Line Ypos Mov AX, [BP].DCB_Xpos ; Get UL Corner Xpos Mov CL, AL ; Save Plane # in CL Shr AX, 2 ; X/4 = Offset Into Line ; ***** USER NOTE ***** MODIFY AS NEEDED ***** ; ; CURRENT PAGE is a DWORD pointer to the currently active ; Mode X video memory page. The first word is the offset ; into the video adaptor, and the second is the constant ; value of A000 - the VGAOs graphics memory segment. ; Here, I assume it to be in DGROUP. Lds DX, dp CURRENT_PAGE ; Get Current VGA Page Add BX, DX ; DS;BX->Start of Line Add BX, AX ; DS;BX->Upper Left Pixel ; Select the first video plane, and set up the registers ; so the next 3 planes can be quickly selected. And CL, PLANE_BITS ; CL = Starting Plane # Mov AX, MAP_MASK_PLANE2 ; Mask & Plane Select Shl AH, CL ; Select correct Plane Mov DX, SC_Index ; VGA Sequencer ports Out DX, AX ; Set Initial Vid Plane Inc DX ; Point DX to SC_Data Mov AL, AH ; Mask for future OUTOs Clr CX ; CX = Constant 0 ; Setup DS; BX = Upper left corner of Image in VGA memory ; BP = Local Stack Frame ; AL = OUT mask for Selecting video Plane ; CX = Constant value 0 for ADC ; DX = SC_Data; VGA Sequencer Data Port ; AH = Destroyed ; SI,DI = Not modified during call ; ; Now we jump to the compiled code which actually draws the ; sprite. The compiled code will return to the caller. Jmp dp [BP].DCB_Image ; Draw Sprite DRAW_COMPILED_BITMAP ENDP ;============================================================= ; DRAW_COMPILED_BITMAP_13h (CompiledImage, X_pos, Y_Pos) ;============================================================= ; ; Sets up a call to a compiled bitmap in Mode 13h. ; ; ENTRY; Image = Far Pointer to Compiled Bitmap Data ; Xpos = X position to Place Upper Left pixel at ; Ypos = Y position to Place Upper Left pixel at ; ; EXIT; No meaningful values returned ; PUBLIC DRAW_COMPILED_BITMAP_13H DRAW_COMPILED_BITMAP_13H PROC FAR Push DS ; Save DS Push BP ; AX-DX are destroyed Mov BP,, SP ; Set up Stack Frame ; Get DS;BX to point to (Xpos, Ypos) in VGA memory ; ***** USER NOTE ***** MODIFY AS NEEDED ***** ; ; Line_Offset is lookup table containing the start ; offset for each line in VGA display memory. ; Here, I assume it to be a table of word values ; which are stored in the current code segment. Mov BX, [BP].DCB_Ypos ; BX = Ypos Add BX, BX ; Scale BX to Word Offset Mov BX, wp CS;Line_Offset[BX] ; Get Offset of Line Ypos Add BX, BP].DCB_Xpos ; Get UL Corner of Sprite Mov AX, VGA_Segment ; Segment A000 Mov DS, AX ; DS;BX -> VGA memory ; Setup DS; BX = Upper left corner of Image in VGA memory ; BP = Local Stack Frame ; AX = Destroyed ; SI, DI = Not modified during call ; CX, DX = Not modified during call ; ; Now we jump to the compiled code which actually draws the ; sprite. The compiled code will return to the caller. Jmp dp [BP]. DCB_Image ; Draw Sprite DRAW_COMPILED_BITMAP_13H ENDP END