_GRAPHICS PROGRAMMING COLUMN_ by Michael Abrash [LISTING ONE] ; Mode X (320x240, 256 colors) system memory-to-display memory masked copy ; routine. Not particularly fast; images for which performance is critical ; should be stored in off-screen memory and copied to screen via latches. Works ; on all VGAs. Copies up to but not including column at SourceEndX and row at ; SourceEndY. No clipping is performed. Mask and source image are both byte- ; per-pixel, and must be of same widths and reside at same coordinates in their ; respective bitmaps. Assembly code tested with TASM 2.0. C near-callable as: ; void CopySystemToScreenMaskedX(int SourceStartX, ; int SourceStartY, int SourceEndX, int SourceEndY, ; int DestStartX, int DestStartY, char * SourcePtr, ; unsigned int DestPageBase, int SourceBitmapWidth, ; int DestBitmapWidth, char * MaskPtr); SC_INDEX equ 03c4h ;Sequence Controller Index register port MAP_MASK equ 02h ;index in SC of Map Mask register SCREEN_SEG equ 0a000h ;segment of display memory in mode X parms struc dw 2 dup (?) ;pushed BP and return address SourceStartX dw ? ;X coordinate of upper left corner of source ; (source is in system memory) SourceStartY dw ? ;Y coordinate of upper left corner of source SourceEndX dw ? ;X coordinate of lower right corner of source ; (the column at EndX is not copied) SourceEndY dw ? ;Y coordinate of lower right corner of source ; (the row at EndY is not copied) DestStartX dw ? ;X coordinate of upper left corner of dest ; (destination is in display memory) DestStartY dw ? ;Y coordinate of upper left corner of dest SourcePtr dw ? ;pointer in DS to start of bitmap which source resides DestPageBase dw ? ;base offset in display memory of page in ; which dest resides SourceBitmapWidth dw ? ;# of pixels across source bitmap (also must ; be width across the mask) DestBitmapWidth dw ? ;# of pixels across dest bitmap (must be multiple of 4) MaskPtr dw ? ;pointer in DS to start of bitmap in which mask ; resides (byte-per-pixel format, just like the source ; image; 0-bytes mean don't copy corresponding source ; pixel, 1-bytes mean do copy) parms ends RectWidth equ -2 ;local storage for width of rectangle RectHeight equ -4 ;local storage for height of rectangle LeftMask equ -6 ;local storage for left rect edge plane mask STACK_FRAME_SIZE equ 6 .model small .code public _CopySystemToScreenMaskedX _CopySystemToScreenMaskedX proc near push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars push si ;preserve caller's register variables push di mov ax,SCREEN_SEG ;point ES to display memory mov es,ax mov ax,[bp+SourceBitmapWidth] mul [bp+SourceStartY] ;top source rect scan line add ax,[bp+SourceStartX] mov bx,ax add ax,[bp+SourcePtr] ;offset of first source rect pixel mov si,ax ; in DS add bx,[bp+MaskPtr] ;offset of first mask pixel in DS mov ax,[bp+DestBitmapWidth] shr ax,1 ;convert to width in addresses shr ax,1 mov [bp+DestBitmapWidth],ax ;remember address width mul [bp+DestStartY] ;top dest rect scan line mov di,[bp+DestStartX] mov cx,di shr di,1 ;X/4 = offset of first dest rect pixel in shr di,1 ; scan line add di,ax ;offset of first dest rect pixel in page add di,[bp+DestPageBase] ;offset of first dest rect pixel ; in display memory and cl,011b ;CL = first dest pixel's plane mov al,11h ;upper nibble comes into play when plane wraps ; from 3 back to 0 shl al,cl ;set the bit for the first dest pixel's plane mov [bp+LeftMask],al ; in each nibble to 1 mov ax,[bp+SourceEndX] ;calculate # of pixels across sub ax,[bp+SourceStartX] ; rect jle CopyDone ;skip if 0 or negative width mov [bp+RectWidth],ax sub word ptr [bp+SourceBitmapWidth],ax ;distance from end of one source scan line to start of next mov ax,[bp+SourceEndY] sub ax,[bp+SourceStartY] ;height of rectangle jle CopyDone ;skip if 0 or negative height mov [bp+RectHeight],ax mov dx,SC_INDEX ;point to SC Index register mov al,MAP_MASK out dx,al ;point SC Index reg to the Map Mask inc dx ;point DX to SC Data reg CopyRowsLoop: mov al,[bp+LeftMask] mov cx,[bp+RectWidth] push di ;remember the start offset in the dest CopyScanLineLoop: cmp byte ptr [bx],0 ;is this pixel mask-enabled? jz MaskOff ;no, so don't draw it ;yes, draw the pixel out dx,al ;set the plane for this pixel mov ah,[si] ;get the pixel from the source mov es:[di],ah ;copy the pixel to the screen MaskOff: inc bx ;advance the mask pointer inc si ;advance the source pointer rol al,1 ;set mask for next pixel's plane adc di,0 ;advance destination address only when ; wrapping from plane 3 to plane 0 loop CopyScanLineLoop pop di ;retrieve the dest start offset add di,[bp+DestBitmapWidth] ;point to the start of the ; next scan line of the dest add si,[bp+SourceBitmapWidth] ;point to the start of the ; next scan line of the source add bx,[bp+SourceBitmapWidth] ;point to the start of the ; next scan line of the mask dec word ptr [bp+RectHeight] ;count down scan lines jnz CopyRowsLoop CopyDone: pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables pop bp ;restore caller's stack frame ret _CopySystemToScreenMaskedX endp end [LISTING TWO] ; Mode X (320x240, 256 colors) display memory to display memory masked copy ; routine. Works on all VGAs. Uses approach of reading 4 pixels at a time from ; source into latches, then writing latches to destination, using Map Mask ; register to perform masking. Copies up to but not including column at ; SourceEndX and row at SourceEndY. No clipping is performed. Results are not ; guaranteed if source and destination overlap. C near-callable as: ; void CopyScreenToScreenMaskedX(int SourceStartX, ; int SourceStartY, int SourceEndX, int SourceEndY, ; int DestStartX, int DestStartY, MaskedImage * Source, ; unsigned int DestPageBase, int DestBitmapWidth); SC_INDEX equ 03c4h ;Sequence Controller Index register port MAP_MASK equ 02h ;index in SC of Map Mask register GC_INDEX equ 03ceh ;Graphics Controller Index register port BIT_MASK equ 08h ;index in GC of Bit Mask register SCREEN_SEG equ 0a000h ;segment of display memory in mode X parms struc dw 2 dup (?) ;pushed BP and return address SourceStartX dw ? ;X coordinate of upper left corner of source SourceStartY dw ? ;Y coordinate of upper left corner of source SourceEndX dw ? ;X coordinate of lower right corner of source ; (the column at SourceEndX is not copied) SourceEndY dw ? ;Y coordinate of lower right corner of source ; (the row at SourceEndY is not copied) DestStartX dw ? ;X coordinate of upper left corner of dest DestStartY dw ? ;Y coordinate of upper left corner of dest Source dw ? ;pointer to MaskedImage struct for source ; which source resides DestPageBase dw ? ;base offset in display memory of page in ; which dest resides DestBitmapWidth dw ? ;# of pixels across dest bitmap (must be multiple of 4) parms ends SourceNextScanOffset equ -2 ;local storage for distance from end of ; one source scan line to start of next DestNextScanOffset equ -4 ;local storage for distance from end of ; one dest scan line to start of next RectAddrWidth equ -6 ;local storage for address width of rectangle RectHeight equ -8 ;local storage for height of rectangle SourceBitmapWidth equ -10 ;local storage for width of source bitmap ; (in addresses) STACK_FRAME_SIZE equ 10 MaskedImage struc Alignments dw 4 dup(?) ;pointers to AlignedMaskedImages for the ; 4 possible destination image alignments MaskedImage ends AlignedMaskedImage struc ImageWidth dw ? ;image width in addresses (also mask width in bytes) ImagePtr dw ? ;offset of image bitmap in display memory MaskPtr dw ? ;pointer to mask bitmap in DS AlignedMaskedImage ends .model small .code public _CopyScreenToScreenMaskedX _CopyScreenToScreenMaskedX proc near push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars push si ;preserve caller's register variables push di cld mov dx,GC_INDEX ;set the bit mask to select all bits mov ax,00000h+BIT_MASK ; from the latches and none from out dx,ax ; the CPU, so that we can write the ; latch contents directly to memory mov ax,SCREEN_SEG ;point ES to display memory mov es,ax mov ax,[bp+DestBitmapWidth] shr ax,1 ;convert to width in addresses shr ax,1 mul [bp+DestStartY] ;top dest rect scan line mov di,[bp+DestStartX] mov si,di shr di,1 ;X/4 = offset of first dest rect pixel in shr di,1 ; scan line add di,ax ;offset of first dest rect pixel in page add di,[bp+DestPageBase] ;offset of first dest rect pixel ; in display memory. now look up the image that's ; aligned to match left-edge alignment of destination and si,3 ;DestStartX modulo 4 mov cx,si ;set aside alignment for later shl si,1 ;prepare for word look-up mov bx,[bp+Source] ;point to source MaskedImage structure mov bx,[bx+Alignments+si] ;point to AlignedMaskedImage ; struc for current left edge alignment mov ax,[bx+ImageWidth] ;image width in addresses mov [bp+SourceBitmapWidth],ax ;remember image width in ; addresses mul [bp+SourceStartY] ;top source rect scan line mov si,[bp+SourceStartX] shr si,1 ;X/4 = address of first source rect pixel in shr si,1 ; scan line add si,ax ;offset of first source rect pixel in image mov ax,si add si,[bx+MaskPtr] ;point to mask offset of first mask pixel in DS mov bx,[bx+ImagePtr] ;offset of first source rect pixel add bx,ax ; in display memory mov ax,[bp+SourceStartX] ;calculate # of addresses across add ax,cx ; rect, shifting if necessary to add cx,[bp+SourceEndX] ; account for alignment cmp cx,ax jle CopyDone ;skip if 0 or negative width add cx,3 and ax,not 011b sub cx,ax shr cx,1 shr cx,1 ;# of addresses across rectangle to copy mov ax,[bp+SourceEndY] sub ax,[bp+SourceStartY] ;AX = height of rectangle jle CopyDone ;skip if 0 or negative height mov [bp+RectHeight],ax mov ax,[bp+DestBitmapWidth] shr ax,1 ;convert to width in addresses shr ax,1 sub ax,cx ;distance from end of one dest scan line to start of next mov [bp+DestNextScanOffset],ax mov ax,[bp+SourceBitmapWidth] ;width in addresses sub ax,cx ;distance from end of source scan line to start of next mov [bp+SourceNextScanOffset],ax mov [bp+RectAddrWidth],cx ;remember width in addresses mov dx,SC_INDEX mov al,MAP_MASK out dx,al ;point SC Index register to Map Mask inc dx ;point to SC Data register CopyRowsLoop: mov cx,[bp+RectAddrWidth] ;width across CopyScanLineLoop: lodsb ;get the mask for this four-pixel set ; and advance the mask pointer out dx,al ;set the mask mov al,es:[bx] ;load the latches with 4-pixel set from source mov es:[di],al ;copy the four-pixel set to the dest inc bx ;advance the source pointer inc di ;advance the destination pointer dec cx ;count off four-pixel sets jnz CopyScanLineLoop mov ax,[bp+SourceNextScanOffset] add si,ax ;point to the start of add bx,ax ; the next source, mask, add di,[bp+DestNextScanOffset] ; and dest lines dec word ptr [bp+RectHeight] ;count down scan lines jnz CopyRowsLoop CopyDone: mov dx,GC_INDEX+1 ;restore the bit mask to its default, mov al,0ffh ; which selects all bits from the CPU out dx,al ; and none from the latches (the GC ; Index still points to Bit Mask) pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables pop bp ;restore caller's stack frame ret _CopyScreenToScreenMaskedX endp end [LISTING THREE] /* Generates all four possible mode X image/mask alignments, stores image alignments in display memory, allocates memory for and generates mask alignments, and fills out an AlignedMaskedImage structure. Image and mask must both be in byte-per-pixel form, and must both be of width ImageWidth. Mask maps isomorphically (one to one) onto image, with each 0-byte in mask masking off corresponding image pixel (causing it not to be drawn), and each non-0-byte allowing corresponding image pixel to be drawn. Returns 0 if failure, or # of display memory addresses (4-pixel sets) used if success. For simplicity, allocated memory is not deallocated in case of failure. Compiled with Borland C++ 2.0 in C compilation mode. */ #include #include #include "maskim.h" extern void CopySystemToScreenX(int, int, int, int, int, int, char *, unsigned int, int, int); unsigned int CreateAlignedMaskedImage(MaskedImage * ImageToSet, unsigned int DispMemStart, char * Image, int ImageWidth, int ImageHeight, char * Mask) { int Align, ScanLine, BitNum, Size, TempImageWidth; unsigned char MaskTemp; unsigned int DispMemOffset = DispMemStart; AlignedMaskedImage *WorkingAMImage; char *NewMaskPtr, *OldMaskPtr; /* Generate each of the four alignments in turn */ for (Align = 0; Align < 4; Align++) { /* Allocate space for the AlignedMaskedImage struct for this alignment */ if ((WorkingAMImage = ImageToSet->Alignments[Align] = malloc(sizeof(AlignedMaskedImage))) == NULL) return 0; WorkingAMImage->ImageWidth = (ImageWidth + Align + 3) / 4; /* width in 4-pixel sets */ WorkingAMImage->ImagePtr = DispMemOffset; /* image dest */ /* Download this alignment of the image */ CopySystemToScreenX(0, 0, ImageWidth, ImageHeight, Align, 0, Image, DispMemOffset, ImageWidth, WorkingAMImage->ImageWidth * 4); /* Calculate the number of bytes needed to store the mask in nibble (Map Mask-ready) form, then allocate that space */ Size = WorkingAMImage->ImageWidth * ImageHeight; if ((WorkingAMImage->MaskPtr = malloc(Size)) == NULL) return 0; /* Generate this nibble oriented (Map Mask-ready) alignment of the mask, one scan line at a time */ OldMaskPtr = Mask; NewMaskPtr = WorkingAMImage->MaskPtr; for (ScanLine = 0; ScanLine < ImageHeight; ScanLine++) { BitNum = Align; MaskTemp = 0; TempImageWidth = ImageWidth; do { /* Set the mask bit for next pixel according to its alignment */ MaskTemp |= (*OldMaskPtr++ != 0) << BitNum; if (++BitNum > 3) { *NewMaskPtr++ = MaskTemp; MaskTemp = BitNum = 0; } } while (--TempImageWidth); /* Set any partial final mask on this scan line */ if (BitNum != 0) *NewMaskPtr++ = MaskTemp; } DispMemOffset += Size; /* mark off the space we just used */ } return DispMemOffset - DispMemStart; } [LISTING FOUR] /* MASKIM.H: structures used for storing and manipulating masked images */ /* Describes one alignment of a mask-image pair */ typedef struct { int ImageWidth; /* image width in addresses in display memory (also mask width in bytes) */ unsigned int ImagePtr; /* offset of image bitmap in display mem */ char *MaskPtr; /* pointer to mask bitmap */ } AlignedMaskedImage; /* Describes all four alignments of a mask-image pair */ typedef struct { AlignedMaskedImage *Alignments[4]; /* ptrs to AlignedMaskedImage structs for four possible destination image alignments */ } MaskedImage; [LISTING FIVE] /* Sample mode X VGA animation program. Portions of this code first appeared in PC Techniques. Compiled with Borland C++ 2.0 in C compilation mode. */ #include #include #include #include #include "maskim.h" #define SCREEN_SEG 0xA000 #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 240 #define PAGE0_START_OFFSET 0 #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4) #define BG_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH*2)/4) #define DOWNLOAD_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH*3)/4) static unsigned int PageStartOffsets[2] = {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; static char GreenAndBrownPattern[] = {2,6,2,6, 6,2,6,2, 2,6,2,6, 6,2,6,2}; static char PineTreePattern[] = {2,2,2,2, 2,6,2,6, 2,2,6,2, 2,2,2,2}; static char BrickPattern[] = {6,6,7,6, 7,7,7,7, 7,6,6,6, 7,7,7,7,}; static char RoofPattern[] = {8,8,8,7, 7,7,7,7, 8,8,8,7, 8,8,8,7}; #define SMOKE_WIDTH 7 #define SMOKE_HEIGHT 7 static char SmokePixels[] = { 0, 0,15,15,15, 0, 0, 0, 7, 7,15,15,15, 0, 8, 7, 7, 7,15,15,15, 8, 7, 7, 7, 7,15,15, 0, 8, 7, 7, 7, 7,15, 0, 0, 8, 7, 7, 7, 0, 0, 0, 0, 8, 8, 0, 0}; static char SmokeMask[] = { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0}; #define KITE_WIDTH 10 #define KITE_HEIGHT 16 static char KitePixels[] = { 0, 0, 0, 0,45, 0, 0, 0, 0, 0, 0, 0, 0,46,46,46, 0, 0, 0, 0, 0, 0,47,47,47,47,47, 0, 0, 0, 0,48,48,48,48,48,48,48, 0, 0, 49,49,49,49,49,49,49,49,49, 0, 0,50,50,50,50,50,50,50, 0, 0, 0,51,51,51,51,51,51,51, 0, 0, 0, 0,52,52,52,52,52, 0, 0, 0, 0, 0,53,53,53,53,53, 0, 0, 0, 0, 0, 0,54,54,54, 0, 0, 0, 0, 0, 0, 0,55,55,55, 0, 0, 0, 0, 0, 0, 0, 0,58, 0, 0, 0, 0, 0, 0, 0, 0, 0,59, 0, 0, 0, 0,66, 0, 0, 0, 0,60, 0, 0,64, 0,65, 0, 0, 0, 0, 0,61, 0, 0,64, 0, 0, 0, 0, 0, 0, 0,62,63, 0,64}; static char KiteMask[] = { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1}; static MaskedImage KiteImage; #define NUM_OBJECTS 20 typedef struct { int X,Y,Width,Height,XDir,YDir,XOtherPage,YOtherPage; MaskedImage *Image; } AnimatedObject; AnimatedObject AnimatedObjects[] = { { 0, 0,KITE_WIDTH,KITE_HEIGHT, 1, 1, 0, 0,&KiteImage}, { 10, 10,KITE_WIDTH,KITE_HEIGHT, 0, 1, 10, 10,&KiteImage}, { 20, 20,KITE_WIDTH,KITE_HEIGHT,-1, 1, 20, 20,&KiteImage}, { 30, 30,KITE_WIDTH,KITE_HEIGHT,-1,-1, 30, 30,&KiteImage}, { 40, 40,KITE_WIDTH,KITE_HEIGHT, 1,-1, 40, 40,&KiteImage}, { 50, 50,KITE_WIDTH,KITE_HEIGHT, 0,-1, 50, 50,&KiteImage}, { 60, 60,KITE_WIDTH,KITE_HEIGHT, 1, 0, 60, 60,&KiteImage}, { 70, 70,KITE_WIDTH,KITE_HEIGHT,-1, 0, 70, 70,&KiteImage}, { 80, 80,KITE_WIDTH,KITE_HEIGHT, 1, 2, 80, 80,&KiteImage}, { 90, 90,KITE_WIDTH,KITE_HEIGHT, 0, 2, 90, 90,&KiteImage}, {100,100,KITE_WIDTH,KITE_HEIGHT,-1, 2,100,100,&KiteImage}, {110,110,KITE_WIDTH,KITE_HEIGHT,-1,-2,110,110,&KiteImage}, {120,120,KITE_WIDTH,KITE_HEIGHT, 1,-2,120,120,&KiteImage}, {130,130,KITE_WIDTH,KITE_HEIGHT, 0,-2,130,130,&KiteImage}, {140,140,KITE_WIDTH,KITE_HEIGHT, 2, 0,140,140,&KiteImage}, {150,150,KITE_WIDTH,KITE_HEIGHT,-2, 0,150,150,&KiteImage}, {160,160,KITE_WIDTH,KITE_HEIGHT, 2, 2,160,160,&KiteImage}, {170,170,KITE_WIDTH,KITE_HEIGHT,-2, 2,170,170,&KiteImage}, {180,180,KITE_WIDTH,KITE_HEIGHT,-2,-2,180,180,&KiteImage}, {190,190,KITE_WIDTH,KITE_HEIGHT, 2,-2,190,190,&KiteImage}, }; void main(void); void DrawBackground(unsigned int); void MoveObject(AnimatedObject *); extern void Set320x240Mode(void); extern void FillRectangleX(int, int, int, int, unsigned int, int); extern void FillPatternX(int, int, int, int, unsigned int, char*); extern void CopySystemToScreenMaskedX(int, int, int, int, int, int, char *, unsigned int, int, int, char *); extern void CopyScreenToScreenX(int, int, int, int, int, int, unsigned int, unsigned int, int, int); extern unsigned int CreateAlignedMaskedImage(MaskedImage *, unsigned int, char *, int, int, char *); extern void CopyScreenToScreenMaskedX(int, int, int, int, int, int, MaskedImage *, unsigned int, int); extern void ShowPage(unsigned int); void main() { int DisplayedPage, NonDisplayedPage, Done, i; union REGS regset; Set320x240Mode(); /* Download the kite image for fast copying later */ if (CreateAlignedMaskedImage(&KiteImage, DOWNLOAD_START_OFFSET, KitePixels, KITE_WIDTH, KITE_HEIGHT, KiteMask) == 0) { regset.x.ax = 0x0003; int86(0x10, ®set, ®set); printf("Couldn't get memory\n"); exit(); } /* Draw the background to the background page */ DrawBackground(BG_START_OFFSET); /* Copy the background to both displayable pages */ CopyScreenToScreenX(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, BG_START_OFFSET, PAGE0_START_OFFSET, SCREEN_WIDTH, SCREEN_WIDTH); CopyScreenToScreenX(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, BG_START_OFFSET, PAGE1_START_OFFSET, SCREEN_WIDTH, SCREEN_WIDTH); /* Move the objects and update their images in the nondisplayed page, then flip the page, until Esc is pressed */ Done = DisplayedPage = 0; do { NonDisplayedPage = DisplayedPage ^ 1; /* Erase each object in nondisplayed page by copying block from background page at last location in that page */ for (i=0; iX + ObjectToMove->XDir; Y = ObjectToMove->Y + ObjectToMove->YDir; if ((X < 0) || (X > (SCREEN_WIDTH - ObjectToMove->Width))) { ObjectToMove->XDir = -ObjectToMove->XDir; X = ObjectToMove->X + ObjectToMove->XDir; } if ((Y < 0) || (Y > (SCREEN_HEIGHT - ObjectToMove->Height))) { ObjectToMove->YDir = -ObjectToMove->YDir; Y = ObjectToMove->Y + ObjectToMove->YDir; } /* Remember previous location for erasing purposes */ ObjectToMove->XOtherPage = ObjectToMove->X; ObjectToMove->YOtherPage = ObjectToMove->Y; ObjectToMove->X = X; /* set new location */ ObjectToMove->Y = Y; } [LISTING SIX] ; Shows the page at the specified offset in the bitmap. Page is displayed when ; this routine returns. This code first appeared in PC Techniques. ; C near-callable as: void ShowPage(unsigned int StartOffset); INPUT_STATUS_1 equ 03dah ;Input Status 1 register CRTC_INDEX equ 03d4h ;CRT Controller Index reg START_ADDRESS_HIGH equ 0ch ;bitmap start address high byte START_ADDRESS_LOW equ 0dh ;bitmap start address low byte ShowPageParms struc dw 2 dup (?) ;pushed BP and return address StartOffset dw ? ;offset in bitmap of page to display ShowPageParms ends .model small .code public _ShowPage _ShowPage proc near push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame ; Wait for display enable to be active (status is active low), to be ; sure both halves of the start address will take in the same frame. mov bl,START_ADDRESS_LOW ;preload for fastest mov bh,byte ptr StartOffset[bp] ; flipping once display mov cl,START_ADDRESS_HIGH ; enable is detected mov ch,byte ptr StartOffset+1[bp] mov dx,INPUT_STATUS_1 WaitDE: in al,dx test al,01h jnz WaitDE ;display enable is active low (0 = active) ; Set the start offset in display memory of the page to display. mov dx,CRTC_INDEX mov ax,bx out dx,ax ;start address low mov ax,cx out dx,ax ;start address high ; Now wait for vertical sync, so the other page will be invisible when ; we start drawing to it. mov dx,INPUT_STATUS_1 WaitVS: in al,dx test al,08h jz WaitVS ;vertical sync is active high (1 = active) pop bp ;restore caller's stack frame ret _ShowPage endp end [LISTING SEVEN] ;;; ;;; mov dx,SC_INDEX+1 ;point to Sequence Controller Data reg ;;; ; (SC Index still points to Map Mask) ;;;<...to this> mov dx,SC_INDEX mov al,MAP_MASK out dx,al ;point SC Index reg to Map Mask inc dx ;point to SC Data reg