Putting Actual Graphics Into the Graphics Adapter
Last time, I threw together a VGA time base. I didn’t spend much time elaborating on it, because honestly it bores me. There is only so much you can say about a counter that hasn’t already been said. This time around the goal is to display an actual image.
A static pattern of lines is a good sign the circuit is working, but it’s a long ways from some kind of interactive display. To do that, there must be a memory to hold image data along with means to extract that data to display.
Nominally, adding this memory isn’t all that complicated. Practically there are some engineering issues to solve before you get too far. Including a few that I didn’t realize until I actually started plugging things together. Whoops.
I didn’t get everything I wanted this time, but I did learn a lot of things. Unpleasant things, to be sure, but things I needed to know before going further- and that’s the real reason I’m doing this.
The Big List
Time Base Generation- VGA requires a rock-stable timing chain to work. It therefore must be done first.- Framebuffer and Bitmap Graphics- Without some kind of memory, you can’t display anything useful.
- Text Mode- A specialized graphics mode that allows for more compact representation of tiled graphics, i.e. text.
- Control and Interface- Adding the final bits of glue logic to hook into an external system.
Bitmap Graphics
The most basic type of computer graphics is the bitmap. “Bitmap” is fairly literal, each pixel on screen is directly connected to at least one bit and laid out in a coordinate grid. (0,0) typically refers to the top left corner, increasing down and to the right.
Displaying a bitmapped graphic is easy. Simply use the row and column counters to fetch the pixel from memory. Using the counters this way ensures the layout of the memory is very similar to the layout of the screen.
We call the memory the pixels are stored in the framebuffer. Designing it turns out to be far more complicated than it seems.
Chunky vs Planar
The first design question to ask is how, exactly, are the pixels arranged in memory?
Pixels can be organized into bit planes, which assign one bit per pixel and one plane per color. Think of it like projecting colored lights over each other. To get a red pixel, you turn the red light on, but not green or blue. Adding green over red gets you yellow, and so on.
Planar graphics has the advantage that pixels are always stored consecutively regardless of how many bits each pixel uses. Adding more bit planes is trivial (relatively speaking), and there only needs to be enough memory to hold the colors you’re actually using.
While attractive in terms of flexibility, planar graphics turns out to be less attractive from a hardware perspective. I’m also not sure if it makes much sense from a usability standpoint- you have to do N times more operations for an N bit pixel edit. While I briefly did consider some sort of planar arrangement, I discarded it in favor of chunky pixels.
Chunky pixels are arranged so that all color information is interleaved into a single contiguous byte structure. That means anything other than 1, 2, 4, or 8 bits per pixel wastes a few bits. Unlike planar pixels, chunky pixels always use the maximum amount of memory- even if you only use one color.
In terms of available hardware, chunky pixels map nicely to byte-width RAM. Smaller than byte values need a little special handling, but it’s trivial to implement. Chunky pixels it is, then.
Color Depth
Having decided to use chunky pixels, the next design question is how many bits each pixel takes. In other words, how many colors a pixel is allowed to hold.
1 bit per pixel obviously takes up the least amount of memory. Pack eight to a byte, and the 200×150 screen fits in only 3,750 bytes. You only get two colors though. On the other end, using 8 bits per pixel would require 30,000 bytes in exchange for 256 colors.
I settled on 16 colors/4 bits per pixel. It’s enough to make a decently colorful display while still remaining manageable on the hardware side.
There are only three primary colors, so the fourth bit serves as an “intensifier” bit. When set it bumps up the brightness a bit. Here’s a palette swatch:
Typically this is referred to as RGBI color. Not all displays will show the exact same colors, but it’ll serve as a useful reference.
Framebuffer
Based on the previous discussion, a 200x150px screen with 4 bits per chunky pixel requires exactly 15,000 bytes of RAM. That means two screens can fit in a standard 32,768 byte RAM with room to spare.
Except it doesn’t. I did not consider the effects of using a simple address decoder until now. Let me explain.
There are 200 pixels per line. 200 can be represented by an 8 bit binary number. 8 bits allows for 256 possible pixels, not 200. 56 bytes are therefore wasted per line. Instead of 15,000 bytes this bumps the memory footprint up to 19,200. This violates an assumption I was working around, meaning I can’t do what I wanted to do.
I do not see a good way to either use (e.g. the Apple II memory holes) or eliminate this 56 byte per line overhead. Slack memory was the argument in favor of planar graphics- but I would need to add so much extra hardware it’s not worth it. I just have to live with 42% of the memory wasted. When a byte only costs 0.0002USD, I guess I can live with it.
All that nonsense aside, I can’t actually use a RAM for this test. Instead I’m using an 8KiB ROM. It can only hold 12,800 pixels, or about one third of the screen. While putting three 8KiB chips together would be easier on the wasted RAM, they are bigger and more costly than a standard 32KiB chip.
Digital to Analog Converter
VGA uses analog signals for color, so there needs to be a DAC to get more than eight colors.
There are several types of DAC, including many one-chip solutions. RAM DACs have a built-in memory and were specifically designed for graphics use. Which makes it really damn annoying that you can’t really get them anymore. Given the simplicity of my system, I chose to build my own.
Using RGBI format means two bits per color. I chose to use a summing DAC over the R2R type because its two bits. A two bit summing DAC uses two resistors, while the R2R type uses at least four. Not a hard choice to make. R2R makes more sense when the color depth is 4 bits per pixel or more.
Unpacking the pixels isn’t really part of the DAC, but might as well mention it here. One pixel is 4 bits, so the HC257 quad 2:1 multiplexer does the job. Part of the decision to use 16 colors was based on the existence of this wonderful device.
Nice and straightforward.
Testing The Framebuffer and DAC
You test the graphics display exactly how you expect to: display an image.
As previously mentioned, I’m using an 8KiB ROM to hold the test image. This can hold a 200×32 picture with pixels packed two per byte. Enough for a test.
The first problem is finding a suitable test image. It needs to be a very particular type of image: a 16 color bitmap. Using nothing more than the generic paint program every PC has, I came up with this:
Text is more than half of what any computer system does, so the left hand side has a custom monospace font of all the English letters, Arabic numbers, and some useful symbols. I’m not too happy with it, but it’ll work as a test.
The other half of the image is a jolly little scene depicting the local scenery. I tried to get as many different colors in the image as possible, but between by nonexistent artistic skills and the fact that nothing is ever painted bright magenta, some colors aren’t shown.
That diagonal line on the right is just marking the end of the image. It shouldn’t be seen.
You can’t just burn a bitmap into a ROM. Well, you can but it won’t display properly. Instead I used this bitmap to data converter to strip the irrelevant stuff out. What’s left is just the image data.
After burning the ROM, it’s time to wire it in and turn it on:
Wow. That’s some color. You can just about see the image, so it’s a partial success. Nothing seemed to be wired wrong- but I had left the BLANK signal disconnected.
At this point I tried wiring in BLANK to the HC257.
Only marginally better! MARGINALLY BETTER!
Not having a proper blanking interval causes color troubles. I assume this is because the ADC in the monitor is looking at the average voltage, trying to find an appropriate black level. Without the blank region it puts black too high, resulting in the first image.
Text is just barely legible.
I’d get a less blurry pic, but it doesn’t look much better in real life.
Color issues appear to be the result of me not understanding the image format. You can see the odd pixels above the bridge that are supposed to be interpreted as a palette. Those extra pixels also appear to be what’s offsetting the image.
At this point I realized my pixels were too small. I had wired in the pixel lines one position too high.
Properly aligned, it still looks like shit.
Sadly this did not fix the artifacting. If anything it made things worse!
I tried tweaking the monitor settings to no avail. Whatever’s causing these artifacts isn’t something I have much control over.
Having accomplished my main goal to display an actual image, I’m going to stop here. There’s plenty to investigate before going further.
Future Development
I was hoping this would be quick and easy. It was. Less than an hour’s worth of work for a resounding success!
Well, partial success at least. I don’t know the BMP format as well as I thought I did. Setting up the palette is something I need to do more research on. You can’t control which color matches to which index- at least not with a simple paint program.
Color troubles are easy to explain away, but I’m not sure why those vertical artifacts show up. My best guess is the monitor ADC isn’t quite synchronized with my VGA signal. VGA is an analog standard, so the digital-analog-digital conversion chain is ripe for loss of fidelity. Since my monitor is 1440×900 it also can’t display 800×600 without some compromises. A 4:3 monitor would fix that issue. Alas, everything I have is 16:9.
VGA also seems to have some undefined behavior. Monitors have more quirks than I expected. Some things I know from earlier experiments, but the artifacting and black level were surprises. I might have to find some other monitors for testing.
Failing to account for the extra 56 bytes per line means I have to do some serious rework. I was expecting to hold two images in memory simultaneously. Now I can only hold one, with some serious wasted space. Perhaps I should review planar graphics some more but the downsides seem to outweigh the benefits.
On the plus side, my calculations went awry- in my favor. I ended up underestimating my image size by half. Cold comfort when you remember it still won’t fit in a standard memory chip without massive waste.
Displaying an image is a big step forwards. Getting this far requires knowledge of how an image is actually laid out in a computer, as well as a lot of subtle gotchas in the hardware.
Next time I’m going to add in text mode. Very useful for small systems without much in the way of resources. It shouldn’t be a hard addition- but we’ll see.
Have a question? Comment? Insight? Post below!