diff --git a/modules/pcjs/lib/video.js b/modules/pcjs/lib/video.js index 71ea591db..faea31751 100644 --- a/modules/pcjs/lib/video.js +++ b/modules/pcjs/lib/video.js @@ -507,14 +507,15 @@ Video.MODE = { CGA_320X200_BW: 5, CGA_640X200: 6, MDA_80X25: 7, - EGA_320X200: 0x0D, // mapped at A000:0000 - EGA_640X200: 0x0E, // mapped at A000:0000 - EGA_640X350_MONO: 0x0F, // mapped at A000:0000, monochrome - EGA_640X350: 0x10, // mapped at A000:0000, color - VGA_640X480_MONO: 0x11, // mapped at A000:0000, monochrome - VGA_640X480: 0x12, // mapped at A000:0000, color - VGA_320X200: 0x13, // mapped at A000:0000, color - VGA_320X400: 0x26, + EGA_320X200: 0x0D, // mapped at A000:0000, color, 4bpp, planar + EGA_640X200: 0x0E, // mapped at A000:0000, color, 4bpp, planar + EGA_640X350_MONO: 0x0F, // mapped at A000:0000, mono, 2bpp, planar + EGA_640X350: 0x10, // mapped at A000:0000, color, 4bpp, planar + VGA_640X480_MONO: 0x11, // mapped at A000:0000, mono, 2bpp, planar + VGA_640X480: 0x12, // mapped at A000:0000, color, 4bpp, planar + VGA_320X200: 0x13, // mapped at A000:0000, color, 8bpp, linear + VGA_320X240: 0x78, // mapped at A000:0000, color, 8bpp, planar ("Mode X") + VGA_320X400: 0x7A, // mapped at A000:0000, color, 8bpp, planar UNKNOWN: 0xFF }; @@ -636,8 +637,8 @@ Video.monitorSpecs[ChipSet.MONITOR.VGACOLOR] = { * 0x0: return SW4 * * These 4 bits are also copied to the byte at 40:88h by the EGA BIOS, where bit 0 is SW1, bit 1 is SW2, - * bit 2 is SW3 and bit 3 is SW4. Our switch settings come from bEGASwitches, which in turn comes from sSwitches, - * which in turn comes from the "switches" property passed to the Video component, if any. + * bit 2 is SW3 and bit 3 is SW4. Our switch settings come from bEGASwitches, which in turn comes from + * sSwitches, which in turn comes from the "switches" property passed to the Video component, if any. * * As usual, the switch settings are reversed in both direction and sense from the switch settings; the * good news, however, is that we can use the parseSwitches() method in the ChipSet component to parse them. @@ -675,7 +676,7 @@ Video.aEGAMonitorSwitches = { * Supported Fonts * * Once we've finished loading the standard 8K font file, aFonts[] should contain one or more of the - * fonts listed below. For the standard MDA/CGA font ROM, the first (MDA) font resides in the first 4Kb, + * entries listed below. For the standard MDA/CGA font ROM, the first (MDA) font resides in the first 4Kb, * and the second and third (CGA) fonts reside in the two 2K halves of the second 4Kb. * * It may seem odd that the cell size for FONT_CGAD is *larger* than the cell size for FONT_CGA, @@ -707,12 +708,16 @@ Video.FONT = { * * 0: # of columns (nCols) * 1: # of rows (nRows) - * 2: # cells per word (nCellsPerWord: # of characters or pixels per 16-bit word) + * 2: # cells per word (nCellsPerWord: # of characters or pixels per word) * 3: # bytes of visible screen padding, if any (used for CGA graphics modes only) * 4: font ID (nFont: undefined if graphics mode) * - * By calculating ([0] * [1]) / [2], we obtain the number of 16-bit words that mode actively displays; - * for example, the amount of visible memory used by mode 0x04 is (320 * 200) / 4, or 16000. + * For MDA and CGA modes, a "word" of memory is 16 bits of CPU-addressable data, so by calculating + * ([0] * [1]) / [2], we obtain the number of words that mode actively displays; for example, the + * amount of visible memory used by mode 0x04 is (320 * 200) / 4, or 16000. + * + * However, for EGA and VGA graphics modes, a "word" of memory is a single element in the video buffer + * containing 32 bits of pixel data. * * The MODES.CGA_40X25 modes specify FONT_CGA instead of FONT_CGAD because we don't automatically * load the FONT_CGAD unless the screen is large enough to accommodate it (see the fDoubleFont calculation). @@ -727,14 +732,15 @@ Video.aModeParms[Video.MODE.CGA_80X25] = [ 80, 25, 1, 0, Video.FONT Video.aModeParms[Video.MODE.CGA_320X200] = [320, 200, 8, 192]; // 0x04 Video.aModeParms[Video.MODE.CGA_640X200] = [640, 200, 16, 192]; // 0x06 Video.aModeParms[Video.MODE.MDA_80X25] = [ 80, 25, 1, 0, Video.FONT.MDA]; // 0x07 -Video.aModeParms[Video.MODE.EGA_320X200] = [320, 200, 16]; // 0x0D -Video.aModeParms[Video.MODE.EGA_640X200] = [640, 200, 16]; // 0x0E -Video.aModeParms[Video.MODE.EGA_640X350_MONO] = [640, 350, 16]; // 0x0F -Video.aModeParms[Video.MODE.EGA_640X350] = [640, 350, 16]; // 0x10 -Video.aModeParms[Video.MODE.VGA_640X480_MONO] = [640, 480, 16]; // 0x11 -Video.aModeParms[Video.MODE.VGA_640X480] = [640, 480, 16]; // 0x12 -Video.aModeParms[Video.MODE.VGA_320X200] = [320, 200, 2]; // 0x13 -Video.aModeParms[Video.MODE.VGA_320X400] = [320, 400, 8]; // 0x26 +Video.aModeParms[Video.MODE.EGA_320X200] = [320, 200, 8]; // 0x0D +Video.aModeParms[Video.MODE.EGA_640X200] = [640, 200, 8]; // 0x0E +Video.aModeParms[Video.MODE.EGA_640X350_MONO] = [640, 350, 8]; // 0x0F +Video.aModeParms[Video.MODE.EGA_640X350] = [640, 350, 8]; // 0x10 +Video.aModeParms[Video.MODE.VGA_640X480_MONO] = [640, 480, 8]; // 0x11 +Video.aModeParms[Video.MODE.VGA_640X480] = [640, 480, 8]; // 0x12 +Video.aModeParms[Video.MODE.VGA_320X200] = [320, 200, 1]; // 0x13 +Video.aModeParms[Video.MODE.VGA_320X240] = [320, 240, 4]; // 0x78 +Video.aModeParms[Video.MODE.VGA_320X400] = [320, 400, 4]; // 0x7A Video.aModeParms[Video.MODE.CGA_40X25_BW] = Video.aModeParms[Video.MODE.CGA_40X25]; // 0x01 Video.aModeParms[Video.MODE.CGA_80X25_BW] = Video.aModeParms[Video.MODE.CGA_80X25]; // 0x03 @@ -3577,7 +3583,6 @@ Video.prototype.getCardColors = function(nBitsPerPixel) this.assert(this.cardColor === this.cardEGA); if (this.fRGBValid && nBitsPerPixel && !this.aRGB[16]) { - this.assert(false); this.fRGBValid = false; } @@ -3617,7 +3622,7 @@ Video.prototype.getCardColors = function(nBitsPerPixel) * for bits 6 and 7 from the ATC COLORSEL register (and overwrite bits 4 and 5 if ATC.MODE.COLORSEL_ALL * is set as well). * - * The only reason the DAC wouldn't be valid is if 1) we're trying to display an image before the machine + * The only reasons the DAC wouldn't be valid are if 1) we're trying to display an image before the machine * and its BIOS have had a chance to initialize the DAC (because we don't preset it to anything, although * perhaps we should), or 2) this is an EGA, which doesn't have a DAC. */ @@ -4274,15 +4279,15 @@ Video.prototype.setDimensions = function() this.nColsLogical = this.nCols; this.nCellsPerWord = Video.aModeParms[Video.MODE.MDA_80X25][2]; - this.cbPadding = 0; + var cbPadding = 0; var modeParms = Video.aModeParms[this.nMode]; if (modeParms) { this.nCols = modeParms[0]; this.nRows = modeParms[1]; this.nCellsPerWord = modeParms[2]; - this.cbPadding = modeParms[3] || 0; - this.nFont = modeParms[4]; // this will be undefined for graphics modes + cbPadding = modeParms[3]; // undefined for EGA/VGA graphics modes only + this.nFont = modeParms[4]; // this will be undefined for all graphics modes if (this.nMonitorType == ChipSet.MONITOR.EGACOLOR || this.nMonitorType == ChipSet.MONITOR.VGACOLOR) { /* @@ -4319,9 +4324,13 @@ Video.prototype.setDimensions = function() this.nCells = (this.nCols * this.nRows)|0; this.nCellCache = (this.nCells / this.nCellsPerWord)|0; - this.cbScreen = ((this.nCellCache << 1) + this.cbPadding)|0; - this.cbSplit = (this.cbPadding? ((this.cbScreen + this.cbPadding) >> 1) : 0); - if (this.nMode >= Video.MODE.EGA_320X200) this.nCellCache <<= 1; // double nCellCache (every cell is a byte) + this.cbScreen = this.nCellCache; + this.cbSplit = 0; + + if (cbPadding !== undefined) { + this.cbScreen = ((this.cbScreen << 1) + cbPadding)|0; + this.cbSplit = (this.cbScreen + cbPadding) >> 1; + } /* * If no fonts were successfully loaded, there's no point in initializing the remaining drawing parameters. @@ -4530,13 +4539,19 @@ Video.prototype.checkMode = function(fForce) nMode = fSEQDotClock? (7 - nMode) : Video.MODE.CGA_640X200; } else { /* - * card.addrBuffer must be 0xA0000, so we need to discriminate between modes 0x0D through 0x10; - * we've already defaulted to 0x0F or 0x10, so determine if it's 0x0D or 0x0E (ie, a 200-row mode) - * and then which one (ie, 320 wide or 640 wide). + * card.addrBuffer must be 0xA0000, so we need to discriminate between modes 0x0D and up; + * we've already defaulted to 0x0F or 0x10, so determine if it's 0x0D or 0x0E (ie, a 200-row + * mode) and then which one (ie, 320 wide or 640 wide). */ if (card.regGRCData[Card.GRC.MODE.INDX] & Card.GRC.MODE.COLOR256) { - nMode = Video.MODE.VGA_320X200; - if (!(card.regCRTData[Card.CRTC.MAX_SCAN.INDX] & Card.CRTC.MAX_SCAN.SCAN_LINE)) { + if (card.regCRTData[Card.CRTC.MAX_SCAN.INDX] & Card.CRTC.MAX_SCAN.SCAN_LINE) { + if (card.regCRTData[Card.CRTC.EGA.VDISP_END] <= 0x8F) { + nMode = Video.MODE.VGA_320X200; + } + else { /* (card.regCRTData[Card.CRTC.EGA.VDISP_END] == 0xDF) */ + nMode = Video.MODE.VGA_320X240; + } + } else { nMode = Video.MODE.VGA_320X400; } } @@ -4672,7 +4687,7 @@ Video.prototype.setMode = function(nMode, fForce) if (fForce !== false) { this.updateScreen(true); } else { - this.initCellCache(true); + this.invalidateScreen(true); } } return true; @@ -4714,14 +4729,28 @@ Video.prototype.initCellCache = function(fNew) var nCells = this.nCellCache; if (this.aCellCache === undefined || this.aCellCache.length != nCells) { this.aCellCache = new Array(nCells); - /* - * TODO: Determine whether, with the introduction of fCellCacheValid, this array initialization is useful. - */ - for (var iCell = 0; iCell < nCells; iCell++) this.aCellCache[iCell] = -1; } } }; +/** + * invalidateScreen(fNew) + * + * Ensure that the next updateScreen() will update every cell; intended for situations where the entire screen needs + * to be redrawn, even though the underlying data in the video buffer has not changed (and therefore cleanMemory() will + * report that the buffer is still clean, and/or all the video data still matches everything in our cell cache). + * + * For example, when the palette is being cycled, the screen is being panned, the page is being flipped, etc. + * + * @this {Video} + * @param {boolean} [fNew] is passed through to initCellCache; typically true when a new mode has been set. + */ +Video.prototype.invalidateScreen = function(fNew) +{ + this.fRGBValid = false; + this.initCellCache(fNew); +}; + /** * doBlink() * @@ -4976,7 +5005,7 @@ Video.prototype.updateScreen = function(fForce) * ignoring MODE_CTRL.BM, etc); generalize this someday. */ this.nColsLogical = card.regCRTData[Card.CRTC.EGA.OFFSET] << (this.nFont? 1 : (card.regCRTData[Card.CRTC.EGA.UNDERLINE.INDX] & Card.CRTC.EGA.UNDERLINE.DWORD)? 3 : 4); - cbScreen = (((this.nColsLogical * (this.nRows-1) + this.nCols) / this.nCellsPerWord) << 1)|0; + cbScreen = ((this.nColsLogical * (this.nRows-1) + this.nCols) / this.nCellsPerWord)|0; } if (addrScreen + cbScreen > addrScreenLimit) { @@ -5005,7 +5034,7 @@ Video.prototype.updateScreen = function(fForce) * that small optimization off. Further optimizations are certainly possible, such as a hash table * of all blinking character locations, but all those optimizations are saved for a rainy day. */ - if (!fForce && this.bus.cleanMemory(addrScreen, cbScreen)) { + if (!fForce && this.fCellCacheValid && this.bus.cleanMemory(addrScreen, cbScreen)) { if (!fBlinkUpdate) return; if (!this.cBlinkVisible) { if (this.iCellCursor < 0) return; @@ -5302,10 +5331,10 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit) /** * updateScreenGraphicsVGA(addrScreen, addrScreenLimit) * - * This function name is a slight misnomer: updateScreenGraphicsEGA() takes care of all the "planar" video modes - * (first introduced by the EGA and later expanded by the VGA), where each pixel's bits are spread across multiple - * planes, whereas this function takes care of just the "linear" video modes introduced by the VGA, such as mode 0x13 - * (320x200x256), where each pixel's bits are contained within a single plane. This is basically all 256-color 8bpp + * This function name is a slight misnomer: updateScreenGraphicsEGA() takes care of all the 4bpp video modes + * (first introduced by the EGA and later expanded by the VGA), where each pixel's bits are spread across the 4 + * planes, whereas this function takes care of just the 8bpp video modes introduced by the VGA, such as mode 0x13 + * (320x200x256), where each pixel's bits are contained within a single plane. This is essentially all 256-color * (CHAIN4, CHAIN1, etc) modes, hence the hard-coded call to getCardColors(8). * * @param addrScreen @@ -5588,11 +5617,12 @@ Video.prototype.inATC = function(port, addrFrom) */ Video.prototype.outATC = function(port, bOut, addrFrom) { - var fPalEnabled = (this.cardEGA.regATCIndx & Card.ATC.INDX_PAL_ENABLE); - if (!this.cardEGA.fATCData) { - this.cardEGA.regATCIndx = bOut; + var card = this.cardEGA; + var fPalEnabled = (card.regATCIndx & Card.ATC.INDX_PAL_ENABLE); + if (!card.fATCData) { + card.regATCIndx = bOut; this.printMessageIO(port, bOut, addrFrom, "ATC.INDX"); - this.cardEGA.fATCData = true; + card.fATCData = true; if ((bOut & Card.ATC.INDX_PAL_ENABLE) && !fPalEnabled) { if (!this.buildFonts()) { if (DEBUG && (!addrFrom || this.messageEnabled())) { @@ -5602,7 +5632,6 @@ Video.prototype.outATC = function(port, bOut, addrFrom) if (DEBUG && (!addrFrom || this.messageEnabled())) { this.printMessage("outATC(" + str.toHexByte(bOut) + "): redraw screen for font changes"); } - this.fRGBValid = false; this.updateScreen(true); } } @@ -5610,19 +5639,19 @@ Video.prototype.outATC = function(port, bOut, addrFrom) * HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VRETRACE interval, * but other "triggers" are currently required; see updateScreen() for details. */ - this.cardEGA.nStartAddress = ((this.cardEGA.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + this.cardEGA.regCRTData[Card.CRTC.START_ADDR_LO])|0; + card.nStartAddress = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0; } else { - var iReg = this.cardEGA.regATCIndx & Card.ATC.INDX_MASK; + card.fATCData = false; + var iReg = card.regATCIndx & Card.ATC.INDX_MASK; if (iReg >= Card.ATC.PALETTE_REGS || !fPalEnabled) { - if (Video.TRAPALL || this.cardEGA.regATCData[iReg] !== bOut) { + if (Video.TRAPALL || card.regATCData[iReg] !== bOut) { if (!addrFrom || this.messageEnabled()) { - this.printMessageIO(port, bOut, addrFrom, "ATC." + this.cardEGA.asATCRegs[iReg]); + this.printMessageIO(port, bOut, addrFrom, "ATC." + card.asATCRegs[iReg]); } - this.cardEGA.regATCData[iReg] = bOut; - this.fRGBValid = false; + card.regATCData[iReg] = bOut; + this.invalidateScreen(); } } - this.cardEGA.fATCData = false; } }; @@ -5928,13 +5957,16 @@ Video.prototype.outDACData = function(port, bOut, addrFrom) if (!addrFrom || this.messageEnabled()) { this.printMessageIO(Card.DAC.DATA.PORT, bOut, addrFrom, "DAC.DATA[" + str.toHexByte(this.cardEGA.regDACAddr) + "][" + str.toHexByte(this.cardEGA.regDACShift) + "]"); } - this.cardEGA.regDACData[this.cardEGA.regDACAddr] = (dw & ~(0x3f << this.cardEGA.regDACShift)) | ((bOut & 0x3f) << this.cardEGA.regDACShift); + var dwNew = (dw & ~(0x3f << this.cardEGA.regDACShift)) | ((bOut & 0x3f) << this.cardEGA.regDACShift); + if (dw !== dwNew) { + this.cardEGA.regDACData[this.cardEGA.regDACAddr] = dwNew; + this.invalidateScreen(); + } this.cardEGA.regDACShift += 6; if (this.cardEGA.regDACShift > 12) { this.cardEGA.regDACShift = 0; this.cardEGA.regDACAddr = (this.cardEGA.regDACAddr + 1) & (Card.DAC.TOTAL_REGS-1); } - this.fRGBValid = false; }; /** diff --git a/tests/pc/vga/L47-1.ASM b/tests/pc/vga/L47-1.ASM new file mode 100644 index 000000000..1ae6ad6d6 --- /dev/null +++ b/tests/pc/vga/L47-1.ASM @@ -0,0 +1,88 @@ +; Mode X (320x240, 256 colors) mode set routine. Works on all VGAs. +; **************************************************************** +; * Revised 6/19/91 to select correct clock; fixes vertical roll * +; * problems on fixed-frequency (IBM 851X-type) monitors. * +; **************************************************************** +; C near-callable as: +; void Set320x240Mode(void); +; Tested with TASM +; Modified from public-domain mode set code by John Bridges. + +SC_INDEX equ 03c4h ;Sequence Controller Index +CRTC_INDEX equ 03d4h ;CRT Controller Index +MISC_OUTPUT equ 03c2h ;Miscellaneous Output register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X + + .model small + .data +; Index/data pairs for CRT Controller registers that differ between +; mode 13h and mode X. +CRTParms label word + dw 00d06h ;vertical total + dw 03e07h ;overflow (bit 8 of vertical counts) + dw 04109h ;cell height (2 to double-scan) + dw 0ea10h ;v sync start + dw 0ac11h ;v sync end and protect cr0-cr7 + dw 0df12h ;vertical displayed + dw 00014h ;turn off dword mode + dw 0e715h ;v blank start + dw 00616h ;v blank end + dw 0e317h ;turn on byte mode +CRT_PARM_LENGTH equ (($-CRTParms)/2) + + .code + public _Set320x240Mode +_Set320x240Mode proc near + push bp ;preserve caller's stack frame + push si ;preserve C register vars + push di ; (don't count on BIOS preserving anything) + + mov ax,13h ;let the BIOS set standard 256-color + int 10h ; mode (320x200 linear) + + mov dx,SC_INDEX + mov ax,0604h + out dx,ax ;disable chain4 mode + mov ax,0100h + out dx,ax ;synchronous reset while setting Misc Output + ; for safety, even though clock unchanged + mov dx,MISC_OUTPUT + mov al,0e3h + out dx,al ;select 25 MHz dot clock & 60 Hz scanning rate + + mov dx,SC_INDEX + mov ax,0300h + out dx,ax ;undo reset (restart sequencer) + + mov dx,CRTC_INDEX ;reprogram the CRT Controller + mov al,11h ;VSync End reg contains register write + out dx,al ; protect bit + inc dx ;CRT Controller Data register + in al,dx ;get current VSync End register setting + and al,7fh ;remove write protect on various + out dx,al ; CRTC registers + dec dx ;CRT Controller Index + cld + mov si,offset CRTParms ;point to CRT parameter table + mov cx,CRT_PARM_LENGTH ;# of table entries +SetCRTParmsLoop: + lodsw ;get the next CRT Index/Data pair + out dx,ax ;set the next CRT Index/Data pair + loop SetCRTParmsLoop + + mov dx,SC_INDEX + mov ax,0f02h + out dx,ax ;enable writes to all four planes + mov ax,SCREEN_SEG ;now clear all display memory, 8 pixels + mov es,ax ; at a time + sub di,di ;point ES:DI to display memory + sub ax,ax ;clear to zero-value pixels + mov cx,8000h ;# of words in display memory + rep stosw ;clear all of display memory + + pop di ;restore C register vars + pop si + pop bp ;restore caller's stack frame + ret +_Set320x240Mode endp + end diff --git a/tests/pc/vga/L47-2.ASM b/tests/pc/vga/L47-2.ASM new file mode 100644 index 000000000..100200624 --- /dev/null +++ b/tests/pc/vga/L47-2.ASM @@ -0,0 +1,52 @@ +; Mode X (320x240, 256 colors) write pixel routine. Works on all VGAs. +; No clipping is performed. +; C near-callable as: +; +; void WritePixelX(int X, int Y, unsigned int PageBase, int Color); + +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next + +parms struc + dw 2 dup (?) ;pushed BP and return address +X dw ? ;X coordinate of pixel to draw +Y dw ? ;Y coordinate of pixel to draw +PageBase dw ? ;base offset in display memory of page in + ; which to draw pixel +Color dw ? ;color in which to draw pixel +parms ends + + .model small + .code + public _WritePixelX +_WritePixelX proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + + mov ax,SCREEN_WIDTH + mul [bp+Y] ;offset of pixel's scan line in page + mov bx,[bp+X] + shr bx,1 + shr bx,1 ;X/4 = offset of pixel in scan line + add bx,ax ;offset of pixel in page + add bx,[bp+PageBase] ;offset of pixel in display memory + mov ax,SCREEN_SEG + mov es,ax ;point ES:BX to the pixel's address + + mov cl,byte ptr [bp+X] + and cl,011b ;CL = pixel's plane + mov ax,0100h + MAP_MASK ;AL = index in SC of Map Mask reg + shl ah,cl ;set only the bit for the pixel's plane to 1 + mov dx,SC_INDEX ;set the Map Mask to enable only the + out dx,ax ; pixel's plane + + mov al,byte ptr [bp+Color] + mov es:[bx],al ;draw the pixel in the desired color + + pop bp ;restore caller's stack frame + ret +_WritePixelX endp + end diff --git a/tests/pc/vga/L47-3.ASM b/tests/pc/vga/L47-3.ASM new file mode 100644 index 000000000..3b910748c --- /dev/null +++ b/tests/pc/vga/L47-3.ASM @@ -0,0 +1,49 @@ +; Mode X (320x240, 256 colors) read pixel routine. Works on all VGAs. +; No clipping is performed. +; C near-callable as: +; +; unsigned int ReadPixelX(int X, int Y, unsigned int PageBase); + +GC_INDEX equ 03ceh ;Graphics Controller Index +READ_MAP equ 04h ;index in GC of the Read Map register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +X dw ? ;X coordinate of pixel to read +Y dw ? ;Y coordinate of pixel to read +PageBase dw ? ;base offset in display memory of page from + ; which to read pixel +parms ends + + .model small + .code + public _ReadPixelX +_ReadPixelX proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + + mov ax,SCREEN_WIDTH + mul [bp+Y] ;offset of pixel's scan line in page + mov bx,[bp+X] + shr bx,1 + shr bx,1 ;X/4 = offset of pixel in scan line + add bx,ax ;offset of pixel in page + add bx,[bp+PageBase] ;offset of pixel in display memory + mov ax,SCREEN_SEG + mov es,ax ;point ES:BX to the pixel's address + + mov ah,byte ptr [bp+X] + and ah,011b ;AH = pixel's plane + mov al,READ_MAP ;AL = index in GC of the Read Map reg + mov dx,GC_INDEX ;set the Read Map to read the pixel's + out dx,ax ; plane + + mov al,es:[bx] ;read the pixel's color + sub ah,ah ;convert it to an unsigned int + + pop bp ;restore caller's stack frame + ret +_ReadPixelX endp + end diff --git a/tests/pc/vga/L47-4.ASM b/tests/pc/vga/L47-4.ASM new file mode 100644 index 000000000..6a9f44d91 --- /dev/null +++ b/tests/pc/vga/L47-4.ASM @@ -0,0 +1,90 @@ +; Mode X (320x240, 256 colors) rectangle fill routine. Works on all +; VGAs. Uses slow approach that selects the plane explicitly for each +; pixel. Fills up to but not including the column at EndX and the row +; at EndY. No clipping is performed. +; C near-callable as: +; +; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, int Color); + +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Color dw ? ;color in which to draw pixel +parms ends + + .model small + .code + public _FillRectangleX +_FillRectangleX proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di + + mov ax,SCREEN_WIDTH + mul [bp+StartY] ;offset in page of top rectangle scan line + mov di,[bp+StartX] + shr di,1 + shr di,1 ;X/4 = offset of first rectangle pixel in scan + ; line + add di,ax ;offset of first rectangle pixel in page + add di,[bp+PageBase] ;offset of first rectangle pixel in + ; display memory + mov ax,SCREEN_SEG + mov es,ax ;point ES:DI to the first rectangle pixel's + ; address + mov dx,SC_INDEX ;set the Sequence Controller Index to + mov al,MAP_MASK ; point to the Map Mask register + out dx,al + inc dx ;point DX to the SC Data register + mov cl,byte ptr [bp+StartX] + and cl,011b ;CL = first rectangle pixel's plane + mov al,01h + shl al,cl ;set only the bit for the pixel's plane to 1 + mov ah,byte ptr [bp+Color] ;color with which to fill + mov bx,[bp+EndY] + sub bx,[bp+StartY] ;BX = height of rectangle + jle FillDone ;skip if 0 or negative height + mov si,[bp+EndX] + sub si,[bp+StartX] ;CX = width of rectangle + jle FillDone ;skip if 0 or negative width +FillRowsLoop: + push ax ;remember the plane mask for the left edge + push di ;remember the start offset of the scan line + mov cx,si ;set count of pixels in this scan line +FillScanLineLoop: + out dx,al ;set the plane for this pixel + mov es:[di],ah ;draw the pixel + shl al,1 ;adjust the plane mask for the next pixel's + and al,01111b ; bit, modulo 4 + jnz AddressSet ;advance address if we turned over from + inc di ; plane 3 to plane 0 + mov al,00001b ;set plane mask bit for plane 0 +AddressSet: + loop FillScanLineLoop + pop di ;retrieve the start offset of the scan line + add di,SCREEN_WIDTH ;point to the start of the next scan + ; line of the rectangle + pop ax ;retrieve the plane mask for the left edge + dec bx ;count down scan lines + jnz FillRowsLoop +FillDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_FillRectangleX endp + end diff --git a/tests/pc/vga/L47-5.ASM b/tests/pc/vga/L47-5.ASM new file mode 100644 index 000000000..e09dd4e13 --- /dev/null +++ b/tests/pc/vga/L47-5.ASM @@ -0,0 +1,127 @@ +; Mode X (320x240, 256 colors) rectangle fill routine. Works on all +; VGAs. Uses medium-speed approach that selects each plane only once +; per rectangle; this results in a fade-in effect for large +; rectangles. Fills up to but not including the column at EndX and the +; row at EndY. No clipping is performed. +; C near-callable as: +; +; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, int Color); + +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Color dw ? ;color in which to draw pixel +parms ends + +StartOffset equ -2 ;local storage for start offset of rectangle +Width equ -4 ;local storage for address width of rectangle +Height equ -6 ;local storage for height of rectangle +PlaneInfo equ -8 ;local storage for plane # and plane mask +STACK_FRAME_SIZE equ 8 + + .model small + .code + public _FillRectangleX +_FillRectangleX 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 ax,SCREEN_WIDTH + mul [bp+StartY] ;offset in page of top rectangle scan line + mov di,[bp+StartX] + shr di,1 + shr di,1 ;X/4 = offset of first rectangle pixel in scan + ; line + add di,ax ;offset of first rectangle pixel in page + add di,[bp+PageBase] ;offset of first rectangle pixel in + ; display memory + mov ax,SCREEN_SEG + mov es,ax ;point ES:DI to the first rectangle pixel's + mov [bp+StartOffset],di ; address + mov dx,SC_INDEX ;set the Sequence Controller Index to + mov al,MAP_MASK ; point to the Map Mask register + out dx,al + mov bx,[bp+EndY] + sub bx,[bp+StartY] ;BX = height of rectangle + jle FillDone ;skip if 0 or negative height + mov [bp+Height],bx + mov dx,[bp+EndX] + mov cx,[bp+StartX] + cmp dx,cx + jle FillDone ;skip if 0 or negative width + dec dx + and cx,not 011b + sub dx,cx + shr dx,1 + shr dx,1 + inc dx ;# of addresses across rectangle to fill + mov [bp+Width],dx + mov word ptr [bp+PlaneInfo],0001h + ;lower byte = plane mask for plane 0, + ; upper byte = plane # for plane 0 +FillPlanesLoop: + mov ax,word ptr [bp+PlaneInfo] + mov dx,SC_INDEX+1 ;point DX to the SC Data register + out dx,al ;set the plane for this pixel + mov di,[bp+StartOffset] ;point ES:DI to rectangle start + mov dx,[bp+Width] + mov cl,byte ptr [bp+StartX] + and cl,011b ;plane # of first pixel in initial byte + cmp ah,cl ;do we draw this plane in the initial byte? + jae InitAddrSet ;yes + dec dx ;no, so skip the initial byte + jz FillLoopBottom ;skip this plane if no pixels in it + inc di +InitAddrSet: + mov cl,byte ptr [bp+EndX] + dec cl + and cl,011b ;plane # of last pixel in final byte + cmp ah,cl ;do we draw this plane in the final byte? + jbe WidthSet ;yes + dec dx ;no, so skip the final byte + jz FillLoopBottom ;skip this planes if no pixels in it +WidthSet: + mov si,SCREEN_WIDTH + sub si,dx ;distance from end of one scan line to start + ; of next + mov bx,[bp+Height] ;# of lines to fill + mov al,byte ptr [bp+Color] ;color with which to fill +FillRowsLoop: + mov cx,dx ;# of bytes across scan line + rep stosb ;fill the scan line in this plane + add di,si ;point to the start of the next scan + ; line of the rectangle + dec bx ;count down scan lines + jnz FillRowsLoop +FillLoopBottom: + mov ax,word ptr [bp+PlaneInfo] + shl al,1 ;set the plane bit to the next plane + inc ah ;increment the plane # + mov word ptr [bp+PlaneInfo],ax + cmp ah,4 ;have we done all planes? + jnz FillPlanesLoop ;continue if any more planes +FillDone: + 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 +_FillRectangleX endp + end diff --git a/tests/pc/vga/L47-6.ASM b/tests/pc/vga/L47-6.ASM new file mode 100644 index 000000000..501018868 --- /dev/null +++ b/tests/pc/vga/L47-6.ASM @@ -0,0 +1,113 @@ +; Mode X (320x240, 256 colors) rectangle fill routine. Works on all +; VGAs. Uses fast approach that fans data out to up to four planes at +; once to draw up to four pixels at once. Fills up to but not +; including the column at EndX and the row at EndY. No clipping is +; performed. +; C near-callable as: +; void FillRectangleX(int StartX, int StartY, int EndX, int EndY, +; unsigned int PageBase, int Color); + +SC_INDEX equ 03c4h ;Sequence Controller Index +MAP_MASK equ 02h ;index in SC of Map Mask register +SCREEN_SEG equ 0a000h ;segment of display memory in mode X +SCREEN_WIDTH equ 80 ;width of screen in bytes from one scan line + ; to the next +parms struc + dw 2 dup (?) ;pushed BP and return address +StartX dw ? ;X coordinate of upper left corner of rect +StartY dw ? ;Y coordinate of upper left corner of rect +EndX dw ? ;X coordinate of lower right corner of rect + ; (the row at EndX is not filled) +EndY dw ? ;Y coordinate of lower right corner of rect + ; (the column at EndY is not filled) +PageBase dw ? ;base offset in display memory of page in + ; which to fill rectangle +Color dw ? ;color in which to draw pixel +parms ends + + .model small + .data +; Plane masks for clipping left and right edges of rectangle. +LeftClipPlaneMask db 00fh,00eh,00ch,008h +RightClipPlaneMask db 00fh,001h,003h,007h + .code + public _FillRectangleX +_FillRectangleX proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve caller's register variables + push di + + cld + mov ax,SCREEN_WIDTH + mul [bp+StartY] ;offset in page of top rectangle scan line + mov di,[bp+StartX] + shr di,1 ;X/4 = offset of first rectangle pixel in scan + shr di,1 ; line + add di,ax ;offset of first rectangle pixel in page + add di,[bp+PageBase] ;offset of first rectangle pixel in + ; display memory + mov ax,SCREEN_SEG ;point ES:DI to the first rectangle + mov es,ax ; pixel's address + mov dx,SC_INDEX ;set the Sequence Controller Index to + mov al,MAP_MASK ; point to the Map Mask register + out dx,al + inc dx ;point DX to the SC Data register + mov si,[bp+StartX] + and si,0003h ;look up left edge plane mask + mov bh,LeftClipPlaneMask[si] ; to clip & put in BH + mov si,[bp+EndX] + and si,0003h ;look up right edge plane + mov bl,RightClipPlaneMask[si] ; mask to clip & put in BL + + mov cx,[bp+EndX] ;calculate # of addresses across rect + mov si,[bp+StartX] + cmp cx,si + jle FillDone ;skip if 0 or negative width + dec cx + and si,not 011b + sub cx,si + shr cx,1 + shr cx,1 ;# of addresses across rectangle to fill - 1 + jnz MasksSet ;there's more than one byte to draw + and bh,bl ;there's only one byte, so combine the left + ; and right edge clip masks +MasksSet: + mov si,[bp+EndY] + sub si,[bp+StartY] ;BX = height of rectangle + jle FillDone ;skip if 0 or negative height + mov ah,byte ptr [bp+Color] ;color with which to fill + mov bp,SCREEN_WIDTH ;stack frame isn't needed any more + sub bp,cx ;distance from end of one scan line to start + dec bp ; of next +FillRowsLoop: + push cx ;remember width in addresses - 1 + mov al,bh ;put left-edge clip mask in AL + out dx,al ;set the left-edge plane (clip) mask + mov al,ah ;put color in AL + stosb ;draw the left edge + dec cx ;count off left edge byte + js FillLoopBottom ;that's the only byte + jz DoRightEdge ;there are only two bytes + mov al,00fh ;middle addresses are drawn 4 pixels at a pop + out dx,al ;set the middle pixel mask to no clip + mov al,ah ;put color in AL + rep stosb ;draw the middle addresses four pixels apiece +DoRightEdge: + mov al,bl ;put right-edge clip mask in AL + out dx,al ;set the right-edge plane (clip) mask + mov al,ah ;put color in AL + stosb ;draw the right edge +FillLoopBottom: + add di,bp ;point to the start of the next scan line of + ; the rectangle + pop cx ;retrieve width in addresses - 1 + dec si ;count down scan lines + jnz FillRowsLoop +FillDone: + pop di ;restore caller's register variables + pop si + pop bp ;restore caller's stack frame + ret +_FillRectangleX endp + end diff --git a/tests/pc/vga/L47-7.C b/tests/pc/vga/L47-7.C new file mode 100644 index 000000000..d9e40fbfb --- /dev/null +++ b/tests/pc/vga/L47-7.C @@ -0,0 +1,25 @@ +/* Program to demonstrate mode X (320x240, 256-colors) rectangle + fill by drawing adjacent 20x20 rectangles in successive colors from + 0 on up across and down the screen +*/ +#include +#include + +void Set320x240Mode(void); +void FillRectangleX(int, int, int, int, unsigned int, int); + +void main() { + int i,j; + union REGS regset; + + Set320x240Mode(); + FillRectangleX(0,0,320,240,0,0); /* clear the screen to black */ + for (j = 1; j < 220; j += 21) { + for (i = 1; i < 300; i += 21) { + FillRectangleX(i, j, i+20, j+20, 0, ((j/21*15)+i/21) & 0xFF); + } + } + getch(); + regset.x.ax = 0x0003; /* switch back to text mode and done */ + int86(0x10, ®set, ®set); +} diff --git a/tests/pc/vga/MAKEFILE b/tests/pc/vga/MAKEFILE index 6c3c356ea..c0a2b370a 100644 --- a/tests/pc/vga/MAKEFILE +++ b/tests/pc/vga/MAKEFILE @@ -16,10 +16,13 @@ LINK=C:\MSDEV\BIN\WIN95\C816\BIN\LINK.EXE ALL: L23-1.EXE L24-1.EXE L25-1.EXE L25-2.EXE L25-3.EXE L25-4.EXE L26-1.EXE L26-2.EXE L27-1.EXE L27-2.EXE L27-3.EXE \ L28-1.EXE L28-2.EXE L28-3.EXE L29-1.EXE L29-2.EXE L29-3.EXE L29-4.EXE L30-1.EXE L30-2.EXE L31-1.EXE L31-2.EXE \ - L33-1.EXE L34-1.EXE L35-2.EXE L35-3.EXE + L33-1.EXE L34-1.EXE L35-1.EXE L35-3.EXE L47-4.EXE -L35-2.EXE: L35-2.OBJ L35-1.OBJ +L35-1.EXE: L35-1.OBJ L35-2.OBJ $(LINK) $**,$@; -L35-3.EXE: L35-2.OBJ L35-3.OBJ +L35-3.EXE: L35-3.OBJ L35-2.OBJ + $(LINK) $**,$@; + +L47-4.EXE: L47-1.OBJ L47-2.OBJ L47-3.OBJ L47-4.OBJ L47-7.OBJ $(LINK) $**,$@; diff --git a/tests/pc/vga/README.md b/tests/pc/vga/README.md index 77c6b1d62..47fa9fcfc 100644 --- a/tests/pc/vga/README.md +++ b/tests/pc/vga/README.md @@ -25,37 +25,56 @@ Development of PCjs VGA support has just begun (June 2015), so don't expect ever List of VGA Samples from [Michael Abrash's Graphics Programming Black Book](https://github.com/jeffpar/abrash-black-book): - * [Chapter 23](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-23.md) + * [Chapter 23: Bones and Sinew](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-23.md) * [L23-1.ASM: Animates four balls bouncing around a playfield by using page flipping and panning](L23-1.ASM) - * [Chapter 24](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-24.md) + * [Chapter 24: Parallel Processing with the VGA](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-24.md) * [L24-1.ASM: Illustrates operation of ALUs and latches of the VGA's Graphics Controller](L24-1.ASM) - * [Chapter 25](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-25.md) + * [Chapter 25: VGA Data Machinery](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-25.md) * [L25-1.ASM: Illustrates operation of data rotate and bit mask features of Graphics Controller](L25-1.ASM) * [L25-2.ASM: Illustrates operation of Map Mask register when drawing to memory that already contains data](L25-2.ASM) * [L25-3.ASM: Illustrates operation of set/reset circuitry to force setting of memory that already contains data](L25-3.ASM) * [L25-4.ASM: Illustrates operation of set/reset circuitry in conjunction with CPU data](L25-4.ASM) - * [Chapter 26](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-26.md) + * [Chapter 26: VGA Write Mode 3](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-26.md) * [L26-1.ASM: Illustrates operation of write mode 3 of the VGA](L26-1.ASM) * [L26-2.ASM: Illustrates high-speed text-drawing operation of write mode 3 of the VGA](L26-2.ASM) - * [Chapter 27](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-27.md) + * [Chapter 27: Yet Another VGA Write Mode](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-27.md) * [L27-1.ASM: Illustrates one use of write mode 2 of the VGA and EGA by animating the image of an "A"](L27-1.ASM) * [L27-2.ASM: Illustrates one use of write mode 2 of the VGA and EGA by drawing lines in color patterns](L27-2.ASM) * [L27-3.ASM: Illustrates flipping from bit-mapped graphics mode to text mode and back](L27-3.ASM) - * [Chapter 28](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-28.md) + * [Chapter 28: Reading VGA Memory](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-28.md) * [L28-1.ASM: Illustrates the use of the Read Map register in read mode 0](L28-1.ASM) * [L28-2.ASM: Illustrates use of read mode 1 (color compare mode) to detect collisions in display memory](L28-2.ASM) * [L28-3.ASM: Illustrates the use of Color Don't Care to support fast read-modify-write operations](L28-3.ASM) - * [Chapter 29](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-29.md) + * [Chapter 29: Saving Screens and Other VGA Mysteries](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-29.md) * [L29-1.ASM: Puts up a mode 10h EGA graphics screen, then saves it to the file SNAPSHOT.SCR](L29-1.ASM) * [L29-2.ASM: Restores a mode 10h EGA graphics screen from the file SNAPSHOT.SCR](L29-2.ASM) * [L29-3.ASM: Illustrates the color mapping capabilities of the EGA's palette registers](L29-3.ASM) * [L29-4.ASM: Demonstrates screen blanking via bit 5 of the Attribute Controller Index register](L29-4.ASM) - * [Chapter 30](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-30.md) + * [Chapter 30: Video Est Omnis Divisa](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-30.md) * [L30-1.ASM: Demonstrates the VGA/EGA split screen in action](L30-1.ASM) * [L30-2.ASM: Demonstrates the interaction of the split screen and horizontal pel panning](L30-2.ASM) - * [Chapter 31](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-31.md) + * [Chapter 31: Higher 256-Color Resolution on the VGA](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-31.md) * [L31-1.ASM: Demonstrates pixel drawing in 320x400 256-color mode on the VGA](L31-1.ASM) - * [L31-2.ASM: Demonstrate the two pages available in 320x400 256-color modes on a VGA](L31-2.ASM) + * [L31-2.ASM: Demonstrates the two pages available in 320x400 256-color modes on a VGA](L31-2.ASM) + * [Chapter 32: Be It Resolved: 360x480](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-32.md) + * [L32-1.ASM: Illustrates VGA line drawing in 360x480 256-color mode](L32-1.ASM) + * [L32-2.C: Sample program to illustrate VGA line drawing in 360x480 256-color mode](L32-2.C) + * [Chapter 33: Yogi Bear and Eurythmics Confront VGA Colors](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-33.md) + * [L33-1.ASM: Demonstrates use of the DAC registers by selecting a smoothly contiguous set of 256 colors](L33-1.ASM) + * [Chapter 34: Changing Colors without Writing Pixels](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-34.md) + * [L34-1.ASM: Fills a band across the screen with vertical bars in all 256 attributes](L34-1.ASM) + * [Chapter 35: Bresenham Is Fast, and Fast Is Good](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-35.md) + * [L35-1.C: C implementation of Bresenham's line drawing algorithm](L35-1.C) + * [L35-2.C: Sample program to illustrate EGA/VGA line drawing routines](L35-2.C) + * [L35-3.ASM: Fast assembler implementation of Bresenham's line drawing algorithm](L35-3.ASM) + * [Chapter 47: Mode X: 256-Color VGA Magic](https://github.com/jeffpar/abrash-black-book/blob/master/src/chapter-35.md) + * [L47-1.ASM: Mode X (320x240, 256 colors) mode set routine](L47-1.ASM) + * [L47-2.ASM: Mode X (320x240, 256 colors) write pixel routine](L47-2.ASM) + * [L47-3.ASM: Mode X (320x240, 256 colors) read pixel routine](L47-3.ASM) + * [L47-4.ASM: Mode X (320x240, 256 colors) rectangle fill routine (slow)](L47-4.ASM) + * [L47-5.ASM: Mode X (320x240, 256 colors) rectangle fill routine (medium)](L47-5.ASM) + * [L47-6.ASM: Mode X (320x240, 256 colors) rectangle fill routine (fast)](L47-6.ASM) + * [L47-7.C: Program to demonstrate mode X (320x240, 256-colors) rectangle fill](L47-7.C) ---