Journal:   Dr. Dobb's Journal  Nov 1991 v16 n11 p123(7)
-----------------------------------------------------------------------------
Title:     Antialiasing with the Sierra Hicolor DAC. (Sierra Semiconductor
           Hicolor digital to analog converter)(Graphics
           Programming)(includes listing) (tutorial)
Author:    Abrash, Michael.
AttFile:    Program:  GP-NOV91.ASC  Source code listing.

Abstract:  Sierra Semiconductor's Hicolor digital to analog converter
           provides classic, high quality antialiasing.  Pixels in the
           Hicolor are stored in one word of display memory; each pixel on
           the screen corresponds to a group of sub-pixels in the memory
           buffer.  Sub-pixels are averaged and the screen pixel is set to
           that level.  In antialiasing, the image draws to the memory buffer
           at a multiple of the resolution that the screen supports.  A
           sub-pixel resolution of twice normal along each axe has visible
           jagged edges but is better than nonantialiased images.  Blending
           colors into a smooth gradient across a boundary allows the human
           eye to identify the boundary with better precision than the
           resolution of the screen.
-----------------------------------------------------------------------------
Descriptors..
Company:   Sierra Semiconductor Corp. (Products).
Ticker:    SERA.
Product:   Sierra Semiconductor HiColor (Digital-to-analog converter)
           (Usage).
Topic:     Digital to Analog Converters
           Tutorial
           Programming Instruction
           Computer Graphics.
Feature:   illustration
           chart.

-----------------------------------------------------------------------------
Full Text:

There's an Italian saying, the gist of which is, "It need not be true, so
long as it's well said."  This strikes close to the essential truth of
antialiasing:  The image need not be accurate, so long as it looks like it
is.  You don't go to the trouble of antialiasing in order to get a
mathematically precise representation of an image; you do it so the amazing
eye/brain integrating/pattern matching system will see what you want it to
see.

This is a particularly relevant thought at the moment, for we're smack in the
middle of discussing the Sierra Hicolor DAC, which makes classic,
high-quality antialiasing, of the sort that Targa boards have offered for
years, available at mass-market prices.  To recap, the Hicolor DAC extends
SuperVGA to provide selection among enough colors for serious rendering and
antialiasing; 32,768 simultaneous colors, to be exact.  Although the Hicolor
DAC falls short of the 24-bpp true color standard, you aren't likely to find
a 24-bpp adapter priced in the $200-300 range.

Last month, we looked at simple, unweighted antialiasing in the context of
the VGA's standard 256-color mode, performing antialiasing between exactly
four colors--red, green, blue, and black--with five semi-independent levels
of each of the three primary colors available.  This month, we'll start off
by discussing the basic Hicolor programming model, then we'll do the same
sort of antialiasing as last month--but this time with 32 fully independent
levels of each primary color and resolutions up to 800x600, which makes quite
a difference indeed.

A Brief Primer on the Sierra Hicolor DAC

The operation of the Hicolor DAC in 32K-color mode is remarkably simple.
First, the VGA must be set to a 256-color mode with twice the desired
horizontal resolution; for example, a 1600x 600 256-color mode would be
selected if 800x600 32K-color mode were desired.  Then, the Hicolor DAC is
set to high-color mode via the command register; in high-color mode, the
Hicolor DAC takes each pair of 256-color pixels, joins them together into one
16-bit pixel, converts the red, green, and blue components (described
shortly) directly to proportional analog values (no palette involved), and
sends them to the monitor.

Ther is a serious problem here, however: There is no standard way for an
application to select a high-color mode.  It's not enough to set up the
Hicolor DAC; the VGA must also be set to the appropriate double-resolution
256-color mode, and the sequence for doing that--especially selecting
high-speed clocks-varies from VGA to VGA.  There is no VESA mode number for
high-color modes; there is a VESA programming guideline for high-color modes,
and I'm looking into it, but it's certainly not as simple as a mode number.
In any case, the VESA interface isn't always available.

