Previous | Table of Contents | Next

#### Linear Planes and True VGA Modes

The VGA’s memory is organized as four 64K planes. Each of these planes is a linear bitmap; that is, each byte from a given plane controls eight adjacent pixels on the screen, the next byte controls the next eight pixels, and so on to the end of the scan line. The next byte then controls the first eight pixels of the next scan line, and so on to the end of the screen.

The VGA adds a powerful twist to linear addressing; the logical width of the screen in VGA memory need not be the same as the physical width of the display. The programmer is free to define all or part of the VGA’s large memory map as a logical screen of up to 4,080 pixels in width, and then use the physical screen as a window onto any part of the logical screen. What’s more, a virtual screen can have any logical height up to the capacity of VGA memory. Such a virtual screen could be used to store a spreadsheet or a CAD/CAM drawing, for instance. As we will see shortly, the VGA provides excellent hardware for moving around the virtual screen; taken together, the virtual screen and the VGA’s smooth panning capabilities can generate very impressive effects.

All four linear planes are addressed in the same 64K memory space starting at A000:0000. Consequently, there are four bytes at any given address in VGA memory. The VGA provides special hardware to assist the CPU in manipulating all four planes, in parallel, with a single memory access, so that the programmer doesn’t have to spend a great deal of time switching between planes. Astute use of this VGA hardware allows VGA software to as much as quadruple performance by processing the data for all the planes in parallel.

Each memory plane provides one bit of data for each pixel. The bits for a given pixel from each of the four planes are combined into a nibble that serves as an address into the VGA’s palette RAM, which maps the one of 16 colors selected by display memory into any one of 64 colors, as shown in Figure 23.1. All sixty-four mappings for all 16 colors are independently programmable. (We’ll discuss the VGA’s color capabilities in detail starting in Chapter 33.)

The VGA BIOS supports several graphics modes (modes 4, 5, and 6) in which VGA memory appears not to be organized as four linear planes. These modes exist for CGA compatibility only, and are not true VGA graphics modes; use them when you need CGA-type operation and ignore them the rest of the time. The VGA’s special features are most powerful in true VGA modes, and it is on the 16-color true-VGA modes (modes 0DH (320×200), 0EH (640×200), 10H (640×350), and 12H (640×480)) that I will concentrate in this part of the book. There is also a 256-color mode, mode 13H, that appears to be a single linear plane, but, as we will see in Chapters 31–34 and 47–49 of this book, that’s a polite fiction—and discarding that fiction gives us an opportunity to unleash the power of the VGA’s hardware for vastly better performance. VGA text modes, which feature soft fonts, are another matter entirely, upon which we’ll touch from time to time.

Figure 23.1 Video data from memory to pixel.

With that background out of the way, we can get on to the sample VGA program shown in Listing 23.1. I suggest you run the program before continuing, since the explanations will mean far more to you if you’ve seen the features in action.

