_GRAPHICS PROGRAMMING COLUMN_ by Michael Abrash [LISTING ONE] /* Looks for a Sierra Hicolor DAC; if one is present, puts the VGA into the specified Hicolor (32K color) mode. Relies on the Tseng Labs ET4000 BIOS and hardware; probably will not work on adapters built around other VGA chips. Returns 1 for success, 0 for failure; failure can result from no Hicolor DAC, too little display memory, or lack of an ET4000. Tested with Borland C++ 2. 0 in C mode in the small model. */ #include #define DAC_MASK 0x3C6 /* DAC pixel mask reg address, also Sierra command reg address when enabled */ #define DAC_WADDR 0x3C8 /* DAC write address reg address */ /* Mode selections: 0x2D=640x350; 0x2E=640x480; 0x2F=640x400; 0x30=800x600 */ int SetHCMode(int Mode) { int i, Temp1, Temp2, Temp3; union REGS regset; /* See if a Sierra SC1148X Hicolor DAC is present, by trying to program and then read back the DAC's command register. (Shouldn't be necessary when using the BIOS Get DAC Type function, but the BIOS function locks up some computers, so it's safer to check the hardware first) */ inp(DAC_WADDR); /* reset the Sierra command reg enable sequence */ for (i=0; i<4; i++) inp(DAC_MASK); /* enable command reg access */ outp(DAC_MASK, 0x00); /* set command reg (if present) to 0x00, and reset command reg enable sequence */ outp(DAC_MASK, 0xFF); /* command reg access no longer enabled; set pixel mask register to 0xFF */ for (i=0; i<4; i++) inp(DAC_MASK); /* enable command reg access */ /* If this is a Hicolor DAC, we should read back the 0 in the command reg; otherwise we get the 0xFF in the pixel mask reg */ i = inp(DAC_MASK); inp(DAC_WADDR); /* reset enable sequence */ if (i == 0xFF) return(0); /* Check for a Tseng Labs ET4000 by poking unique regs, (assumes VGA configured for color, w/CRTC addressing at 3D4/5) */ outp(0x3BF, 3); outp(0x3D8, 0xA0); /* unlock extended registers */ /* Try toggling AC R16 bit 4 and seeing if it takes */ inp(0x3DA); outp(0x3C0, 0x16 | 0x20); outp(0x3C0, ((Temp1 = inp(0x3C1)) | 0x10)); Temp2 = inp(0x3C1); outp(0x3C0, 0x16 | 0x20); outp(0x3C0, (inp(0x3C1) & ~0x10)); Temp3 = inp(0x3C1); outp(0x3C0, 0x16 | 0x20); outp(0x3C0, Temp1); /* restore original AC R16 setting */ /* See if the bit toggled; if so, it's an ET3000 or ET4000 */ if ((Temp3 & 0x10) || !(Temp2 & 0x10)) return(0); outp(0x3D4, 0x33); Temp1 = inp(0x3D5); /* get CRTC R33 setting */ outp(0x3D5, 0x0A); Temp2 = inp(0x3D5); /* try writing to CRTC */ outp(0x3D5, 0x05); Temp3 = inp(0x3D5); /* R33 */ outp(0x3D5, Temp1); /* restore original CRTC R33 setting */ /* If the register was writable, it's an ET4000 */ if ((Temp3 != 0x05) || (Temp2 != 0x0A)) return(0); /* See if a Sierra SC1148X Hicolor DAC is present by querying the (presumably) ET4000-compatible BIOS. Not really necessary after the hardware check above, but generally more useful; in the future it will return information about other high-color DACs */ regset.x.ax = 0x10F1; /* Get DAC Type BIOS function # */ int86(0x10, ®set, ®set); /* ask BIOS for the DAC type */ if (regset.x.ax != 0x0010) return(0); /* function not supported */ switch (regset.h.bl) { case 0: return(0); /* normal DAC (non-Hicolor) */ case 1: break; /* Sierra SC1148X 15-bpp Hicolor DAC */ default: return(0); /* other high-color DAC */ } /* Set Hicolor mode */ regset.x.ax = 0x10F0; /* Set High-Color Mode BIOS function # */ regset.h.bl = Mode; /* desired resolution */ int86(0x10, ®set, ®set); /* have BIOS enable Hicolor mode */ return (regset.x.ax == 0x0010); /* 1 for success, 0 for failure */ } [LISTING TWO] /* Demonstrates non-antialiased drawing in 640x480 Hicolor (32K color) mode on an ET4000-based SuperVGA with a Sierra Hicolor DAC installed. Tested with Borland C++ 2.0 in C mode in the small model. */ #include #include #include "polygon.h" /* Draws the polygon described by the point list PointList in color Color, with all vertices offset by (x,y) */ #define DRAW_POLYGON(PointList,Color,x,y) { \ Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ Polygon.PointPtr = PointList; \ FillCnvxPolyDrvr(&Polygon, Color, x, y, DrawHCLineList);} void main(void); extern int SetHCMode(int); extern int FillCnvxPolyDrvr(struct PointListHeader *, int, int, int, void (*)()); extern void DrawHCLineList(struct HLineList *, int); int BitmapWidthInBytes = 640*2; /* # of bytes per raster line */ void main() { struct PointListHeader Polygon; static struct Point Face0[] = {{396,276},{422,178},{338,88},{288,178}}; static struct Point Face1[] = {{306,300},{396,276},{288,178},{210,226}}; static struct Point Face2[] = {{338,88},{266,146},{210,226},{288,178}}; union REGS regset; /* Attempt to enable 640x480 Hicolor mode */ if (SetHCMode(0x2E) == 0) { printf("No Hicolor DAC detected\n"); exit(0); }; /* Draw the cube */ DRAW_POLYGON(Face0, 0x1F, 0, 0); /* full-intensity blue */ DRAW_POLYGON(Face1, 0x1F << 5, 0, 0); /* full-intensity green */ DRAW_POLYGON(Face2, 0x1F << 10, 0, 0); /* full-intensity red */ getch(); /* wait for a keypress */ /* Return to text mode and exit */ regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ int86(0x10, ®set, ®set); } [LISTING THREE] /* Draws all pixels in the list of horizontal lines passed in, in Hicolor (32K color) mode on an ET4000-based SuperVGA. Uses a slow pixel-by-pixel approach. Tested with Borland C++ 2.0 in C mode in the small model. */ #include #include "polygon.h" #define SCREEN_SEGMENT 0xA000 #define GC_SEGMENT_SELECT 0x3CD void DrawPixel(int, int, int); extern int BitmapWidthInBytes; /* # of pixels per line */ void DrawHCLineList(struct HLineList * HLineListPtr, int Color) { struct HLine *HLinePtr; int Y, X; /* Point to XStart/XEnd descriptor for the first (top) horizontal line */ HLinePtr = HLineListPtr->HLinePtr; /* Draw each horizontal line in turn, starting with the top one and advancing one line each time */ for (Y = HLineListPtr->YStart; Y < (HLineListPtr->YStart + HLineListPtr->Length); Y++, HLinePtr++) { /* Draw each pixel in the current horizontal line in turn, starting with the leftmost one */ for (X = HLinePtr->XStart; X <= HLinePtr->XEnd; X++) DrawPixel(X, Y, Color); } } /* Draws the pixel at (X, Y) in color Color in Hicolor mode on an ET4000-based SuperVGA */ void DrawPixel(int X, int Y, int Color) { unsigned int far *ScreenPtr, Bank; unsigned long BitmapAddress; /* Full bitmap address of pixel, as measured from address 0 to address 0xFFFFF. (X << 1) because pixels are 2 bytes in size */ BitmapAddress = (unsigned long) Y * BitmapWidthInBytes + (X << 1); /* Map in the proper bank. Bank # is upper word of bitmap addr */ Bank = *(((unsigned int *)&BitmapAddress) + 1); /* Upper nibble is read bank #, lower nibble is write bank # */ outp(GC_SEGMENT_SELECT, (Bank << 4) | Bank); /* Draw into the bank */ FP_SEG(ScreenPtr) = SCREEN_SEGMENT; FP_OFF(ScreenPtr) = *((unsigned int *)&BitmapAddress); *ScreenPtr = (unsigned int)Color; } [LISTING FOUR] ; Draws all pixels in the list of horizontal lines passed in, in ; Hicolor (32K color) mode on an ET4000-based SuperVGA. Uses REP STOSW ; to fill each line. Tested with TASM 2.0. C near-callable as: ; void DrawHCLineList(struct HLineList * HLineListPtr, int Color); SCREEN_SEGMENT equ 0a000h GC_SEGMENT_SELECT equ 03cdh HLine struc XStart dw ? ;X coordinate of leftmost pixel in line XEnd dw ? ;X coordinate of rightmost pixel in line HLine ends HLineList struc Lngth dw ? ;# of horizontal lines YStart dw ? ;Y coordinate of topmost line HLinePtr dw ? ;pointer to list of horz lines HLineList ends Parms struc dw 2 dup(?) ;return address & pushed BP HLineListPtr dw ? ;pointer to HLineList structure Color dw ? ;color with which to fill Parms ends ; Advances both the read and write windows to the next 64K bank. ; Note: Theoretically, a delay between IN and OUT may be needed under ; some circumstances to avoid accessing the VGA chip too quickly, but ; in actual practice, I haven't found any delay to be required. INCREMENT_BANK macro push ax ;preserve fill color push dx ;preserve scan line start pointer mov dx,GC_SEGMENT_SELECT in al,dx ;get the current segment select add al,11h ;increment both the read & write banks out dx,al ;set the new bank # pop dx ;restore scan line start pointer pop ax ;restore fill color endm .model small .data extrn _BitmapWidthInBytes:word .code public _DrawHCLineList align 2 _DrawHCLineList proc near push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame push si ;preserve caller's register variables push di cld ;make string instructions inc pointers mov ax,SCREEN_SEGMENT mov es,ax ;point ES to display memory for REP STOS mov si,[bp+HLineListPtr] ;point to the line list mov ax,[_BitmapWidthInBytes] ;point to the start of the mul [si+YStart] ; first scan line on which to draw mov di,ax ;ES:DI points to first scan line to mov al,dl ; draw; AL is the initial bank # ;upper nibble of AL is read bank #, mov cl,4 ; lower nibble is write bank # (only shl dl,cl ; the write bank is really needed for or al,dl ; this module, but it's less confusing ; to point both to the same place) mov dx,GC_SEGMENT_SELECT out dx,al ;set the initial bank mov dx,di ;ES:DX points to first scan line mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor ; for the first (top) horizontal line mov si,[si+Lngth] ;# of scan lines to draw and si,si ;are there any lines to draw? jz FillDone ;no, so we're done mov ax,[bp+Color] ;color with which to fill mov bp,[_BitmapWidthInBytes] ;so we can keep everything ; in registers inside the loop ;***stack frame pointer destroyed!*** FillLoop: mov di,[bx+XStart] ;left edge of fill on this line mov cx,[bx+XEnd] ;right edge of fill sub cx,di jl LineFillDone ;skip if negative width inc cx ;# of pixels to fill on this line add di,di ;*2 because pixels are 2 bytes in size add dx,bp ;do we cross a bank during this line? jnc NormalFill ;no jz NormalFill ;no ;yes, there is a bank crossing on this ; line; figure out where sub dx,bp ;point back to start of line add di,dx ;offset of left edge of fill jc CrossBankBeforeFilling ;raster splits before the left ; edge of fill add cx,cx ;fill width in bytes (pixels * 2) add di,cx ;do we split during the fill area? jnc CrossBankAfterFilling ;raster splits after the right jz CrossBankAfterFilling ; edge of fill ;bank boundary falls within fill area; ; draw in two parts, one in each bank sub di,cx ;point back to start of fill area neg di ;# of bytes left before split sub cx,di ;# of bytes to fill to the right of ; the bank split push cx ;remember right-of-split fill width mov cx,di ;# of left-of-split bytes to fill shr cx,1 ;# of left-of-split words to fill neg di ;offset at which to start filling rep stosw ;fill left-of-split portion of line pop cx ;get back right-of-split fill width shr cx,1 ;# of right-of-split words to fill ;advance to the next bank INCREMENT_BANK ;point to the next bank (DI already ; points to offset 0, as desired) rep stosw ;fill right-of-split portion of line add dx,bp ;point to the next scan line jmp short CountDownLine ; (already advanced the bank) ;====================================================================== align 2 ;dfill area is entirely to the left of CrossBankAfterFilling: ; the bank boundary sub di,cx ;point back to start of fill area shr cx,1 ;CX = fill width in pixels jmp short FillAndAdvance ;doesn't split until after the ; fill area, so handle normally ;====================================================================== align 2 ;fill area is entirely to the right of CrossBankBeforeFilling: ; the bank boundary INCREMENT_BANK ;first, point to the next bank, where ; the fill area resides rep stosw ;fill this scan line add dx,bp ;point to the next scan line jmp short CountDownLine ; (already advanced the bank) ;====================================================================== align 2 ;no bank boundary problems; just fill NormalFill: ; normally sub dx,bp ;point back to start of line add di,dx ;offset of left edge of fill FillAndAdvance: rep stosw ;fill this scan line LineFillDone: add dx,bp ;point to the next scan line jnc CountDownLine ;didn't cross a bank boundary INCREMENT_BANK ;did cross, so point to the next bank CountDownLine: add bx,size HLine ;point to the next line descriptor dec si ;count off lines to fill jnz FillLoop FillDone: pop di ;restore caller's register variables pop si pop bp ;restore caller's stack frame ret ;====================================================================== _DrawHCLineList endp end [LISTING FIVE] /* Demonstrates unweighted antialiased drawing in 640x480 Hicolor (32K color) mode. Tested with Borland C++ 2.0 in C mode in the small model. */ #include #include #include #include #include "polygon.h" /* Draws the polygon described by the point list PointList in the color specified by RED, GREEN, AND BLUE, with all vertices offset by (x,y), to ScanLineBuffer, at ResMul multiple of horizontal and vertical resolution. The address of ColorTemp is cast to an int to satisfy the prototype for FillCnvxPolyDrvr; this trick will work only in a small data model */ #define DRAW_POLYGON_HIGH_RES(PointList,RED,GREEN,BLUE,x,y,ResMul) { \ Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ Polygon.PointPtr = PointTemp; \ /* Multiply all vertical & horizontal coordinates */ \ for (k=0; k 0xFFFF) { printf("Band won't fit in one segment\n"); exit(0); } if ((ScanLineBuffer = malloc((int)BufferSize)) == NULL) { printf("Couldn't get memory\n"); exit(0); } /* Attempt to enable 640x480 Hicolor mode */ if (SetHCMode(0x2E) == 0) { printf("No Hicolor DAC detected\n"); exit(0); }; /* Scan out the polygons at high resolution one screen scan line at a time (ResolutionMultiplier high-res scan lines at a time) */ for (i=TopBound; i<=BottomBound; i++) { /* Set the band dimensions for this pass */ ScanBandEnd = (ScanBandStart = i*ResolutionMultiplier) + ResolutionMultiplier - 1; /* Clear the drawing buffer */ memset(ScanLineBuffer, 0, BufferSize); /* Draw the current band of the cube to the scan line buffer */ DRAW_POLYGON_HIGH_RES(Face0,0xFF,0,0,0,0,ResolutionMultiplier); DRAW_POLYGON_HIGH_RES(Face1,0,0xFF,0,0,0,ResolutionMultiplier); DRAW_POLYGON_HIGH_RES(Face2,0,0,0xFF,0,0,ResolutionMultiplier); /* Coalesce subpixels into normal screen pixels (megapixels) and draw them */ for (j=LeftBound; j<=RightBound; j++) { jXRes = j*ResolutionMultiplier; /* For each screen pixel, sum all the corresponding subpixels, for each color component */ for (k=Red=Green=Blue=0; kYStart, i; struct RGB *BufferPtr, *WorkingBufferPtr; /* Done if fully off the bottom or top of the band */ if (YStart > ScanBandEnd) return; Length = HLineListPtr->Length; if ((YStart + Length) <= ScanBandStart) return; /* Point to XStart/XEnd descriptor for the first (top) horizontal line */ HLinePtr = HLineListPtr->HLinePtr; /* Confine drawing to the specified band */ if (YStart < ScanBandStart) { /* Skip ahead to the start of the band */ Length -= ScanBandStart - YStart; HLinePtr += ScanBandStart - YStart; YStart = ScanBandStart; } if (Length > (ScanBandEnd - YStart + 1)) Length = ScanBandEnd - YStart + 1; /* Point to the start of the first scan line on which to draw */ BufferPtr = ScanLineBuffer + (YStart-ScanBandStart)*ScanBandWidth; /* Draw each horizontal line within the band in turn, starting with the top one and advancing one line each time */ while (Length-- > 0) { /* Fill whole horiz line with Color if it has positive width */ if ((Width = HLinePtr->XEnd - HLinePtr->XStart + 1) > 0) { WorkingBufferPtr = BufferPtr + HLinePtr->XStart; for (i = 0; i < Width; i++) *WorkingBufferPtr++ = *Color; } HLinePtr++; /* point to next scan line X info */ BufferPtr += ScanBandWidth; /* point to start of next line */ } } [LISTING SEVEN] /* POLYGON.H: Header file for polygon-filling code */ /* Describes a single point (used for a single vertex) */ struct Point { int X; /* X coordinate */ int Y; /* Y coordinate */ }; /* Describes a series of points (used to store a list of vertices that describe a polygon; each vertex is assumed to connect to the two adjacent vertices, and the last vertex is assumed to connect to the first) */ struct PointListHeader { int Length; /* # of points */ struct Point * PointPtr; /* pointer to list of points */ }; /* Describes the beginning and ending X coordinates of a single horizontal line */ struct HLine { int XStart; /* X coordinate of leftmost pixel in line */ int XEnd; /* X coordinate of rightmost pixel in line */ }; /* Describes a Length-long series of horizontal lines, all assumed to be on contiguous scan lines starting at YStart and proceeding downward (used to describe scan-converted polygon to low-level hardware-dependent drawing code)*/ struct HLineList { int Length; /* # of horizontal lines */ int YStart; /* Y coordinate of topmost line */ struct HLine * HLinePtr; /* pointer to list of horz lines */ }; /* Describes a color as an RGB triple, plus one byte for other info */ struct RGB { unsigned char Red, Green, Blue, Spare; };