Consequently, high-color mode selection is adapter-dependent.  Fortunately,
most of the Hicolor-based boards are built around the Tseng Labs ET4000 VGA
chip (ATI is the only exception I know of, which is why I'm focusing on
ET4000-based Hicolor boards in this column), and Tseng provides a BIOS
interface for high-color modes.  (There's no guarantee that manufacturers
using the ET4000 will follow the Tseng interface, but I suspect they will, as
it's the closest thing to a standard at the mooment.)  Unfortunately, when I
run the ET4000 BIOS function that reports whether a Hicolor DAC is present on
my Toshiba portable without a Hicolor board installed, it hangs my system, so
it's not a good idea to rely on the BIOS functions alone.

My solution, shown in Listing One (page 146), is to first check for a Hicolor
DAC and an ET4000 at the hardware level; if both are present, I call the BIOS
(which is presumably at least not hostile to the Tseng BIOS high-color
extensions at this point) to check for the availability of Hicolor modes, and
finally, if all has gone well, to set the desired high-color mode.  This is
probably overkill, but at least this way you get three kinds of chip ID code
to mix and match as you wish.

Programming the Hicolor DAC

The pixel format of the Hicolor DAC is straightforward: Each pixel is stored
in one word of display memory, with the lowest 5 bits forming the blue
component, the next 5 bits forming the green component, the next 5 bits
forming the red component, and bit 15 ignored, as shown in Figure 1.  Pixels
start at even addresses.  The bits within a word are organized Intel style,
with the byte at the even address containing bits 7-0 (blue and part of
green), and the byte at the odd address containing bits 158 (red and the rest
of green).

Pixels proceed linearly for the length of the bitmap; the organization is the
same as 256-color mode, except that each pixel takes up one word, rather than
one byte.  As in SuperVGA 256-color modes, the bitmap is too long to be
addressed in the 64K video memory window, so banking must be used; again, the
banking is just like 256-color or banking, except that each bank contains
only half as many pixels; 32,768, to be exact.

On the ET4000, the Segment Select register at 3CDh controls banking, as shown
in Figure 2.  There are 16 banks, each spanning 64 Kbytes of the total
1-Mbyte bitmap.  Banks can be selected separately for read and write to
facilitate scrolling and screen-to-screen copies, although we won't need that
today.  Simple enough, but there's a catch: broken rasters.

Banks are 64K in length.  If each Hicolor scan line is 1600 bytes long, then
65,536/1600=40 raster lines (lines 0-39) fit in bank 0--with 1536 bytes of
the next line (line 40), also in the first bank.  The last 64 bytes of line
40 are in bank 1, as shown in Figure 3, so the line is split by the bank
boundary; hence the term "broken raster."  Broken rasters crop up for other
bank crossings as well, and make Hicolor programming somewhat slower (the
extent depends heavily on the quality of the code) and considerably more
complicated.

Broken rasters are not unique to Hicolor modes; 800x600 and 640x480 256-color
modes also normally have broken rasters.  However, there's a clever
workaround for broken rasters in 256-color modes: Stretch the bitmap width to
1K pixels, via the Row Offset register, so that banks split between raster
lines (although this works for 800x600 only if the VGA has 1 Mbyte of
memory).

Sad to say, stretching the bitmap width to 1K pixels doesn't work in Hicolor
mode.  There's not enough memory to do it at 800x600 (at least until 2-Mbyte
VGAs appear), but that's not the problem at 640x480.  The problem is that a
Row Offset register setting of 256 would be required to stretch the bitmap
width to 1K pixels--and the Row Offset register only goes up to 255.  I'm
sure that when the VGA was being designed, 255 seemed like plenty, but then,
640K once seemed like pie in the sky.

The upshot is that Hicolor programming unavoidably requires handling broken
rasters.  That's a nuisance, but a manageable one; next, we'll see polygon
fill code that deals with broken rasters.

Non-antialiased Hicolor Drawing

