From 718073f45fa31730faf7e8f678b3cf8ebbd872c7 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Tue, 30 Jun 2015 20:29:51 -0700 Subject: [PATCH] Scaffolding for VGA CHAIN4 support --- .editorconfig | 7 +- modules/pcjs/lib/memory.js | 12 +- modules/pcjs/lib/video.js | 222 ++++++++++++++++++++++++++---- tests/pc/vga/L42-1.C | 131 ++++++++++++++++++ tests/pc/vga/L42-2.C | 124 +++++++++++++++++ tests/pc/vga/L42-3.C | 29 ++++ tests/pc/vga/L42-4.C | 64 +++++++++ tests/pc/vga/L42-5.C | 40 ++++++ tests/pc/vga/L42-6.ASM | 270 +++++++++++++++++++++++++++++++++++++ 9 files changed, 869 insertions(+), 30 deletions(-) create mode 100644 tests/pc/vga/L42-1.C create mode 100644 tests/pc/vga/L42-2.C create mode 100644 tests/pc/vga/L42-3.C create mode 100644 tests/pc/vga/L42-4.C create mode 100644 tests/pc/vga/L42-5.C create mode 100644 tests/pc/vga/L42-6.ASM diff --git a/.editorconfig b/.editorconfig index 350383ad4..d5dca64dd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ root = true end_of_line = lf insert_final_newline = true -[*.bas] +[*.BAS,*.bas] indent_style = space indent_size = 8 trim_trailing_whitespace = true @@ -17,6 +17,11 @@ indent_style = tab indent_size = 8 trim_trailing_whitespace = true +[{*.C,*.H,*.c,*.h}] +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + [modules/**.js] indent_style = space indent_size = 4 diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index a3a80fccd..6cc768f6b 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -636,8 +636,8 @@ Memory.prototype = { */ writeShortDefault: function writeShortDefault(off, w, addr) { Component.assert(!(w & ~0xffff)); - this.writeByte(off, w & 0xff); - this.writeByte(off + 1, w >> 8); + this.writeByte(off, w & 0xff, addr); + this.writeByte(off + 1, w >> 8, addr); }, /** * writeLongDefault(off, w, addr) @@ -648,10 +648,10 @@ Memory.prototype = { * @param {number} addr */ writeLongDefault: function writeLongDefault(off, w, addr) { - this.writeByte(off, w & 0xff); - this.writeByte(off + 1, (w >> 8) & 0xff); - this.writeByte(off + 2, (w >> 16) & 0xff); - this.writeByte(off + 3, (w >>> 24)); + this.writeByte(off, w & 0xff, addr); + this.writeByte(off + 1, (w >> 8) & 0xff, addr); + this.writeByte(off + 2, (w >> 16) & 0xff, addr); + this.writeByte(off + 3, (w >>> 24), addr); }, /** * readByteMemory(off, addr) diff --git a/modules/pcjs/lib/video.js b/modules/pcjs/lib/video.js index dec9eb250..805d46a89 100644 --- a/modules/pcjs/lib/video.js +++ b/modules/pcjs/lib/video.js @@ -703,7 +703,7 @@ Video.FONT = { * * 0: # of columns (nCols) * 1: # of rows (nRows) - * 2: # cells per word (nCellsPerWord: # of characters or pixels per word) + * 2: # cells per word (nCellsPerWord: # of characters or pixels per 16-bit word) * 3: # bytes of visible screen padding, if any (used for CGA graphics modes only) * 4: font ID (nFont: undefined if graphics mode) * @@ -729,7 +729,7 @@ Video.aModeParms[Video.MODE.EGA_640X350_MONO] = [640, 350, 16]; 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, 16]; // 0x13 +Video.aModeParms[Video.MODE.VGA_320X200] = [320, 200, 2]; // 0x13 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 @@ -1243,7 +1243,7 @@ Card.ATC = { TEXTGRCC: 0x04, // bit 2: set for line graphics in character codes 0xC0-0xDF, clear otherwise TEXTBLINK: 0x08, // bit 3: set for text blink attribute, clear for background intensity attribute RESERVED: 0x10, // bit 4: reserved - PANCOMPAT: 0x20, // bit 5: set for PEL panning compatibility + PANCOMPAT: 0x20, // bit 5: set for pixel-panning compatibility PELWIDTH: 0x40, // bit 6: set for 256-color modes, clear for all other modes COLORSEL: 0x80 // bit 7: set for P5,P4 mapped to bits 1,0 of the Color Select register }, @@ -1258,7 +1258,7 @@ Card.ATC = { }, HORZPAN: { INDX: 0x13, // ATC Horizontal PEL Panning Register - SHIFT_LEFT: 0x0F // bits 0-3 indicate # of PELs to shift left + SHIFT_LEFT: 0x0F // bits 0-3 indicate # of pixels to shift left }, COLORSEL: { INDX: 0x14, // ATC Color Select Register (VGA only) @@ -1383,11 +1383,11 @@ if (DEBUGGER) Card.SEQ.REGS = ["RESET","CLOCKING","MAPMASK","CHARMAP","MEMMODE"] /* * VGA Digital-to-Analog Converter (DAC) Registers (regDACMask, regDACState, regDACAddr, and regDACData) * - * To write PEL data, write an address to DAC.ADDR.PORT_WRITE, then write 3 bytes to DAC.DATA.PORT; the low 6 bits + * To write DAC data, write an address to DAC.ADDR.PORT_WRITE, then write 3 bytes to DAC.DATA.PORT; the low 6 bits * of each byte will be concatenated to form an 18-bit DAC value (red is least significant, followed by green, then blue). * When the final byte is received, the 18-bit DAC value is updated and regDACAddr is auto-incremented. * - * To read PEL data, the process is similar, but the initial address is written to DAC.ADDR.PORT_READ instead. + * To read DAC data, the process is similar, but the initial address is written to DAC.ADDR.PORT_READ instead. * * DAC.STATE.PORT and DAC.ADDR.PORT_WRITE can be read at any time and will not interfere with a read or write operation * in progress. To prevent "snow", reading or writing DAC values should be limited to retrace intervals (see regStatus1), @@ -1568,6 +1568,7 @@ Card.ACCESS = { MODE0: 0x0400, MODE1: 0x0500, EVENODD: 0x1000, + CHAIN4: 0x4000, MASK: 0xFF00 }, WRITE: { // and WRITE values are designed to be OR'ed with READ values @@ -1575,6 +1576,7 @@ Card.ACCESS = { MODE1: 0x0001, MODE2: 0x0002, MODE3: 0x0003, // VGA only + CHAIN4: 0x0004, EVENODD: 0x0010, ROT: 0x0020, AND: 0x0060, @@ -1620,6 +1622,23 @@ Card.ACCESS.readByteMode0 = function readByteMode0(off, addr) return (dw >> this.controller.nReadMapShift) & 0xff; }; +/** + * readByteMode0Chain4(off, addr) + * + * See writeByteMode0Chain4 for a description of how writes are distributed across planes. + * + * @this {Memory} + * @param {number} off + * @param {number} [addr] + * @return {number} + */ +Card.ACCESS.readByteMode0Chain4 = function readByteMode0Chain4(off, addr) +{ + var idw = (off & ~0x3) + this.offset; + var shift = (off & 0x3) << 3; + return ((this.controller.latches = this.adw[idw]) >> shift) & 0xff; +}; + /** * readByteMode0EvenOdd(off, addr) * @@ -1713,6 +1732,44 @@ Card.ACCESS.writeByteMode0 = function writeByteMode0(off, b, addr) } }; +/** + * writeByteMode0Chain4(off, b, addr) + * + * This is how we distribute a write of 0xff across the address space to the planes, assuming that + * all planes are enabled by the Sequencer's MAPMASK register (which we assume still controls access): + * + * off idw adw[idw] + * ------ ------ ---------- + * 0x0000: 0x0000 0x000000ff + * 0x0001: 0x0000 0x0000ff00 + * 0x0002: 0x0000 0x00ff0000 + * 0x0003: 0x0000 0xff000000 + * 0x0004: 0x0001 0x000000ff + * 0x0005: 0x0001 0x0000ff00 + * 0x0006: 0x0001 0x00ff0000 + * 0x0007: 0x0001 0xff000000 + * ... + * + * @this {Memory} + * @param {number} off + * @param {number} b (which should already be pre-masked to 8 bits; see Bus.prototype.setByteDirect) + * @param {number} [addr] + */ +Card.ACCESS.writeByteMode0Chain4 = function writeByteMode0Chain4(off, b, addr) +{ + var idw = (off & ~0x3) + this.offset; + var shift = (off & 0x3) << 3; + /* + * TODO: Consider adding a separate "unmasked" version of this CHAIN4 write function whenever nSeqMapMask is -1 + * (or removing nSeqMapMask from the equation altogether, if no one uses CHAIN4 with anything less than all planes enabled). + */ + var dw = ((b << shift) & this.controller.nSeqMapMask) | (this.adw[idw] & ~((0xff << shift) & this.controller.nSeqMapMask)); + if (this.adw[idw] != dw) { + this.adw[idw] = dw; + this.fDirty = true; + } +}; + /** * writeByteMode0EvenOdd(off, b, addr) * @@ -1726,8 +1783,8 @@ Card.ACCESS.writeByteMode0EvenOdd = function writeByteMode0EvenOdd(off, b, addr) off += this.offset; var dw = b | (b << 8) | (b << 16) | (b << 24); // - // When even/odd addressing is enabled, nSeqMapMask must be cleared for planes 1 and 3 - // if the address is even, and cleared for planes 0 and 2 if the address is odd. + // When even/odd addressing is enabled, nSeqMapMask must be cleared for planes 1 + // and 3 if the address is even, and cleared for planes 0 and 2 if the address is odd. // var idw = off & ~0x1; dw = (dw & this.controller.nBitMapMask) | (this.controller.latches & ~this.controller.nBitMapMask); @@ -1859,8 +1916,8 @@ Card.ACCESS.writeByteMode1 = function writeByteMode1(off, b, addr) Card.ACCESS.writeByteMode1EvenOdd = function writeByteMode1EvenOdd(off, b, addr) { /* - * TODO: As discussed in getAccess(), we need to run some tests on real EGA/VGA hardware to determine - * exactly where latches are written (ie, to which address) when EVENODD is in effect. + * TODO: As discussed in getAccess(), we need to run some tests on real EGA/VGA hardware to + * determine exactly where latches are written (ie, to which address) when EVENODD is in effect. */ off += this.offset; // @@ -1993,7 +2050,8 @@ Card.ACCESS.writeByteMode3 = function writeByteMode3(off, b, addr) Card.ACCESS.afn = []; Card.ACCESS.afn[Card.ACCESS.READ.MODE0] = Card.ACCESS.readByteMode0; -Card.ACCESS.afn[Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD] = Card.ACCESS.readByteMode0EvenOdd; +Card.ACCESS.afn[Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.CHAIN4] = Card.ACCESS.readByteMode0Chain4; +Card.ACCESS.afn[Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD] = Card.ACCESS.readByteMode0EvenOdd; Card.ACCESS.afn[Card.ACCESS.READ.MODE1] = Card.ACCESS.readByteMode1; Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0] = Card.ACCESS.writeByteMode0; @@ -2001,6 +2059,7 @@ Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.ROT] = Card.ACCESS. Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.AND] = Card.ACCESS.writeByteMode0And; Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.OR] = Card.ACCESS.writeByteMode0Or; Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.XOR] = Card.ACCESS.writeByteMode0Xor; +Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.CHAIN4] = Card.ACCESS.writeByteMode0Chain4; Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD] = Card.ACCESS.writeByteMode0EvenOdd; Card.ACCESS.afn[Card.ACCESS.WRITE.MODE1] = Card.ACCESS.writeByteMode1; Card.ACCESS.afn[Card.ACCESS.WRITE.MODE1 | Card.ACCESS.WRITE.EVENODD] = Card.ACCESS.writeByteMode1EvenOdd; @@ -3929,6 +3988,7 @@ Video.prototype.getAccess = function() var nAccess; var card = this.cardActive; + this.fLinear = false; var regGRCMode = card.regGRCData[Card.GRC.MODE.INDX]; if (regGRCMode != null) { var nReadAccess = Card.ACCESS.READ.MODE0; @@ -4020,6 +4080,11 @@ Video.prototype.getAccess = function() nReadAccess |= Card.ACCESS.READ.EVENODD; nWriteAccess |= Card.ACCESS.WRITE.EVENODD; } + if (regSEQMode & Card.SEQ.MEMMODE.CHAIN4) { + nReadAccess |= Card.ACCESS.READ.CHAIN4; + nWriteAccess |= Card.ACCESS.WRITE.CHAIN4; + this.fLinear = true; + } } nAccess = nReadAccess | nWriteAccess; } @@ -4114,6 +4179,7 @@ Video.prototype.setDimensions = function() 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); + this.fLinear = false; // set for 8bpp "linear" VGA modes only if (this.nMode >= Video.MODE.EGA_320X200) this.nCellCache <<= 1; // double nCellCache (every cell is a byte) /* @@ -4316,18 +4382,21 @@ Video.prototype.checkMode = function(fForce) if (fSEQDotClock) nMode -= 2; } else { if (card.addrBuffer == 0xB8000) { - // - // Since nMode will have been assigned a default of either 0x02 or 0x03, convert that to either - // 0x05 or 0x04 if we're in a low-res graphics mode, 0x06 otherwise. - // + /* + * Since nMode will have been assigned a default of either 0x02 or 0x03, convert that to either + * 0x05 or 0x04 if we're in a low-res graphics mode, 0x06 otherwise. + */ 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). - // - if (nCRTCVertTotal < 500) { + /* + * 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). + */ + if (card.regSEQData[Card.SEQ.MEMMODE.INDX] & Card.SEQ.MEMMODE.CHAIN4) { + nMode = Video.MODE.VGA_320X200; + } + else if (nCRTCVertTotal < 500) { if (nCRTCVertTotal < 350) { nMode = (fSEQDotClock? Video.MODE.EGA_320X200 : Video.MODE.EGA_640X200); } @@ -4811,9 +4880,12 @@ Video.prototype.updateScreen = function(fForce) else if (this.cbSplit) { this.updateScreenGraphicsCGA(addrScreen, addrScreenLimit); } - else { + else if (!this.fLinear) { this.updateScreenGraphicsEGA(addrScreen, addrScreenLimit); } + else { + this.updateScreenGraphicsVGA(addrScreen, addrScreenLimit); + } }; /** @@ -4994,7 +5066,7 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit) data = adwMemory[idw]; /* - * Figure out how many visible pixels this byte represents; usually 8, unless panning is being used. + * Figure out how many visible pixels this data represents; usually 8, unless panning is being used. */ var iPixel, nPixels = 8; @@ -5079,6 +5151,110 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit) } }; +/** + * updateScreenGraphicsVGA(addrScreen, addrScreenLimit) + * + * The name is a slight misnomer: updateScreenGraphicsEGA() takes care of all the "planar" video modes, which were + * first introduced by the EGA and later expanded by the VGA, whereas this function takes care of just the "linear" + * video modes introduced by the VGA, such as mode 0x13 (320x200x256). Those modes may also be referred to as CHAIN4 + * modes, since I think all of them require that the CHAIN4 bit in the Sequencer's MEMMODE register be set. + * + * @param addrScreen + * @param addrScreenLimit + */ +Video.prototype.updateScreenGraphicsVGA = function(addrScreen, addrScreenLimit) +{ + var addr, data; + + addr = addrScreen; + this.cBlinkVisible = 0; + + var iCell = 0; + var aPixelColors = this.getCardColors(); + var adwMemory = this.cardActive.adwMemory; + + var x = 0, y = 0; + var xDirty = this.nCols, xMaxDirty = 0, yDirty = this.nRows, yMaxDirty = 0; + + var iPixelFirst = this.cardActive.regATCData[Card.ATC.HORZPAN.INDX] & Card.ATC.HORZPAN.SHIFT_LEFT; + /* + * TODO: What should happen if the card is programmed such that nColsLogical is LESS THAN nCols? + */ + var nRowAdjust = (this.nColsLogical > this.nCols? ((this.nColsLogical - this.nCols - iPixelFirst) >> 3) : 0); + + while (addr < addrScreenLimit) { + var idw = addr++ - this.addrBuffer; + this.assert(idw >= 0 && idw < adwMemory.length); + data = adwMemory[idw]; + + /* + * Figure out how many visible pixels this data represents; usually 4, unless panning is being used. + */ + var iPixel, nPixels = 4; + + if (iPixelFirst) { + /* + * Notice that we're not using the cell cache when panning is active, because the cached cell data no + * longer aligns with the data we're pulling out of the video buffer, and it's not clear that the effort + * to realign the data and make a valid cache comparison would save enough work to make it worthwhile. + */ + if (!x) { + data <<= iPixelFirst; + nPixels -= iPixelFirst; + /* + * This is as good a place as any to invalidate the cell cache when panning is active; this ensures + * we don't rely on stale cache contents once panning stops. + */ + this.fCellCacheValid = false; + } else { + iPixel = this.nCols - x; + if (nPixels > iPixel) nPixels = iPixel; + } + } else { + this.assert(iCell < this.aCellCache.length); + if (this.fCellCacheValid && data === this.aCellCache[iCell]) { + x += nPixels; + nPixels = 0; + } else { + this.aCellCache[iCell] = data; + } + iCell++; + } + + if (nPixels) { + if (x < xDirty) xDirty = x; + for (iPixel = 0; iPixel < nPixels; iPixel++) { + var bPixel = data & 0xff; + this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]); + data >>>= 8; + } + if (x > xMaxDirty) xMaxDirty = x; + if (y < yDirty) yDirty = y; + if (y >= yMaxDirty) yMaxDirty = y + 1; + } + + this.assert(x <= this.nCols); + + if (x >= this.nCols) { + x = 0; + if (++y > this.nRows) break; + addr += nRowAdjust; + } + } + + if (!iPixelFirst) this.fCellCacheValid = true; + + /* + * For a fascinating discussion of the best way to update the screen canvas at this point, see updateScreenGraphicsCGA(). + */ + if (xDirty < this.nCols) { + var cxDirty = xMaxDirty - xDirty; + var cyDirty = yMaxDirty - yDirty; + this.contextScreenBuffer.putImageData(this.imageScreenBuffer, 0, 0, xDirty, yDirty, cxDirty, cyDirty); + this.contextScreen.drawImage(this.canvasScreenBuffer, 0, 0, this.nCols, this.nRows, 0, 0, this.cxScreen, this.cyScreen); + } +}; + /** * getRetraceBits(card) * diff --git a/tests/pc/vga/L42-1.C b/tests/pc/vga/L42-1.C new file mode 100644 index 000000000..ad849bbd7 --- /dev/null +++ b/tests/pc/vga/L42-1.C @@ -0,0 +1,131 @@ +/* Function to draw an antialiased line from (X0,Y0) to (X1,Y1), using an + * antialiasing approach published by Xiaolin Wu in the July 1991 issue of + * Computer Graphics. Requires that the palette be set up so that there + * are NumLevels intensity levels of the desired drawing color, starting at + * color BaseColor (100% intensity) and followed by (NumLevels-1) levels of + * evenly decreasing intensity, with color (BaseColor+NumLevels-1) being 0% + * intensity of the desired drawing color (black). This code is suitable for + * use at screen resolutions, with lines typically no more than 1K long; for + * longer lines, 32-bit error arithmetic must be used to avoid problems with + * fixed-point inaccuracy. No clipping is performed in DrawWuLine; it must be + * performed either at a higher level or in the DrawPixel function. + * Tested with Borland C++ in C compilation mode and the small model. + */ +extern void DrawPixel(int, int, int); + +/* Wu antialiased line drawer. + * (X0,Y0),(X1,Y1) = line to draw + * BaseColor = color # of first color in block used for antialiasing, the + * 100% intensity version of the drawing color + * NumLevels = size of color block, with BaseColor+NumLevels-1 being the + * 0% intensity version of the drawing color + * IntensityBits = log base 2 of NumLevels; the # of bits used to describe + * the intensity of the drawing color. 2**IntensityBits==NumLevels + */ +void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor, int NumLevels, unsigned int IntensityBits) +{ + unsigned int IntensityShift, ErrorAdj, ErrorAcc; + unsigned int ErrorAccTemp, Weighting, WeightingComplementMask; + int DeltaX, DeltaY, Temp, XDir; + + /* Make sure the line runs top to bottom */ + if (Y0 > Y1) { + Temp = Y0; Y0 = Y1; Y1 = Temp; + Temp = X0; X0 = X1; X1 = Temp; + } + /* Draw the initial pixel, which is always exactly intersected by + the line and so needs no weighting */ + DrawPixel(X0, Y0, BaseColor); + + if ((DeltaX = X1 - X0) >= 0) { + XDir = 1; + } else { + XDir = -1; + DeltaX = -DeltaX; /* make DeltaX positive */ + } + /* Special-case horizontal, vertical, and diagonal lines, which + require no weighting because they go right through the center of + every pixel */ + if ((DeltaY = Y1 - Y0) == 0) { + /* Horizontal line */ + while (DeltaX-- != 0) { + X0 += XDir; + DrawPixel(X0, Y0, BaseColor); + } + return; + } + if (DeltaX == 0) { + /* Vertical line */ + do { + Y0++; + DrawPixel(X0, Y0, BaseColor); + } while (--DeltaY != 0); + return; + } + if (DeltaX == DeltaY) { + /* Diagonal line */ + do { + X0 += XDir; + Y0++; + DrawPixel(X0, Y0, BaseColor); + } while (--DeltaY != 0); + return; + } + /* Line is not horizontal, diagonal, or vertical */ + ErrorAcc = 0; /* initialize the line error accumulator to 0 */ + /* # of bits by which to shift ErrorAcc to get intensity level */ + IntensityShift = 16 - IntensityBits; + /* Mask used to flip all bits in an intensity weighting, producing the + result (1 - intensity weighting) */ + WeightingComplementMask = NumLevels - 1; + /* Is this an X-major or Y-major line? */ + if (DeltaY > DeltaX) { + /* Y-major line; calculate 16-bit fixed-point fractional part of a + pixel that X advances each time Y advances 1 pixel, truncating the + result so that we won't overrun the endpoint along the X axis */ + ErrorAdj = ((unsigned long) DeltaX << 16) / (unsigned long) DeltaY; + /* Draw all pixels other than the first and last */ + while (--DeltaY) { + ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ + ErrorAcc += ErrorAdj; /* calculate error for next pixel */ + if (ErrorAcc <= ErrorAccTemp) { + /* The error accumulator turned over, so advance the X coord */ + X0 += XDir; + } + Y0++; /* Y-major, so always advance Y */ + /* The IntensityBits most significant bits of ErrorAcc give us the + intensity weighting for this pixel, and the complement of the + weighting for the paired pixel */ + Weighting = ErrorAcc >> IntensityShift; + DrawPixel(X0, Y0, BaseColor + Weighting); + DrawPixel(X0 + XDir, Y0, BaseColor + (Weighting ^ WeightingComplementMask)); + } + /* Draw the final pixel, which is always exactly intersected by the line + and so needs no weighting */ + DrawPixel(X1, Y1, BaseColor); + return; + } + /* It's an X-major line; calculate 16-bit fixed-point fractional part of a + pixel that Y advances each time X advances 1 pixel, truncating the + result to avoid overrunning the endpoint along the X axis */ + ErrorAdj = ((unsigned long) DeltaY << 16) / (unsigned long) DeltaX; + /* Draw all pixels other than the first and last */ + while (--DeltaX) { + ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ + ErrorAcc += ErrorAdj; /* calculate error for next pixel */ + if (ErrorAcc <= ErrorAccTemp) { + /* The error accumulator turned over, so advance the Y coord */ + Y0++; + } + X0 += XDir; /* X-major, so always advance X */ + /* The IntensityBits most significant bits of ErrorAcc give us the + intensity weighting for this pixel, and the complement of the + weighting for the paired pixel */ + Weighting = ErrorAcc >> IntensityShift; + DrawPixel(X0, Y0, BaseColor + Weighting); + DrawPixel(X0, Y0 + 1, BaseColor + (Weighting ^ WeightingComplementMask)); + } + /* Draw the final pixel, which is always exactly intersected by the line + and so needs no weighting */ + DrawPixel(X1, Y1, BaseColor); +} diff --git a/tests/pc/vga/L42-2.C b/tests/pc/vga/L42-2.C new file mode 100644 index 000000000..20f475475 --- /dev/null +++ b/tests/pc/vga/L42-2.C @@ -0,0 +1,124 @@ +/* Sample line-drawing program to demonstrate Wu antialiasing. Also draws + * non-antialiased lines for comparison. + * Tested with Borland C++ in C compilation mode and the small model. + */ +#include +#include + +void SetPalette(struct WuColor *); +extern void DrawWuLine(int, int, int, int, int, int, unsigned int); +extern void DrawLine(int, int, int, int, int); +extern void SetMode(void); +extern int ScreenWidthInPixels; /* screen dimension globals */ +extern int ScreenHeightInPixels; + +#define NUM_WU_COLORS 2 /* # of colors we'll do antialiased drawing with */ +struct WuColor { /* describes one color used for antialiasing */ + int BaseColor; /* # of start of palette intensity block in DAC */ + int NumLevels; /* # of intensity levels */ + int IntensityBits; /* IntensityBits == log2 NumLevels */ + int MaxRed; /* red component of color at full intensity */ + int MaxGreen; /* green component of color at full intensity */ + int MaxBlue; /* blue component of color at full intensity */ +}; +enum {WU_BLUE=0, WU_WHITE=1}; /* drawing colors */ +struct WuColor WuColors[NUM_WU_COLORS] = /* blue and white */ + {{192, 32, 5, 0, 0, 0x3F}, {224, 32, 5, 0x3F, 0x3F, 0x3F}}; + +void main() +{ + int CurrentColor, i; + union REGS regset; + + /* Draw Wu-antialiased lines in all directions */ + SetMode(); + SetPalette(WuColors); + for (i=5; i + +/* Screen dimension globals, used in main program to scale. */ +int ScreenWidthInPixels = 320; +int ScreenHeightInPixels = 200; + +/* Mode 13h draw pixel function. */ +void DrawPixel(int X, int Y, int Color) +{ + #define SCREEN_SEGMENT 0xA000 + unsigned char far *ScreenPtr; + + FP_SEG(ScreenPtr) = SCREEN_SEGMENT; + FP_OFF(ScreenPtr) = (unsigned int) Y * ScreenWidthInPixels + X; + *ScreenPtr = Color; +} + +/* Mode 13h mode-set function. */ +void SetMode() +{ + union REGS regset; + + /* Set to 320x200 256-color graphics mode */ + regset.x.ax = 0x0013; + int86(0x10, ®set, ®set); +} diff --git a/tests/pc/vga/L42-4.C b/tests/pc/vga/L42-4.C new file mode 100644 index 000000000..dc8a20608 --- /dev/null +++ b/tests/pc/vga/L42-4.C @@ -0,0 +1,64 @@ +/* Function to draw a non-antialiased line from (X0,Y0) to (X1,Y1), using a + * simple fixed-point error accumulation approach. + * Tested with Borland C++ in C compilation mode and the small model. + */ +extern void DrawPixel(int, int, int); + +/* Non-antialiased line drawer. + * (X0,Y0),(X1,Y1) = line to draw, Color = color in which to draw + */ +void DrawLine(int X0, int Y0, int X1, int Y1, int Color) +{ + unsigned long ErrorAcc, ErrorAdj; + int DeltaX, DeltaY, XDir, Temp; + + /* Make sure the line runs top to bottom */ + if (Y0 > Y1) { + Temp = Y0; Y0 = Y1; Y1 = Temp; + Temp = X0; X0 = X1; X1 = Temp; + } + DrawPixel(X0, Y0, Color); /* draw the initial pixel */ + if ((DeltaX = X1 - X0) >= 0) { + XDir = 1; + } else { + XDir = -1; + DeltaX = -DeltaX; /* make DeltaX positive */ + } + if ((DeltaY = Y1 - Y0) == 0) /* done if only one point in the line */ + if (DeltaX == 0) return; + + ErrorAcc = 0x8000; /* initialize line error accumulator to .5, so we can + advance when we get halfway to the next pixel */ + /* Is this an X-major or Y-major line? */ + if (DeltaY > DeltaX) { + /* Y-major line; calculate 16-bit fixed-point fractional part of a + pixel that X advances each time Y advances 1 pixel */ + ErrorAdj = ((((unsigned long)DeltaX << 17) / (unsigned long)DeltaY) + 1) >> 1; + /* Draw all pixels between the first and last */ + do { + ErrorAcc += ErrorAdj; /* calculate error for this pixel */ + if (ErrorAcc & ~0xFFFFL) { + /* The error accumulator turned over, so advance the X coord */ + X0 += XDir; + ErrorAcc &= 0xFFFFL; /* clear integer part of result */ + } + Y0++; /* Y-major, so always advance Y */ + DrawPixel(X0, Y0, Color); + } while (--DeltaY); + return; + } + /* It's an X-major line; calculate 16-bit fixed-point fractional part of a + pixel that Y advances each time X advances 1 pixel */ + ErrorAdj = ((((unsigned long)DeltaY << 17) / (unsigned long)DeltaX) + 1) >> 1; + /* Draw all remaining pixels */ + do { + ErrorAcc += ErrorAdj; /* calculate error for this pixel */ + if (ErrorAcc & ~0xFFFFL) { + /* The error accumulator turned over, so advance the Y coord */ + Y0++; + ErrorAcc &= 0xFFFFL; /* clear integer part of result */ + } + X0 += XDir; /* X-major, so always advance X */ + DrawPixel(X0, Y0, Color); + } while (--DeltaX); +} diff --git a/tests/pc/vga/L42-5.C b/tests/pc/vga/L42-5.C new file mode 100644 index 000000000..d7fe39ef1 --- /dev/null +++ b/tests/pc/vga/L42-5.C @@ -0,0 +1,40 @@ +/* Mode set and pixel-drawing functions for the 640x480 256-color mode of + * Tseng Labs ET4000-based SuperVGAs. + * Tested with Borland C++ in C compilation mode and the small model. + */ +#include + +/* Screen dimension globals, used in main program to scale */ +int ScreenWidthInPixels = 640; +int ScreenHeightInPixels = 480; + +/* ET4000 640x480 256-color draw pixel function. */ +void DrawPixel(int X, int Y, int Color) +{ + #define SCREEN_SEGMENT 0xA000 + #define GC_SEGMENT_SELECT 0x3CD /* ET4000 segment (bank) select reg */ + unsigned char far *ScreenPtr; + unsigned int Bank; + unsigned long BitmapAddress; + + /* Full bitmap address of pixel, as measured from address 0 to 0xFFFFF */ + BitmapAddress = (unsigned long) Y * ScreenWidthInPixels + X; + /* Bank # is upper word of bitmap addr */ + Bank = BitmapAddress >> 16; + /* 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 = Color; +} + +/* ET4000 640x480 256-color mode-set function. */ +void SetMode() +{ + union REGS regset; + + /* Set to 640x480 256-color graphics mode */ + regset.x.ax = 0x002E; + int86(0x10, ®set, ®set); +} diff --git a/tests/pc/vga/L42-6.ASM b/tests/pc/vga/L42-6.ASM new file mode 100644 index 000000000..630b4cd4e --- /dev/null +++ b/tests/pc/vga/L42-6.ASM @@ -0,0 +1,270 @@ +; C near-callable function to draw an antialiased line from +; (X0,Y0) to (X1,Y1), in mode 13h, the VGA's standard 320x200 256-color +; mode. Uses an antialiasing approach published by Xiaolin Wu in the July +; 1991 issue of Computer Graphics. Requires that the palette be set up so +; that there are NumLevels intensity levels of the desired drawing color, +; starting at color BaseColor (100% intensity) and followed by (NumLevels-1) +; levels of evenly decreasing intensity, with color (BaseColor+NumLevels-1) +; being 0% intensity of the desired drawing color (black). No clipping is +; performed in DrawWuLine. Handles a maximum of 256 intensity levels per +; antialiased color. This code is suitable for use at screen resolutions, +; with lines typically no more than 1K long; for longer lines, 32-bit error +; arithmetic must be used to avoid problems with fixed-point inaccuracy. +; Tested with TASM. +; +; C near-callable as: +; void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor, +; int NumLevels, unsigned int IntensityBits); + +SCREEN_WIDTH_IN_BYTES equ 320 ;# of bytes from the start of one scan line +; to the start of the next +SCREEN_SEGMENT equ 0a000h ;segment in which screen memory resides + +; Parameters passed in stack frame. +parms struc + dw 2 dup (?) ;pushed BP and return address +X0 dw ? ;X coordinate of line start point +Y0 dw ? ;Y coordinate of line start point +X1 dw ? ;X coordinate of line end point +Y1 dw ? ;Y coordinate of line end point +BaseColor dw ? ;color # of first color in block used for + ;antialiasing, the 100% intensity version of the + ;drawing color +NumLevels dw ? ;size of color block, with BaseColor+NumLevels-1 + ; being the 0% intensity version of the drawing color + ; (maximum NumLevels = 256) +IntensityBits dw ? ;log base 2 of NumLevels; the # of bits used to + ; describe the intensity of the drawing color. + ; 2**IntensityBits==NumLevels + ; (maximum IntensityBits = 8) +parms ends + + .model small + .code +; Screen dimension globals, used in main program to scale. +_ScreenWidthInPixels dw 320 +_ScreenHeightInPixels dw 200 + + .code + public _DrawWuLine +_DrawWuLine proc near + push bp ;preserve caller's stack frame + mov bp,sp ;point to local stack frame + push si ;preserve C's register variables + push di + push ds ;preserve C's default data segment + cld ;make string instructions increment their pointers + +; Make sure the line runs top to bottom. + mov si,[bp].X0 + mov ax,[bp].Y0 + cmp ax,[bp].Y1 ;swap endpoints if necessary to ensure that + jna NoSwap ; Y0 <= Y1 + xchg [bp].Y1,ax + mov [bp].Y0,ax + xchg [bp].X1,si + mov [bp].X0,si +NoSwap: + +; Draw the initial pixel, which is always exactly intersected by the line +; and so needs no weighting. + mov dx,SCREEN_SEGMENT + mov ds,dx ;point DS to the screen segment + mov dx,SCREEN_WIDTH_IN_BYTES + mul dx ;Y0 * SCREEN_WIDTH_IN_BYTES yields the offset + ; of the start of the row start the initial + ; pixel is on + add si,ax ;point DS:SI to the initial pixel + mov al,byte ptr [bp].BaseColor ;color with which to draw + mov [si],al ;draw the initial pixel + + mov bx,1 ;XDir = 1; assume DeltaX >= 0 + mov cx,[bp].X1 + sub cx,[bp].X0 ;DeltaX; is it >= 1? + jns DeltaXSet ;yes, move left->right, all set + ;no, move right->left + neg cx ;make DeltaX positive + neg bx ;XDir = -1 +DeltaXSet: + +; Special-case horizontal, vertical, and diagonal lines, which require no +; weighting because they go right through the center of every pixel. + mov dx,[bp].Y1 + sub dx,[bp].Y0 ;DeltaY; is it 0? + jnz NotHorz ;no, not horizontal + ;yes, is horizontal, special case + and bx,bx ;draw from left->right? + jns DoHorz ;yes, all set + std ;no, draw right->left +DoHorz: + lea di,[bx+si] ;point DI to next pixel to draw + mov ax,ds + mov es,ax ;point ES:DI to next pixel to draw + mov al,byte ptr [bp].BaseColor ;color with which to draw + ;CX = DeltaX at this point + rep stosb ;draw the rest of the horizontal line + cld ;restore default direction flag + jmp Done ;and we're done + + align 2 +NotHorz: + and cx,cx ;is DeltaX 0? + jnz NotVert ;no, not a vertical line + ;yes, is vertical, special case + mov al,byte ptr [bp].BaseColor ;color with which to draw +VertLoop: + add si,SCREEN_WIDTH_IN_BYTES ;point to next pixel to draw + mov [si],al ;draw the next pixel + dec dx ;--DeltaY + jnz VertLoop + jmp Done ;and we're done + + align 2 +NotVert: + cmp cx,dx ;DeltaX == DeltaY? + jnz NotDiag ;no, not diagonal + ;yes, is diagonal, special case + mov al,byte ptr [bp].BaseColor ;color with which to draw +DiagLoop: + lea si,[si+SCREEN_WIDTH_IN_BYTES+bx] + ;advance to next pixel to draw by + ; incrementing Y and adding XDir to X + mov [si],al ;draw the next pixel + dec dx ;--DeltaY + jnz DiagLoop + jmp Done ;and we're done + +; Line is not horizontal, diagonal, or vertical. + align 2 +NotDiag: +; Is this an X-major or Y-major line? + cmp dx,cx + jb XMajor ;it's X-major + +; It's a Y-major line. Calculate the 16-bit fixed-point fractional part of a +; pixel that X advances each time Y advances 1 pixel, truncating the result +; to avoid overrunning the endpoint along the X axis. + xchg dx,cx ;DX = DeltaX, CX = DeltaY + sub ax,ax ;make DeltaX 16.16 fixed-point value in DX:AX + div cx ;AX = (DeltaX << 16) / DeltaY. Won't overflow + ; because DeltaX < DeltaY + mov di,cx ;DI = DeltaY (loop count) + sub si,bx ;back up the start X by 1, as explained below + mov dx,-1 ;initialize the line error accumulator to -1, + ; so that it will turn over immediately and + ; advance X to the start X. This is necessary + ; properly to bias error sums of 0 to mean + ; "advance next time" rather than "advance + ; this time," so that the final error sum can + ; never cause drawing to overrun the final X + ; coordinate (works in conjunction with + ; truncating ErrorAdj, to make sure X can't + ; overrun) + mov cx,8 ;CL = # of bits by which to shift + sub cx,[bp].IntensityBits ; ErrorAcc to get intensity level (8 + ; instead of 16 because we work only + ; with the high byte of ErrorAcc) + mov ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an + dec ch ; intensity weighting, producing + ; result (1 - intensity weighting) + mov bp,BaseColor[bp] ;***stack frame not available*** + ;***from now on *** + xchg bp,ax ;BP = ErrorAdj, AL = BaseColor, + ; AH = scratch register + +; Draw all remaining pixels. +YMajorLoop: + add dx,bp ;calculate error for next pixel + jnc NoXAdvance ;not time to step in X yet + ;the error accumulator turned over, + ;so advance the X coord + add si,bx ;add XDir to the pixel pointer +NoXAdvance: + add si,SCREEN_WIDTH_IN_BYTES ;Y-major, so always advance Y + +; The IntensityBits most significant bits of ErrorAcc give us the intensity +; weighting for this pixel, and the complement of the weighting for the +; paired pixel. + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + add ah,al ;BaseColor + Weighting + mov [si],ah ;DrawPixel(X, Y, BaseColor + Weighting); + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + xor ah,ch ;Weighting ^ WeightingComplementMask + add ah,al ;BaseColor + (Weighting ^ WeightingComplementMask) + mov [si+bx],ah ;DrawPixel(X+XDir, Y, +; BaseColor + (Weighting ^ WeightingComplementMask)); + dec di ;--DeltaY + jnz YMajorLoop + jmp Done ;we're done with this line + +; It's an X-major line. + align 2 +XMajor: +; Calculate the 16-bit fixed-point fractional part of a pixel that Y advances +; each time X advances 1 pixel, truncating the result to avoid overrunning +; the endpoint along the X axis. + sub ax,ax ;make DeltaY 16.16 fixed-point value in DX:AX + div cx ;AX = (DeltaY << 16) / Deltax. Won't overflow + ; because DeltaY < DeltaX + mov di,cx ;DI = DeltaX (loop count) + sub si,SCREEN_WIDTH_IN_BYTES;back up the start X by 1, as + ; explained below + mov dx,-1 ;initialize the line error accumulator to -1, + ; so that it will turn over immediately and + ; advance Y to the start Y. This is necessary + ; properly to bias error sums of 0 to mean + ; "advance next time" rather than "advance + ; this time," so that the final error sum can + ; never cause drawing to overrun the final Y + ; coordinate (works in conjunction with + ; truncating ErrorAdj, to make sure Y can't + ; overrun) + mov cx,8 ;CL = # of bits by which to shift + sub cx,[bp].IntensityBits ; ErrorAcc to get intensity level (8 + ; instead of 16 because we work only + ; with the high byte of ErrorAcc) + mov ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an + dec ch ; intensity weighting, producing + ; result (1 - intensity weighting) + mov bp,BaseColor[bp] ;***stack frame not available*** + ;***from now on *** + xchg bp,ax ;BP = ErrorAdj, AL = BaseColor, + ; AH = scratch register +; Draw all remaining pixels. +XMajorLoop: + add dx,bp ;calculate error for next pixel + jnc NoYAdvance ;not time to step in Y yet + ;the error accumulator turned over, + ; so advance the Y coord + add si,SCREEN_WIDTH_IN_BYTES;advance Y +NoYAdvance: + add si,bx ;X-major, so add XDir to the pixel pointer + +; The IntensityBits most significant bits of ErrorAcc give us the intensity +; weighting for this pixel, and the complement of the weighting for the +; paired pixel. + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + add ah,al ;BaseColor + Weighting + mov [si],ah ;DrawPixel(X, Y, BaseColor + Weighting); + mov ah,dh ;msb of ErrorAcc + shr ah,cl ;Weighting = ErrorAcc >> IntensityShift; + xor ah,ch ;Weighting ^ WeightingComplementMask + add ah,al ;BaseColor + (Weighting ^ WeightingComplementMask) + mov [si+SCREEN_WIDTH_IN_BYTES],ah + +; DrawPixel(X, Y+SCREEN_WIDTH_IN_BYTES, BaseColor + (Weighting ^ WeightingComplementMask)); + dec di ;--DeltaX + jnz XMajorLoop + +Done: ;we're done with this line + pop ds ;restore C's default data segment + pop di ;restore C's register variables + pop si + pop bp ;restore caller's stack frame + ret ;done +_DrawWuLine endp + end +