LISTING 23.1 L23-1.ASM

    ; Sample VGA program.
    ; Animates four balls bouncing around a playfield by using
    ; page flipping. Playfield is panned smoothly both horizontally
    ; and vertically.
    ; By Michael Abrash.
    ;
    stack   segment para stack 'STACK'
            db      512 dup(?)
    stack   ends
    ;
    MEDRES_VIDEO_MODE       equ     0       ;define for 640x350 video mode
                                            ; comment out for 640x200 mode
    VIDEO_SEGMENT   equ     0a000h          ;display memory segment for
                                            ; true VGA graphics modes
    LOGICAL_SCREEN_WIDTH    equ     672/8   ;width in bytes and height in scan
    LOGICAL_SCREEN_HEIGHT   equ     384     ; lines of the virtual screen
                                            ; we'll work with
    PAGE0           equ     0       ;flag for page 0 when page flipping
    PAGE1           equ     1       ;flag for page 1 when page flipping
    PAGE0_OFFSET    equ     0       ;start offset of page 0 in VGA memory
    PAGE1_OFFSET    equ     LOGICAL_SCREEN_WIDTH * LOGICAL_SCREEN_HEIGHT
                                    ;start offset of page 1 (both pages
                                    ; are 672x384 virtual screens)
    BALL_WIDTH      equ     24/8    ;width of ball in display memory bytes
    BALL_HEIGHT     equ     24      ;height of ball in scan lines
    BLANK_OFFSET    equ     PAGE1_OFFSET * 2        ;start of blank image
                                                    ; in VGA memory
    BALL_OFFSET     equ     BLANK_OFFSET + (BALL_WIDTH * BALL_HEIGHT)
                                    ;start offset of ball image in VGA memory
    NUM_BALLS       equ     4       ;number of balls to animate
    ;
    ; VGA register equates.
    ;
    SC_INDEX        equ     3c4h    ;SC index register
    MAP_MASK        equ     2       ;SC map mask register
    GC_INDEX        equ     3ceh    ;GC index register
    GC_MODE         equ     5       ;GC mode register
    CRTC_INDEX      equ     03d4h   ;CRTC index register
    START_ADDRESS_HIGH equ  0ch     ;CRTC start address high byte
    START_ADDRESS_LOW equ   0dh     ;CRTC start address low byte
    CRTC_OFFSET     equ     13h     ;CRTC offset register
    INPUT_STATUS_1  equ     03dah   ;VGA status register
    VSYNC_MASK      equ     08h     ;vertical sync bit in status register 1
    DE_MASK         equ     01h     ;display enable bit in status register 1
    AC_INDEX        equ     03c0h   ;AC index register
    HPELPAN         equ     20h OR 13h   ;AC horizontal pel panning register
                                         ; (bit 7 is high to keep palette RAM
                                         ; addressing on)
    dseg    segment para common 'DATA'
    CurrentPage             db      PAGE1           ;page to draw to
    CurrentPageOffset       dw      PAGE1_OFFSET
    ;
    ; Four plane's worth of multicolored ball image.
    ;
    BallPlane0Image label   byte            ;blue plane image
            db      000h, 03ch, 000h, 001h, 0ffh, 080h
            db      007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h
            db      4 * 3 dup(000h)
            db      07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh
            db      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
            db      4 * 3 dup(000h)
            db      07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch
            db      03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h
            db      4 * 3 dup(000h)
    BallPlane1Image label   byte            ;green plane image
            db      4 * 3 dup(000h)
            db      01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch
            db      03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh
            db      07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh
            db      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
            db      8 * 3 dup(000h)
            db      00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h
            db      001h, 0ffh, 080h, 000h, 03ch, 000h
    BallPlane2Image label   byte            ;red plane image
            db      12 * 3 dup(000h)
            db      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
            db      0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh
            db      07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch
            db      03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h
            db      00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h
            db      001h, 0ffh, 080h, 000h, 03ch, 000h
    BallPlane3Image label   byte            ;intensity on for all planes,
                                            ; to produce high-intensity colors
            db      000h, 03ch, 000h, 001h, 0ffh, 080h
            db      007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h
            db      01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch
            db      03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh
            db      07fh, 0ffh, 0feh, 0ffh, 0ffh, 0ffh
            db      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
            db      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
            db      0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh
            db      07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch
            db      03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h
            db      00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h
            db      001h, 0ffh, 080h, 000h, 03ch, 000h
    ;
    BallX           dw      15, 50, 40, 70          ;array of ball x coords
    BallY           dw      40, 200, 110, 300       ;array of ball y coords
    LastBallX       dw      15, 50, 40, 70          ;previous ball x coords
    LastBallY       dw      40, 100, 160, 30        ;previous ball y coords
    BallXInc        dw      1, 1, 1, 1              ;x move factors for ball
    BallYInc        dw      8, 8, 8, 8              ;y move factors for ball
    BallRep         dw      1, 1, 1, 1              ;# times to keep moving
                                                    ; ball according to current
                                                    ; increments
    BallControl     dw      Ball0Control, Ball1Control     ;pointers to current
                    dw      Ball2Control, Ball3Control     ; locations in ball
                                                           ; control strings
    BallControlString     dw    Ball0Control, Ball1Control ;pointers to
                          dw    Ball2Control, Ball3Control ; start of ball
                                                           ; control strings
    ;
    ; Ball control strings.
    ;
    Ball0Control    label   word
            dw      10, 1, 4, 10, -1, 4, 10, -1, -4, 10, 1, -4, 0
    Ball1Control    label   word
            dw      12, -1, 1, 28, -1, -1, 12, 1, -1, 28, 1, 1, 0
    Ball2Control    label   word
            dw      20, 0, -1, 40, 0, 1, 20, 0, -1, 0
    Ball3Control    label   word
            dw      8, 1, 0, 52, -1, 0, 44, 1, 0, 0
    ;
    ; Panning control string.
    ;
    ifdef MEDRES_VIDEO_MODE
    PanningControlString    dw      32, 1, 0, 34, 0, 1, 32, -1, 0, 34, 0, -1, 0
    else
    PanningControlString    dw      32, 1, 0, 184, 0, 1, 32, -1, 0, 184, 0, -1, 0
    endif
    PanningControl  dw      PanningControlString   ;pointer to current location
                                                   ; in panning control string
    PanningRep      dw      1      ;# times to pan according to current
                                   ; panning increments
    PanningXInc     dw      1      ;x panning factor
    PanningYInc     dw      0      ;y panning factor
    HPan            db      0      ;horizontal pel panning setting
    PanningStartOffset dw   0      ;start offset adjustment to produce vertical
                                   ; panning & coarse horizontal panning
    dseg    ends
    ;
    ; Macro to set indexed register P2 of chip with index register
    ; at P1 to AL.
    ;
    SETREG  macro   P1, P2
            mov     dx,P1
            mov     ah,al
            mov     al,P2
            out     dx,ax
            endm
    ;
    cseg    segment para public 'CODE'
            assume  cs:cseg, ds:dseg
    start   proc    near
            mov     ax,dseg
            mov     ds,ax
    ;
    ; Select graphics mode.
    ;
    ifdef MEDRES_VIDEO_MODE
            mov     ax,010h
    else
            mov     ax,0eh
    endif
            int     10h
    ;
    ; ES always points to VGA memory.
    ;
            mov     ax,VIDEO_SEGMENT
            mov     es,ax
    ;
    ; Draw border around playfield in both pages.
    ;
            mov     di,PAGE0_OFFSET
            call    DrawBorder      ;page 0 border
            mov     di,PAGE1_OFFSET
            call    DrawBorder      ;page 1 border
    ;
    ; Draw all four plane's worth of the ball to undisplayed VGA memory.
    ;
            mov     al,01h          ;enable plane 0
            SETREG  SC_INDEX, MAP_MASK
            mov     si,offset BallPlane0Image
            mov     di,BALL_OFFSET
            mov     cx,BALL_WIDTH * BALL_HEIGHT
            rep movsb
            mov     al,02h          ;enable plane 1
            SETREG  SC_INDEX, MAP_MASK
            mov     si,offset BallPlane1Image
            mov     di,BALL_OFFSET
            mov     cx,BALL_WIDTH * BALL_HEIGHT
            rep movsb
            mov     al,04h          ;enable plane 2
            SETREG  SC_INDEX, MAP_MASK
            mov     si,offset BallPlane2Image
            mov     di,BALL_OFFSET
            mov     cx,BALL_WIDTH * BALL_HEIGHT
            rep movsb
            mov     al,08h          ;enable plane 3
            SETREG  SC_INDEX, MAP_MASK
            mov     si,offset BallPlane3Image
            mov     di,BALL_OFFSET
            mov     cx,BALL_WIDTH * BALL_HEIGHT
            rep movsb
    ;
    ; Draw a blank image the size of the ball to undisplayed VGA memory.
    ;
            mov     al,0fh                 ;enable all memory planes, since the
            SETREG  SC_INDEX, MAP_MASK     ; blank has to erase all planes
            mov     di,BLANK_OFFSET
            mov     cx,BALL_WIDTH * BALL_HEIGHT
            sub     al,al
            rep stosb
    ;
    ; Set VGA to write mode 1, for block copying ball and blank images.
    ;
            mov     dx,GC_INDEX
            mov     al,GC_MODE
            out     dx,al           ;point GC Index to GC Mode register
            inc     dx              ;point to GC Data register
            jmp     $+2             ;delay to let bus settle
            in      al,dx           ;get current state of GC Mode
            and     al,not 3        ;clear the write mode bits
            or      al,1            ;set the write mode field to 1
            jmp     $+2             ;delay to let bus settle
            out     dx,al
    ;
    ; Set VGA offset register in words to define logical screen width.
    ;
            mov     al,LOGICAL_SCREEN_WIDTH / 2
            SETREG  CRTC_INDEX, CRTC_OFFSET
    ;
    ; Move the balls by erasing each ball, moving it, and
    ; redrawing it, then switching pages when they're all moved.
    ;
    BallAnimationLoop:
            mov     bx,( NUM_BALLS * 2 ) - 2
    EachBallLoop:
    ;
    ; Erase old image of ball in this page (at location from one more earlier).
    ;
            mov     si,BLANK_OFFSET ;point to blank image
            mov     cx,[LastBallX+bx]
            mov     dx,[LastBallY+bx]
            call    DrawBall
    ;
    ; Set new last ball location.
    ;
            mov     ax,[BallX+bx]
            mov     [LastballX+bx],ax
            mov     ax,[BallY+bx]
            mov     [LastballY+bx],ax
    ;
    ; Change the ball movement values if it's time to do so.
    ;
            dec     [BallRep+bx]           ;has current repeat factor run out?
            jnz     MoveBall
            mov     si,[BallControl+bx]    ;it's time to change movement values
            lodsw                          ;get new repeat factor from
                                           ; control string
            and     ax,ax                  ;at end of control string?
            jnz     SetNewMove
            mov     si,[BallControlString+bx]       ;reset control string
            lodsw                           ;get new repeat factor
    SetNewMove:
            mov     [BallRep+bx],ax         ;set new movement repeat factor
            lodsw                           ;set new x movement increment
            mov     [BallXInc+bx],ax
            lodsw                           ;set new y movement increment
            mov     [BallYInc+bx],ax
            mov     [BallControl+bx],si     ;save new control string pointer
    ;
    ; Move the ball.
    ;
    MoveBall:
            mov     ax,[BallXInc+bx]
            add     [BallX+bx],ax           ;move in x direction
            mov     ax,[BallYInc+bx]
            add     [BallY+bx],ax           ;move in y direction
    ;
    ; Draw ball at new location.
    ;
            mov     si,BALL_OFFSET  ;point to ball's image
            mov     cx,[BallX+bx]
            mov     dx,[BallY+bx]
            call    DrawBall
    ;
            dec     bx
            dec     bx
            jns     EachBallLoop

    ;
    ; Set up the next panning state (but don't program it into the
    ; VGA yet).
    ;
            call    AdjustPanning

    ;
    ; Wait for display enable (pixel data being displayed) so we know
    ; we're nowhere near vertical sync, where the start address gets
    ; latched and used.
    ;
            call    WaitDisplayEnable
    ;
    ; Flip to the new page by changing the start address.
    ;
            mov     ax,[CurrentPageOffset]
            add     ax,[PanningStartOffset]
            push    ax
            SETREG  CRTC_INDEX, START_ADDRESS_LOW
            mov     al,byte ptr [CurrentPageOffset+1]
            pop     ax
            mov     al,ah
            SETREG  CRTC_INDEX, START_ADDRESS_HIGH
    ;
    ; Wait for vertical sync so the new start address has a chance
    ; to take effect.
    ;
            call    WaitVSync
    ;
    ; Set horizontal panning now, just as new start address takes effect.
    ;
            mov     al,[HPan]
            mov     dx,INPUT_STATUS_1
            in      al,dx                   ;reset AC addressing to index reg
            mov     dx,AC_INDEX
            mov     al,HPELPAN
            out     dx,al                   ;set AC index to pel pan reg
            mov     al,[HPan]
            out     dx,al                   ;set new pel panning
    ;
    ; Flip the page to draw to to the undisplayed page.
    ;
            xor     [CurrentPage],1
            jnz     IsPage1
            mov     [CurrentPageOffset],PAGE0_OFFSET
            jmp     short EndFlipPage
    IsPage1:
            mov     [CurrentPageOffset],PAGE1_OFFSET
    EndFlipPage:
    ;
    ; Exit if a key's been hit.
    ;
            mov     ah,1
            int     16h
            jnz     Done
            jmp     BallAnimationLoop
    ;
    ; Finished, clear key, reset screen mode and exit.
    ;
    Done:
            mov     ah,0    ;clear key
            int     16h
    ;
            mov     ax,3    ;reset to text mode
            int     10h
    ;
            mov     ah,4ch  ;exit to DOS
            int     21h
    ;
    start   endp
    ;
    ; Routine to draw a ball-sized image to all planes, copying from
    ; offset SI in VGA memory to offset CX,DX (x,y) in VGA memory in
    ; the current page.
    ;
    DrawBall        proc    near
            mov     ax,LOGICAL_SCREEN_WIDTH
            mul     dx      ;offset of start of top image scan line
            add     ax,cx   ;offset of upper left of image
            add     ax,[CurrentPageOffset]  ;offset of start of page
            mov     di,ax
            mov     bp,BALL_HEIGHT
            push    ds
            push    es
            pop     ds      ;move from VGA memory to VGA memory
    DrawBallLoop:
            push    di
            mov     cx,BALL_WIDTH
            rep movsb       ;draw a scan line of image
            pop     di
            add     di,LOGICAL_SCREEN_WIDTH ;point to next destination scan line
            dec     bp
            jnz     DrawBallLoop
            pop     ds
            ret
    DrawBall        endp
    ;
    ; Wait for the leading edge of vertical sync pulse.
    ;
    WaitVSync       proc    near
            mov     dx,INPUT_STATUS_1
    WaitNotVSyncLoop:
            in      al,dx
            and     al,VSYNC_MASK
            jnz     WaitNotVSyncLoop
    WaitVSyncLoop:
            in      al,dx
            and     al,VSYNC_MASK
            jz      WaitVSyncLoop
            ret
    WaitVSync       endp

    ;
    ; Wait for display enable to happen (pixels to be scanned to
    ; the screen, indicating we're in the middle of displaying a frame).
    ;
    WaitDisplayEnable       proc    near
            mov     dx,INPUT_STATUS_1
    WaitDELoop:
            in      al,dx
            and     al,DE_MASK
            jnz     WaitDELoop
            ret
    WaitDisplayEnable       endp

    ;
    ; Perform horizontal/vertical panning.
    ;
    AdjustPanning   proc    near
            dec     [PanningRep]    ;time to get new panning values?
            jnz     DoPan
            mov     si,[PanningControl]     ;point to current location in
                                            ; panning control string
            lodsw                           ;get panning repeat factor
            and     ax,ax                   ;at end of panning control string?
            jnz     SetnewPanValues
            mov     si,offset PanningControlString  ;reset to start of string
            lodsw                           ;get panning repeat factor
    SetNewPanValues:
            mov     [PanningRep],ax         ;set new panning repeat value
            lodsw
            mov     [PanningXInc],ax        ;horizontal panning value
            lodsw
            mov     [PanningYInc],ax        ;vertical panning value
            mov     [PanningControl],si     ;save current location in panning
                                            ; control string
    ;
    ; Pan according to panning values.
    ;
    DoPan:
            mov     ax,[PanningXInc]        ;horizontal panning
            and     ax,ax
            js      PanLeft                 ;negative means pan left
            jz      CheckVerticalPan
            mov     al,[HPan]
            inc     al                      ;pan right; if pel pan reaches
            cmp     al,8                    ; 8, it's time to move to the
            jb      SetHPan                 ; next byte with a pel pan of 0
            sub     al,al                   ; and a start offset that's one
            inc     [PanningStartOffset]    ; higher
            jmp     short SetHPan
    PanLeft:
            mov     al,[HPan]
            dec     al                      ;pan left; if pel pan reaches -1,
            jns     SetHPan                 ; it's time to move to the next
            mov     al,7                    ; byte with a pel pan of 7 and a
            dec     [PanningStartOffset]    ; start offset that's one lower
    SetHPan:
            mov     [HPan],al               ;save new pel pan value
    CheckVerticalPan:
            mov     ax,[PanningYInc]        ;vertical panning
            and     ax,ax
            js      PanUp                   ;negative means pan up
            jz      EndPan
            add     [PanningStartOffset],LOGICAL_SCREEN_WIDTH
                                            ;pan down by advancing the start
                                            ; address by a scan line
            jmp     short EndPan
    PanUp:
            sub     [PanningStartOffset],LOGICAL_SCREEN_WIDTH
                                            ;pan up by retarding the start
                                            ; address by a scan line
    EndPan:
            ret
    ;
    ; Draw textured border around playfield that starts at DI.
    ;
    DrawBorder      proc    near
    ;
    ; Draw the left border.
    ;
            push    di
            mov     cx,LOGICAL_SCREEN_HEIGHT / 16
    DrawLeftBorderLoop:
            mov     al,0ch          ;select red color for block
            call    DrawBorderBlock
            add     di,LOGICAL_SCREEN_WIDTH * 8
            mov     al,0eh          ;select yellow color for block
            call    DrawBorderBlock
            add     di,LOGICAL_SCREEN_WIDTH * 8
            loop    DrawLeftBorderLoop
            pop     di
    ;
    ; Draw the right border.
    ;
            push    di
            add     di,LOGICAL_SCREEN_WIDTH - 1
            mov     cx,LOGICAL_SCREEN_HEIGHT / 16
    DrawRightBorderLoop:
            mov     al,0eh          ;select yellow color for block
            call    DrawBorderBlock
            add     di,LOGICAL_SCREEN_WIDTH * 8
            mov     al,0ch          ;select red color for block
            call    DrawBorderBlock
            add     di,LOGICAL_SCREEN_WIDTH * 8
            loop    DrawRightBorderLoop
            pop     di
    ;
    ; Draw the top border.
    ;
            push    di
            mov     cx,(LOGICAL_SCREEN_WIDTH - 2) / 2
    DrawTopBorderLoop:
            inc     di
            mov     al,0eh          ;select yellow color for block
            call    DrawBorderBlock
            inc     di
            mov     al,0ch          ;select red color for block
            call    DrawBorderBlock
            loop    DrawTopBorderLoop
            pop     di
    ;
    ; Draw the bottom border.
    ;
            add     di,(LOGICAL_SCREEN_HEIGHT - 8) * LOGICAL_SCREEN_WIDTH
            mov     cx,(LOGICAL_SCREEN_WIDTH - 2) / 2
    DrawBottomBorderLoop:
            inc     di
            mov     al,0ch          ;select red color for block
            call    DrawBorderBlock
            inc     di
            mov     al,0eh          ;select yellow color for block
            call    DrawBorderBlock
            loop    DrawBottomBorderLoop
            ret
    DrawBorder      endp
    ;
    ; Draws an 8x8 border block in color in AL at location DI.
    ; DI preserved.
    ;
    DrawBorderBlock proc    near
            push    di
            SETREG  SC_INDEX, MAP_MASK
            mov     al,0ffh
            rept 8
            stosb
            add     di,LOGICAL_SCREEN_WIDTH - 1
            endm
            pop     di
            ret
    DrawBorderBlock endp
    AdjustPanning   endp
    cseg    ends
            end     start

Previous | Table of Contents | Next

Graphics Programming Black Book © 2001 Michael Abrash