Listing Two (page 146) draws a perspective cube in 640x480 32K color mode,
with help from the initialization code in Listing One, the DrawPixel-based
low-level polygon fill code in Listing Three (page 146), and the header file
in Listing Seven, page 149.  (FILCNVXD.C from last month and Listing Four
from the March column are also required.)  Not surprisingly, the cube drawn
by Listing Two looks a lot like the non-antialiased cube drawn last month,
but isn't as jagged because the resolution is now much higher.  Nonetheless,
jaggies are still quite prominent, and they remain clearly visible at
800x600.  (I've used 640x480 mode so that the code will work on
fixed-frequency monitors, but Listing Two can be altered for 800x600 mode
simply by changing the parameter passed to SetHCMode and the value of Bitmap
WidthInBytes.)

Listing Two doesn't run very fast when linked to Listing Three; I suspect
you'll like the low-level polygon fill code in Listing Four (page 146) much
better.  This code handles broken rasters reasonably efficiently, by checking
for them at the beginning of each scan line, then splitting up the fill and
banking appropriately whenever a bank crossing is detected.

Simple Unweighted Antialiasing

As the saying goes, you can never be too rich, too thin, or have too many
colors available.  Personally, I only buy one of those three assertions:  You
really can't have too many colors.  Listings Five (page 148) and Six (page
149), together with Listings One, Three, and Seven, FILCNVXD.C from last
month, and Listing Four from March, show why.  This program draws the same
cube, but this time employing the simple, unweighted antialiasing we used
last month--and taking advantage of the full color range of the Hicolor DAC.
The results are excellent: On my venerable NEC MultiSync, at a viewing
distance of one foot, all but two of the edges look absolutely smooth, with
not the slightest hint of jaggies, and the two imperfect edges show only
slight ripples.  At two feet, the cube looks perfect.  The difference between
the non-antialiased and antialiased cubes is astounding, considering that
we're working with the same resolution in both cases.

A quick review of the simple antialiasing used this month and last: The image
is drawn to a memory buffer at a multiple of the resolution that the actual
screen supports.  Each pixel on the screen maps to a group of hi-res pixels
(subpixels), arranged in a square, in the memory buffer.  The colors of the
subpixels in each square are averaged, and the corresponding screen pixel is
set to the average subpixel color.

There's not enough memory to scan out the entire image at high resolution
(about 50K is required just to scan out one raster line at 4X resolution!),
so Listing Six scans out just those pixels that lie in a specified band.
(Each band corresponds to a single raster line in Listing Five.)  Note that
Listing Six draws 32-bit pixels to the memory buffer; this is true color,
plus an extra byte for flexibility.  Consequently, Listing Six is a
general-purpose tool, and can be used with any sort of adapter, so long as
the main program knows how to convert from true color to adapter-specific
pixels.  Listing Five does this by calculating the average intensity in each
subpixel group of each of the three primary colors, in the range 0-255, then
looking up the gamma corrected equivalent color value for the Hicolor DAC,
mapped into the range 0-31.

A quick look at the gamma-corrected color mapping table in Listing Five shows
why hardware gamma correction is sorely missed in the Hicolor DAC.  The
brightest half of the color range--from half intensity to full intensity--is
spanned by only 9 of the Hicolor DAC's 32 color values.  That means that for
brighter colors, the Hicolor DAC effectively has only half the color
resolution that you'd expect from 5 bits per color gun, and the resolution is
even worse at the highest intensities.

I'd like to take a moment to emphasize that although Listing Five works with
only the three primary colors, it could just as easily work with the
thousands of colors that can be produced as mixes of the three primaries;
there are none of the limitations of 256-color mode, and no special tricks
(such as biasing the palette according to color frequency) need be used.
Inevitably, though, proportionately fewer intermediate blends are available
and hence antialiasing becomes less precise when there is less contrast
between colors; you're not going to be able to do much antialiasing between a
pixel with a green true color value of 250 and another with a value of 255.
This is where the lack of gamma correction and the difference between 15-bpp
and true color become apparent.

Notes on the Antialiasing Implementation

