abrash-black-book/44-04.md
2013-12-30 20:26:41 +11:00

123 lines
7.8 KiB
Markdown

Listing 44.1 is written in C. It could equally well have been written in
assembly language, and would then have been somewhat faster. However,
wanted to make the point (as I've made again and again) that assembly
language, and, indeed, optimization in general, is needed only in the
most critical portions of any program, and then only when the program
would otherwise be too slow. Only in a highly performance-sensitive
situation would the performance boost resulting from converting Listing
44.1 to assembly justify the time spent in coding and the bugs that
would likely creep in—and the sample program already updates the screen
at the maximum possible rate of once per frame even on a 1985-vintage
8-MHz AT. In this case, faster performance would result only in a longer
wait for the page to flip.
#### Write Mode 3 {#Heading5 align="center"}
It's possible to update the bitmap very efficiently on the VGA, because
the VGA can draw up to 8 pixels at once, and because the VGA provides a
number of hardware features to speed up drawing. This article makes
considerable use of one particularly unusual hardware feature, write
mode 3. We discussed write mode 3 back in Chapter 26, but we've covered
a lot of ground since then—so I'm going to run through a quick refresher
on write mode 3.
Some background: In the standard VGA's high-resolution mode, mode 12H
(640x480 with 16 colors, the mode in which this chapter's sample program
runs), each byte of display memory controls 8 adjacent pixels on the
screen. (The color of each pixel is, in turn, controlled by 4 bits
spread across the four VGA memory planes, but we need not concern
ourselves with that here.) Now, there will often be times when we want
to change some but not all of the pixels controlled by a particular byte
of display memory. This is not easily done, for there is no way to write
half a byte, or two bits, or such to memory; it's the whole byte or none
of it at all.
You might think that using AND and OR to manipulate individual bits
could solve the problem. Alas, not so. ANDing and ORing would work if
the VGA had only one plane of memory (like the original monochrome
Hercules Graphics Adapter) but the VGA has four planes, and ANDing and
ORing would work only if we selected and manipulated each plane
separately, a process that would be hideously slow. No, with the VGA you
must use the hardware assist features, or you might as well forget about
real-time screen updates altogether. Write mode 3 will do the trick for
our present needs.
Write mode 3 is useful when you want to set some but not all of the
pixels in a single byte of display memory *to the same color.* That is,
if you want to draw a number of pixels within a byte in a single color,
write mode 3 is a good way to do it.
Write mode 3 works like this. First, set the Graphics Controller Mode
register to write mode 3. (Look at Listing 44.2 for code that does
everything described here.) Next, set the Set/Reset register to the
color with which you wish to draw, in the range 0-15. (It is not
necessary to explicitly enable set/reset via the Enable Set/Reset
register; write mode 3 does that automatically.) Then, to draw
individual pixels within a single byte, simply read display memory, and
then write a byte to display memory with 1-bits where you want the color
to be drawn and 0-bits where you want the current bitmap contents to be
preserved. (Note well that *the data actually read by the CPU doesn't
matter;* the read operation latches all four planes' data, as described
way back in Chapter 24.) So, for example, if write mode 3 is enabled and
the Set/Reset register is set to 1 (blue), then the following sequence
of operations:
mov dx,0a000h
mov es,dx
mov al,es:[0]
mov byte ptr es:[0],0f0h
will change the first 4 pixels on the screen (the left nibble of the
byte at offset 0 in display memory) to blue, and will leave the next 4
pixels (the right nibble of the byte at offset 0) unchanged.
Using one **MOV** to read from display memory and another to write to
display memory is not particularly efficient on some processors. In
Listing 44.2, I instead use **XCHG**, which reads and then writes a
memory location in a single operation, as in:
mov dx,0a000h
mov es,dx
mov al,0f0h
xchg es:[0],al
Again, the actual value that's read is irrelevant. In general, the
**XCHG** approach is more compact than two **MOV**s, and is faster on
386 and earlier processors, but slower on 486s and Pentiums.
If all pixels in a byte of display memory are to be drawn in a single
color, it's not necessary to read before writing, because none of the
information in display memory at that byte needs to be preserved; a
simple write of 0FFH (to draw all bits) will set all 8 pixels to the
set/reset color:
mov dx,0a000h
mov es,dx
mov byte ptr es:[di],0ffh
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *If you're familiar with VGA programming, you're no doubt aware that everything that can be done with write mode 3 can also be accomplished in write mode 0 or write mode 2 by using the Bit Mask register. However, setting the Bit Mask register requires at least one **OUT** per byte written, in addition to the read and write of display memory, and **OUT**s are often slower than display memory accesses, especially on 386s and 486s. One of the great virtues of write mode 3 is that it requires virtually no **OUT**s and is therefore substantially faster for masking than the other write modes.*
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
In short, write mode 3 is a good choice for single-color drawing that
modifies individual pixels within display memory bytes. Not
coincidentally, the sample application draws only single-color objects
within the animation area; this allows write mode 3 to be used for all
drawing, in keeping with our desire for speedy screen updates.
#### Drawing Text {#Heading6 align="center"}
We'll need text in the sample application; is that also a good use for
write mode 3? Sometimes it is, but not in this particular case.
Each character in a font is represented by a pattern of bits, with
1-bits representing character pixels and 0-bits representing background
pixels. Since we'll be using the 8x8 font stored in the BIOS ROM (a
pointer to which can be obtained by calling a BIOS service, as
illustrated by Listing 44.2), each character is exactly 8 bits, or 1
byte wide. We'll further insist that characters be placed on byte
boundaries (that is, with their left edges only at pixels with X
coordinates that are multiples of 8); this means that the character
bytes in the font are automatically aligned with display memory, and no
rotation or clipping of characters is needed. Finally, we'll draw all
text in white.