--- title: Michael Abrash's Graphics Programming Black Book, Special Edition author: Michael Abrash date: '1997-07-01' identifier: - scheme: ISBN text: 1576101746 publisher: The Coriolis Group category: 'Web and Software Development: Game Development,Web and Software Development: Graphics and Multimedia Development' chapter: '34' pages: 637-652 --- ## Chapter 34 -- Changing Colors without Writing Pixels ### Special Effects through Realtime Manipulation of DAC Colors Sometimes, strange as it may seem, the harder you try, the less you accomplish. Brute force is fine when it suffices, but it does not always suffice, and when it does not, finesse and alternative approaches are called for. Such is the case with rapidly cycling through colors by repeatedly loading the VGA's Digital to Analog Converter (DAC). No matter how much you optimize your code, you just can't reliably load the whole DAC cleanly in a single frame, so you had best find other ways to use the DAC to cycle colors. What's more, BIOS support for DAC loading is so inconsistent that it's unusable for color cycling; direct loading through the I/O ports is the only way to go. We'll see why next, as we explore color cycling, and then finish up this chapter and this section by cleaning up some odds and ends about VGA color. There's a lot to be said about loading the DAC, so let's dive right in and see where the complications lie. ### Color Cycling As we've learned in past chapters, the VGA's DAC contains 256 storage locations, each holding one 18-bit value representing an RGB color triplet organized as 6 bits per primary color. Each and every pixel generated by the VGA is fed into the DAC as an 8-bit value (refer to Chapter 33 and to Chapter A on the companion CD-ROM to see how pixels become 8-bit values in non-256 color modes) and each 8-bit value is used to look up one of the 256 values stored in the DAC. The looked-up value is then converted to analog red, green, and blue signals and sent to the monitor to form one pixel. That's straightforward enough, and we've produced some pretty impressive color effects by loading the DAC once and then playing with the 8-bit path into the DAC. Now, however, we want to generate color effects by dynamically changing the values stored in the DAC in real time, a technique that I'll call *color cycling*. The potential of color cycling should be obvious: Smooth motion can easily be simulated by altering the colors in an appropriate pattern, and all sorts of changing color effects can be produced without altering a single bit of display memory. For example, a sunset can be made to color and darken by altering the DAC locations containing the colors used to draw the sunset, or a river can be made to appear to flow by cycling through the colors used to draw the river. Another use for color cycling is in providing more realistic displays for applications like realtime 3-D games, where the VGA's 256 simultaneous colors can be made to seem like many more by changing the DAC settings from frame to frame to match the changing color demands of the rendered scene. Which leaves only one question: How do we load the DAC smoothly in realtime? Actually, so far as I know, you can't. At least you can't load the *entire* DAC—all 256 locations—frame after frame without producing distressing on-screen effects on at least some computers. In non-256 color modes, it is indeed possible to load the DAC quickly enough to cycle all displayed colors (of which there are 16 or fewer), so color cycling could be used successfully to cycle all colors in such modes. On the other hand, color paging (which flips among a number of color sets stored within the DAC in all modes other than 256 color mode, as discussed in Chapter A on the companion CD-ROM) can be used in non-256 color modes to produce many of the same effects as color cycling and is considerably simpler and more reliable then color cycling, so color paging is generally superior to color cycling whenever it's available. In short, color cycling is really the method of choice for dynamic color effects only in 256-color mode—but, regrettably, color cycling is at its least reliable and capable in that mode, as we'll see next. ### The Heart of the Problem Here's the problem with loading the entire DAC repeatedly: The DAC contains 256 color storage locations, each loaded via either 3 or 4 `OUT` instructions (more on that next), so at least 768 `OUT`s are needed to load the entire DAC. That many `OUT`s take a considerable amount of time, all the more so because `OUT`s are painfully slow on 486s and Pentiums, and because the DAC is frequently on the ISA bus (although VLB and PCI are increasingly common), where wait states are inserted in fast computers. In an 8 MHz AT, 768 `OUT`s alone would take 288 microseconds, and the data loading and looping that are also required would take in the ballpark of 1,800 microseconds more, for a minimum of 2 milliseconds total. As it happens, the DAC should only be loaded during vertical blanking; that is, the time between the end of displaying the bottom border and the start of displaying the top border, when no video information at all is being sent to the screen by the DAC. Otherwise, small dots of snow appear on the screen, and while an occasional dot of this sort wouldn't be a problem, the constant DAC loading required by color cycling would produce a veritable snowstorm on the screen. By the way, I do mean "border," not "frame buffer"; the overscan pixels pass through the DAC just like the pixels controlled by the frame buffer, so you can't even load the DAC while the border color is being displayed without getting snow. The start of vertical blanking itself is not easy to find, but the leading edge of the vertical sync pulse is easy to detect via bit 3 of the Input Status 1 register at 3DAH; when bit 3 is 1, the vertical sync pulse is active. Conveniently, the vertical sync pulse starts partway through but not too far into vertical blanking, so it serves as a handy way to tell when it's safe to load the DAC without producing snow on the screen. So we wait for the start of the vertical sync pulse, then begin to load the DAC. There's a catch, though. On many computers—Pentiums, 486s, and 386s sometimes, 286s most of the time, and 8088s all the time—there just isn't enough time between the start of the vertical sync pulse and the end of vertical blanking to load all 256 DAC locations. That's the crux of the problem with the DAC, and shortly we'll get to a tool that will let you explore for yourself the extent of the problem on computers in which you're interested. First, though, we must address *another* DAC loading problem: the BIOS. #### Loading the DAC via the BIOS The DAC can be loaded either directly or through subfunctions 10H (for a single DAC register) or 12H (for a block of DAC registers) of the BIOS video service interrupt 10H, function 10H, described in Chapter 33. For cycling the contents of the entire DAC, the block-load function (invoked by executing `INT` 10H with AH = 10H and AL = 12H to load a block of CX DAC locations, starting at location BX, from the block of RGB triplets—3 bytes per triplet—starting at ES:DX into the DAC) would be the better of the two, due to the considerably greater efficiency of calling the BIOS once rather than 256 times. At any rate, we'd like to use one or the other of the BIOS functions for color cycling, because we know that whenever possible, one should use a BIOS function in preference to accessing hardware directly, in the interests of avoiding compatibility problems. In the case of color cycling, however, it is emphatically *not* possible to use either of the BIOS functions, for they have problems. Serious problems. The difficulty is this: IBM's BIOS specification describes exactly how the parameters passed to the BIOS control the loading of DAC locations, and all clone BIOSes meet that specification scrupulously, which is to say that if you invoke `INT` 10H, function 10H, subfunction 12H with a given set of parameters, you can be sure that you will end up with the same values loaded into the same DAC locations on all VGAs from all vendors. IBM's spec does *not*, however, describe whether vertical retrace should be waited for before loading the DAC, nor does it mention whether video should be left enabled while loading the DAC, leaving cloners to choose whatever approach they desire—and, alas, every VGA cloner seems to have selected a different approach. I tested four clone VGAs from different manufacturers, some in a 20 MHz 386 machine and some in a 10 MHz 286 machine. Two of the four waited for vertical retrace before loading the DAC; two didn't. Two of the four blanked the display while loading the DAC, resulting in flickering bars across the screen. One showed speckled pixels spattered across the top of the screen while the DAC was being loaded. Also, not one was able to load all 256 DAC locations without showing *some* sort of garbage on the screen for at least one frame, but that's not the BIOS's fault; it's a problem endemic to the VGA. > ![](images/i.jpg) > These findings lead me inexorably to the conclusion that the BIOS should > not be used to load the DAC dynamically. That is, if you're loading the > DAC just once in preparation for a graphics session—sort of a DAC mode > set—by all means load by way of the BIOS. No one will care that some > garbage is displayed for a single frame; heck, I have boards that bounce > and flicker and show garbage every time I do a mode set, and the amount > of garbage produced by loading the DAC once is far less noticeable. If, > however, you intend to load the DAC repeatedly for color cycling, avoid > the BIOS DAC load functions like the plague. They will bring you only > heartache. As but one example of the unsuitability of the BIOS DAC-loading functions for color cycling, imagine that you want to cycle all 256 colors 70 times a second, which is once per frame. In order to accomplish that, you would normally wait for the start of the vertical sync signal (marking the end of the frame), then call the BIOS to load the DAC. On some boards—boards with BIOSes that don't wait for vertical sync before loading the DAC—that will work pretty well; you will, in fact, load the DAC once a frame. On other boards, however, it will work very poorly indeed; your program will wait for the start of vertical sync, and then the BIOS will wait for the start of the next vertical sync, with the result being that the DAC gets loaded only once every *two* frames. Sadly, there's no way, short of actually profiling the performance of BIOS DAC loads, for you to know which sort of BIOS is installed in a particular computer, so unless you can always control the brand of VGA your software will run on, you really can't afford to color cycle by calling the BIOS. Which is not to say that loading the DAC directly is a picnic either, as we'll see next. #### Loading the DAC Directly So we must load the DAC directly in order to perform color cycling. The DAC is loaded directly by sending (with an `OUT` instruction) the number of the DAC location to be loaded to the DAC Write Index register at 3C8H and then performing three `OUT`s to write an RGB triplet to the DAC Data register at 3C9H. This approach must be repeated 256 times to load the entire DAC, requiring over a thousand `OUT`s in all. There is another, somewhat faster approach, but one that has its risks. After an RGB triplet is written to the DAC Data register, the DAC Write Index register automatically increments to point to the next DAC location, and this repeats indefinitely as successive RGB triplets are written to the DAC. By taking advantage of this feature, the entire DAC can be loaded with just 769 `OUT`s: one `OUT` to the DAC Write Index register and 768 `OUT`s to the DAC Data register. So what's the drawback? Well, imagine that as you're loading the DAC, an interrupt-driven TSR (such as a program switcher or multitasker) activates and writes to the DAC; you could end up with quite a mess on the screen, especially when your program resumes and continues writing to the DAC—but in all likelihood to the wrong locations. No problem, you say; just disable interrupts for the duration. Good idea—but it takes much longer to load the DAC than interrupts should be disabled for. If, on the other hand, you set the index for each DAC location separately, you can disable interrupts 256 times, once as each DAC location is loaded, without problems. As I commented in the last chapter, I don't have any gruesome tale to relate that mandates taking the slower but safer road and setting the index for each DAC location separately while interrupts are disabled. I'm merely hypothesizing as to what ghastly mishaps *could*. happen. However, it's been my experience that anything that can happen on the PC *does* happen eventually; there are just too dang many PCs out there for it to be otherwise. However, load the DAC any way you like; just don't blame me if you get a call from someone who's claims that your program sometimes turns their screen into something resembling month-old yogurt. It's not really your fault, of course—but try explaining that to *them!* ### A Test Program for Color Cycling Anyway, the choice of how to load the DAC is yours. Given that I'm not providing you with any hard-and-fast rules (mainly because there don't seem to be any), what you need is a tool so that you can experiment with various DAC-loading approaches for yourself, and that's exactly what you'll find in Listing 34.1. Listing 34.1 draws a band of vertical lines, each one pixel wide, across the screen. The attribute of each vertical line is one greater than that of the preceding line, so there's a smooth gradient of attributes from left to right. Once everything is set up, the program starts cycling the colors stored in however many DAC locations are specified by the `CYCLE_SIZE` equate; as many as all 256 DAC locations can be cycled. (Actually, `CYCLE_SIZE`-1 locations are cycled, because location 0 is kept constant in order to keep the background and border colors from changing, but `CYCLE_SIZE` locations are *loaded*, and it's the number of locations we can load without problems that we're interested in.) **LISTING 34.1 L34-1.ASM** ```nasm ; Fills a band across the screen with vertical bars in all 256 ; attributes, then cycles a portion of the palette until a key is ; pressed. ; Assemble with MASM or TASM USE_BIOS equ 1 ;set to 1 to use BIOS functions to access the ; DAC, 0 to read and write the DAC directly GUARD_AGAINST_INTS equ 1 ;1 to turn off interrupts and set write index ; before loading each DAC location, 0 to rely ; on the DAC auto-incrementing WAIT_VSYNC equ 1 ;set to 1 to wait for the leading edge of ; vertical sync before accessing the DAC, 0 ; not to wait NOT_8088 equ 0 ;set to 1 to use REP INSB and REP OUTSB when ; accessing the DAC directly, 0 to use ; IN/STOSB and LODSB/OUT CYCLE_SIZE equ 256 ;# of DAC locations to cycle, 256 max SCREEN_SEGMENT equ 0a000h ;mode 13h display memory segment SCREEN_WIDTH_IN_BYTES equ 320 ;# of bytes across the screen in mode 13h INPUT_STATUS_1 equ 03dah ;input status 1 register port DAC_READ_INDEX equ 03c7h ;DAC Read Index register DAC_WRITE_INDEX equ 03c8h ;DAC Write Index register DAC_DATA equ 03c9h ;DAC Data register if NOT_8088 .286 endif ;NOT_8088 .model small .stack 100h .data ;Storage for all 256 DAC locations, organized as one three-byte ; (actually three 6-bit values; upper two bits of each byte aren't ; significant) RGB triplet per color. PaletteTempdb 256*3 dup(?) .code start: mov ax,@data mov ds,ax ;Select VGA's standard 256-color graphics mode, mode 13h. mov ax,0013h ;AH = 0: set mode function, int 10h ; AL = 13h: mode # to set ;Read all 256 DAC locations into PaletteTemp (3 6-bit values, one ; each for red, green, and blue, per DAC location). if WAIT_VSYNC ;Wait for the leading edge of the vertical sync pulse; this ensures ; that we read the DAC starting during the vertical non-display ; period. mov dx,INPUT_STATUS_1 WaitNotVSync: ;wait to be out of vertical sync in al,dx and al,08h jnz WaitNotVSync WaitVSync: ;wait until vertical sync begins in al,dx and al,08h jz WaitVSync endif ;WAIT_VSYNC if USE_BIOS mov ax,1017h ;AH = 10h: set DAC function, ; AL = 17h: read DAC block subfunction sub bx,bx ;start with DAC location 0 mov cx,256 ;read out all 256 locations mov dx,seg PaletteTemp mov es,dx mov dx,offset PaletteTemp ;point ES:DX to array in which ; the DAC values are to be stored int 10h ;read the DAC else ;!USE_BIOS if GUARD_AGAINST_INTS mov cx,CYCLE_SIZE ;# of DAC locations to load mov di,seg PaletteTemp mov es,di mov di,offset PaletteTemp ;dump the DAC into this array sub ah,ah ;start with DAC location 0 DACStoreLoop: mov dx,DAC_READ_INDEX mov al,ah cli out dx,al ;set the DAC location # mov dx,DAC_DATA in al,dx ;get the red component stosb in al,dx ;get the green component stosb in al,dx ;get the blue component stosb sti inc ah loopDACStoreLoop else;!GUARD_AGAINST_INTS mov dx,DAC_READ_INDEX sub al,al out dx,al ;set the initial DAC location to 0 mov di,seg PaletteTemp mov es,di mov di,offset PaletteTemp ;dump the DAC into this array mov dx,DAC_DATA if NOT_8088 mov cx,CYCLE_SIZE*3 rep insb ;read CYCLE_SIZE DAC locations at once else;!NOT_8088 mov cx,CYCLE_SIZE ;# of DAC locations to load DACStoreLoop: in al,dx ;get the red component stosb in al,dx ;get the green component stosb in al,dx ;get the blue component stosb loop DACStoreLoop endif ;NOT_8088 endif ;GUARD_AGAINST_INTS endif ;USE_BIOS ;Draw a series of 1-pixel-wide vertical bars across the screen in ; attributes 1 through 255. mov ax,SCREEN_SEGMENT mov es,ax mov di,50*SCREEN_WIDTH_IN_BYTES ;point ES:DI to the start ; of line 50 on the screen cld mov dx,100 ;draw 100 lines high RowLoop: mov al,1 ;start each line with attr 1 mov cx,SCREEN_WIDTH_IN_BYTES ;do a full line across ColumnLoop: stosb ;draw a pixel add al,1 ;increment the attribute ad cal,0 ;if the attribute just turned ; over to 0, increment it to 1 ; because we're not going to ; cycle DAC location 0, so ; attribute 0 won't change loop ColumnLoop dec dx jnz RowLoop ;Cycle the specified range of DAC locations until a key is pressed. CycleLoop: ;Rotate colors 1-255 one position in the PaletteTemp array; ; location 0 is always left unchanged so that the background ; and border don't change. push word ptr PaletteTemp+(1*3) ;set aside PaletteTemp push word ptr PaletteTemp+(1*3)+2; setting for attr 1 mov cx,254 mov si,offset PaletteTemp+(2*3) mov di,offset PaletteTemp+(1*3) mov ax,ds mov es,ax mov cx,254*3/2 rep movsw ;rotate PaletteTemp settings ; for attrs 2 through 255 to ; attrs 1 through 254 pop bx ;get back original settings pop ax ; for attribute 1 and move stosw ; them to the PaletteTemp mov es:[di],bl ; location for attribute 255 if WAIT_VSYNC ;Wait for the leading edge of the vertical sync pulse; this ensures ; that we reload the DAC starting during the vertical non-display ; period. mov dx,INPUT_STATUS_1 WaitNotVSync2: ;wait to be out of vertical sync in al,dx and al,08h jnz WaitNotVSync2 WaitVSync2: ;wait until vertical sync begins in al,dx and al,08h jz WaitVSync2 endif ;WAIT_VSYNC if USE_BIOS ;Set the new, rotated palette. mov ax,1012h ;AH = 10h: set DAC function, ; AL = 12h: set DAC block subfunction sub bx,bx ;start with DAC location 0 mov cx,CYCLE_SIZE ;# of DAC locations to set mov dx,seg PaletteTemp mov es,dx mov dx,offset PaletteTemp ;point ES:DX to array from which ; to load the DAC int 10h ;load the DAC else ;!USE_BIOS if GUARD_AGAINST_INTS mov cx,CYCLE_SIZE ;# of DAC locations to load mov si,offset PaletteTemp ;load the DAC from this array sub ah,ah ;start with DAC location 0 DACLoadLoop: mov dx,DAC_WRITE_INDEX mov al,ah cli out dx,al ;set the DAC location # mov dx,DAC_DATA lodsb out dx,al ;set the red component lodsb out dx,al ;set the green component lodsb out dx,al ;set the blue component sti inc ah loop DACLoadLoop else;!GUARD_AGAINST_INTS mov dx,DAC_WRITE_INDEX sub al,al out dx,al ;set the initial DAC location to 0 mov si,offset PaletteTemp ;load the DAC from this array mov dx,DAC_DATA if NOT_8088 mov cx,CYCLE_SIZE*3 rep outsb ;load CYCLE_SIZE DAC locations at once else;!NOT_8088 mov cx,CYCLE_SIZE ;# of DAC locations to load DACLoadLoop: lodsb out dx,al ;set the red component lodsb out dx,al ;set the green component lodsb out dx,al ;set the blue component loop DACLoadLoop endif;NOT_8088 endif;GUARD_AGAINST_INTS endif;USE_BIOS ;See if a key has been pressed. mov ah,0bh ;DOS check standard input status fn int 21h and al,al ;is a key pending? jz CycleLoop ;no, cycle some more ;Clear the keypress. mov ah,1 ;DOS keyboard input fn int 21h ;Restore text mode and done. mov ax,0003h ;AH = 0: set mode function, int 10h ; AL = 03h: mode # to set mov ah,4ch ;DOS terminate process fn int 21h endstart ``` The big question is, How does Listing 34.1 cycle colors? Via the BIOS or directly? With interrupts enabled or disabled? *Et cetera?* However you like, actually. Four equates at the top of Listing 34.1 select the sort of color cycling performed; by changing these equates and `CYCLE_SIZE`, you can get a feel for how well various approaches to color cycling work with whatever combination of computer system and VGA you care to test. The `USE_BIOS` equate is simple. Set `USE_BIOS` to 1 to load the DAC through the block-load-DAC BIOS function, or to 0 to load the DAC directly with `OUT`s. If `USE_BIOS` is 1, the only other equate of interest is `WAIT_VSYNC`. If `WAIT_VSYNC` is 1, the program waits for the leading edge of vertical sync before loading the DAC; if `WAIT_VSYNC` is 0, the program doesn't wait before loading. The effect of setting or not setting `WAIT_VSYNC` depends on whether the BIOS of the VGA the program is running on waits for vertical sync before loading the DAC. You may end up with a double wait, causing color cycling to proceed at half speed, you may end up with no wait at all, causing cycling to occur far too rapidly (and almost certainly with hideous on-screen effects), or you may actually end up cycling at the proper one-cycle-per-frame rate. If `USE_BIOS` is 0, `WAIT_VSYNC` still applies. However, you will always want to set `WAIT_VSYNC` to 1 when `USE_BIOS` is 0; otherwise, cycling will occur much too fast, and a good deal of continuous on-screen garbage is likely to make itself evident as the program loads the DAC non-stop. If `USE_BIOS` is 0, `GUARD_AGAINST_INTS` determines whether the possibility of the DAC loading process being interrupted is guarded against by disabling interrupts and setting the write index once for every location loaded and whether the DAC's autoincrementing feature is relied upon or not. If `GUARD_AGAINST_INTS` is 1, the following sequence is followed for the loading of each DAC location in turn: Interrupts are disabled, the DAC Write Index register is set appropriately, the RGB triplet for the location is written to the DAC Data register, and interrupts are enabled. This is the slow but safe approach described earlier. Matters get still more interesting if `GUARD_AGAINST_INTS` is 0. In that case, if `NOT_8088` is 0, then an autoincrementing load is performed in a straightforward fashion; the DAC Write Index register is set to the index of the first location to load and the RGB triplet is sent to the DAC by way of three `LODSB/OUT DX,AL` pairs, with `LOOP` repeating the process for each of the locations in turn. If, however, `NOT_8088` is 1, indicating that the processor is a 286 or better (perhaps `AT_LEAST_286` would have been a better name), then after the initial DAC Write Index value is set, all 768 DAC locations are loaded with a single `REP OUTSB`. This is clearly the fastest approach, but it runs the risk, albeit remote, that the loading sequence will be interrupted and the DAC registers will become garbled. My own experience with Listing 34.1 indicates that it is sometimes possible to load all 256 locations cleanly but sometimes it is not; it all depends on the processor, the bus speed, the VGA, and the DAC, as well as whether autoincrementation and `REP OUTSB` are used. I'm not going to bother to report how many DAC locations I *could* successfully load with each of the various approaches, for the simple reason that I don't have enough data points to make reliable suggestions, and I don't want you acting on my comments and running into trouble down the pike. You now have a versatile tool with which to probe the limitations of various DAC-loading approaches; use it to perform your own tests on a sampling of the slowest hardware configurations you expect your programs to run on, then leave a generous safety margin. One thing's for sure, though—you're not going to be able to cycle all 256 DAC locations cleanly once per frame on a reliable basis across the current generation of PCs. That's why I said at the outset that brute force isn't appropriate to the task of color cycling. That doesn't mean that color cycling can't be used, just that subtler approaches must be employed. Let's look at some of those alternatives. ### Color Cycling Approaches that Work First of all, I'd like to point out that when color cycling does work, it's a thing of beauty. Assemble Listing 34.1 so that it doesn't use the BIOS to load the DAC, doesn't guard against interrupts, and uses 286-specific instructions if your computer supports them. Then tinker with `CYCLE_SIZE` until the color cycling is perfectly clean on your computer. Color cycling looks stunningly smooth, doesn't it? And this is crude color cycling, working with the default color set; switch over to a color set that gradually works its way through various hues and saturations, and you could get something that looks for all the world like true-color animation (albeit working with a small subset of the full spectrum at any one time). Given that, how can we take advantage of color cycling within the limitations of loading the DAC? The simplest approach, and my personal favorite, is that of cycling a portion of the DAC while using the rest of the DAC locations for other, non-cycling purposes. For example, you might allocate 32 DAC locations to the aforementioned sunset, reserve 160 additional locations for use in drawing a static mountain scene, and employ the remaining 64 locations to draw images of planes, cars, and the like in the foreground. The 32 sunset colors could be cycled cleanly, and the other 224 colors would remain the same throughout the program, or would change only occasionally. That suggests a second possibility: If you have several different color sets to be cycled, interleave the loading so that only one color set is cycled per frame. Suppose you are animating a night scene, with stars twinkling in the background, meteors streaking across the sky, and a spaceship moving across the screen with its jets flaring. One way to produce most of the necessary effects with little effort would be to draw the stars in several attributes and then cycle the colors for *those* attributes, draw the meteor paths in successive attributes, one for each pixel, and then cycle the colors for those attributes, and do much the same for the jets. The only remaining task would be to animate the spaceship across the screen, which is not a particularly difficult task. > ![](images/i.jpg) > The key to getting all the color cycling to work in the above example, > however, would be to assign each color cycling task a different part of > the DAC, with each part cycled independently as needed. If, as is > likely, the total number of DAC locations cycled proved to be too great > to manage in one frame, you could simply cycle the colors of the stars > after one frame, the colors of the meteors after the next, and the > colors of the jets after yet another frame, then back around to cycling > the colors of the stars. By splitting up the DAC in this manner and > interleaving the cycling tasks, you can perform a great deal of > seemingly complex color animation without loading very much of the DAC > during any one frame. Yet another and somewhat odder workaround is that of using only 128 DAC locations and page flipping. (Page flipping in 256-color modes involves using the VGA's undocumented 256-color modes; see Chapters 31, 43, and 47 for details.) In this mode of operation, you'd first display page 0, which is drawn entirely with colors 0-127. Then you'd draw page 1 to look just like page 0, except that colors 128-255 are used instead. You'd load DAC locations 128-255 with the next cycle settings for the 128 colors you're using, then you'd switch to display the second page with the new colors. Then you could modify page 0 as needed, drawing in colors 0-127, load DAC locations 0-127 with the next color cycle settings, and flip back to page 0. The idea is that you modify only those DAC locations that are not used to display any pixels on the current screen. The advantage of this is *not*, as you might think, that you don't generate garbage on the screen when modifying undisplayed DAC locations; in fact, you do, for a spot of interference will show up if you set a DAC location, displayed or not, during display time. No, you still have to wait for vertical sync and load only during vertical blanking before loading the DAC when page flipping with 128 colors; the advantage is that since none of the DAC locations you're modifying is currently displayed, you can spread the loading out over two or more vertical blanking periods—however long it takes. If you did this without the 128-color page flipping, you might get odd on-screen effects as some of the colors changed after one frame, some after the next, and so on—or you might not; changing the entire DAC in chunks over several frames is another possibility worth considering. Yet another approach to color cycling is that of loading a bit of the DAC during each horizontal blanking period. Combine that with counting scan lines, and you could vastly expand the number of simultaneous on-screen colors by cycling colors *as a frame is displayed*, so that the color set changes from scan line to scan line down the screen. The possibilities are endless. However, were I to be writing 256-color software that used color cycling, I'd find out how many colors could be cycled after the start of vertical sync on the slowest computer I expected the software to run on, I'd lop off at least 10 percent for a safety margin, and I'd structure my program so that no color cycling set exceeded that size, interleaving several color cycling sets if necessary. That's what *I'd* do. Don't let yourself be held back by my limited imagination, though! Color cycling may be the most complicated of all the color control techniques, but it's also the most powerful. ### Odds and Ends In my experience, when relying on the autoincrementing feature while loading the DAC, the Write Index register wraps back from 255 to 0, and likewise when you load a block of registers through the BIOS. So far as I know, this is a characteristic of the hardware, and should be consistent; also, Richard Wilton documents this behavior for the BIOS in the VGA bible, *Programmer's Guide to PC Video Systems, Second Edition* (Microsoft Press), so you should be able to count on it. Not that I see that DAC index wrapping is especially useful, but it never hurts to understand exactly how your resources behave, and I never know when one of you might come up with a serviceable application for any particular quirk. #### The DAC Mask There's one register in the DAC that I haven't mentioned yet, the DAC Mask register at 03C6H. The operation of this register is simple but powerful; it can mask off any or all of the 8 bits of pixel information coming into the DAC from the VGA. Whenever a bit of the DAC Mask register is 1, the corresponding bit of pixel information is passed along to the DAC to be used in looking up the RGB triplet to be sent to the screen. Whenever a bit of the DAC Mask register is 0, the corresponding pixel bit is ignored, and a 0 is used for that bit position in all look-ups of RGB triplets. At the extreme, a DAC Mask setting of 0 causes all 8 bits of pixel information to be ignored, so DAC location 0 is looked up for every pixel, and the entire screen displays the color stored in DAC location 0. This makes setting the DAC Mask register to 0 a quick and easy way to blank the screen. #### Reading the DAC The DAC can be read directly, via the DAC Read Index register at 3C7H and the DAC Data register at 3C9H, in much the same way as it can be written directly by way of the DAC Write Index register—complete with autoincrementing the DAC Read Index register after every three reads. Everything I've said about writing to the DAC applies to reading from the DAC. In fact, reading from the DAC can even cause snow, just as loading the DAC does, so it should ideally be performed during vertical blanking. The DAC can also be read by way of the BIOS in either of two ways. `INT` 10H, function 10H (AH=10H), subfunction 15H (AL=15H) reads out a single DAC location, specified by BX; this function returns the RGB triplet stored in the specified location with the red component in the lower 6 bits of DH, the green component in the lower 6 bits of CH, and the blue component in the lower 6 bits of CL. `INT` 10H, function 10H (AH=10H), subfunction 17H (AL=17H) reads out a block of DAC locations of length CX, starting with the location specified by BX. ES:DX must point to the buffer in which the RGB values from the specified block of DAC locations are to be stored. The form of this buffer (RGB, RGB, RGB ..., with three bytes per RGB triple) is exactly the same as that of the buffer used when calling the BIOS to load a block of registers. Listing 34.1 illustrates reading the DAC both through the BIOS block-read function and directly, with the direct-read code capable of conditionally assembling to either guard against interrupts or not and to use `REP INSB` or not. As you can see, reading the DAC settings is very much symmetric with setting the DAC. #### Cycling Down And so, at long last, we come to the end of our discussion of color control on the VGA. If it has been more complex than anyone might have imagined, it has also been most rewarding. There's as much obscure but very real potential in color control as there is anywhere on the VGA, which is to say that there's a very great deal of potential indeed. Put color cycling or color paging together with the page flipping and image drawing techniques explored elsewhere in this book, and you'll leave the audience gasping and wondering "How the heck did they *do* that?"