Listing Five features user-selectable subpixel resolution (the multiple of
the screen resolution at which the image should be drawn into the memory
buffer).  A subpixel resolution of two times normal along both axes (2X)
looks much better than nonantialiased drawing, but still has visible jaggies.
Subpixel resolution of 4X looks terrific, as mentioned earlier.  Higher
subpixel resolutions are, practically speaking, reserved for 386 protected
mode, because they would require a buffer larger than 64K to hold the
high-res equivalent of a single scan line.

On the downside, Listing Five is very slow, even though the conversion
process from true color pixels to Hicolor pixels is limited to the bounding
rectangle for the cube being drawn, thereby saving the time that was wasted
last month drawing the empty space around the cube.  It could easily be sped
up by, say, an order of magnitude, in a number of ways.  First, you could
implement an ASM function that's the equivalent of memset, but stores longs
(dwords) rather than chars (bytes).  In the absence of any such C library
function, Listing Six uses a loop with a pointer to a long, hardly a recipe
for high performance.

Listing Five could also be sped up by doing the screen pixel construction
from each square of subpixels in assembly language using pointers rather than
array look-ups.  It would also help to organize the screen pixel drawing more
as a variant rectangle fill, instead of going through DrawPixel every time,
so that the screen pointer doesn't have to be recalculated from scratch and
the bank doesn't need to be calculated and set for every pixel.  Clipping
each polygon to the band before rather than after scanning it out would speed
things up, as would building an edge list for the polygons once, ahead of
time, then advancing it incrementally to scan out each band, rather than
doing one complete scan of each polygon for each band.  Bigger bands would
help; drawing the whole image to the memory buffer in one burst, then
converting the entire image to Hicolor pixels in a single operation, would be
ideal, but would require a ridiculous amount of memory.  (Would you believe,
31 megs for one full 800x600 Hicolor screen at 4X resolution?)

Finally, to alter Listing Five for 800x 600 Hicolor mode, change the
parameter passed to SetHCMode, the value of BitmapWidthInBytes, and the value
of SCREEN WIDTH.

Further Thoughts on Antialiasing

The banded true color approach of Listings Five and Six is easily extended to
other antialiasing approaches.  For example, you could, if you wished,
average together all the subpixels not within a square, but rather within a
circle of radius sqrt(2.0)*ResolutionMultiplier/2 around each pixel center.
This approach is a little more complicated, but it has one great virtue:  An
image will be antialiased identically, regardless of its rotation.

Why is the shape of the subpixed area that's collected into a screen pixel
important, when the maximum resolution we can actually draw with is the
resolution of the screen?  I'll quote William vanRyper, from the
graphics.disp/vga conference on BIX:

If you anti-alias an edge on the screen, and let the eye-brain pick the edge
somewhere the gradien between the object color and the background, you can
adjust the placement of that perceptual edge by altering the ramp of the
gradient.  If the number of intermediate values you can choose among is
greater then the number of gradient pixels you set (across the edge), you can
adjust the position of the perceptucal edge in increments of less than a
pixel.  This means you can locate the antialiased object to sub-pixel
precesion.

In other words, by using blends of color in a smooth, consistent gradient
across a boundary, you can get the eye to pick out the boundary location with
a precision that's greater than the resolution of the screen.  This is, of
course, part and parcel of the wonderful eye/brain magic that allows color to
substitute for resolution and makes antialiasing worthwhile.

Given that we can draw images with perceived resolution higher than the
screen, consistency in subpixel placement is very important.  Unfortunately,
our simple square antialiasing does not produce the same results (a
consistent color gradient) for an image rotated 45 degrees as it does for an
unrotated image--but antialiasing based on a circular subpixel area does.  So
the shape of the subpixel area used for antialiasing matters because if it's
not symmetric in all directions, boundaries will appear to wiggle as images
roate, destroying the image of reality that antialiased animation strives to
create.

On the other hand, if you're drawing only static images, use a square
subpixel area for antialiasing; it's fast, easy, and looks just fine in that
context.  As I said at the outset, we're not seeking mathematical perfection
here, just a good-looking display for the purpose at hand.  If it looks good,
it is good
