Using the CGA/EGA/VGA planar graphics modes

I have trouble to grasp how to use colors in CGA/EGA/VGA video graphics modes. The video modes I'm particularly interested in are 0Dh (EGA 320x200) and 12h (VGA 640x480). Both of these modes have 4 planes, thus 16 colors.

My (probably incorrect) understanding is that I should activate a set of planes by writing a bitmask to port 03C4h, then when I write to video memory, the data only gets written to the activated planes. Mostly I used this document to get my information, though I also encountered several other tutorials and discussions: http://www.techhelpmanual.com/89-video_memory_layouts.html

Now I'm trying to write pixels in all possible colors in the first word in the video memory (top left part of screen). I load 1 for the initial bitmask to AH and 1 bit to BX. Then in a loop, I increment AH and shift (SHL) the bit in BX to hit a different pixel next time. I OR BX to A000h:0000h to add each pixels by leaving the already existing pixels untouched.

What I'm expected to see is a line of pixels in all possible 16 EGA colors on the top left of the screen. What I actually see is 7 white and 1 bright yellow dots with black pixels in between them. What am I doing wrong?

Also, every tutorial says that I must write 0005h to port 03CEh before I start to use planes. What is the purpose of that? When I comment those lines out, I can still use planes (I mean, in other programs). Previously I had success using planes when I was writing to different words in video memory (so I didn't need different color pixels in one block of 16 pixels that's represented by a single word in video memory); and when I used BIOS functions (e.g. INT 10h/AH=0Ch) to write pixels, but still I want to understand how to use planar graphics without BIOS, as I believe the BIOS functions are slow.

Here is my code (indentation is optimized for 8-width tabs, so it kind of looks off here):

; ----------------------------------------------------------------------
; PLANE - TEST PLANAR VIDEO MODE
; nasm -o plane.com plane.asm
; ----------------------------------------------------------------------

        ORG 100h            ; Code starts at CS:0100h

        MOV AX, 0F00h       ; Get current video mode
        INT 10h
        PUSH    AX          ; Save it to restore later

        MOV AX, 000Dh       ; Set video mode 0Dh (EGA 320x200, 16 color)
        INT 10h

        MOV DX, 03CEh       ; Set up for plane masking
        MOV AX, 0005h
        OUT DX, AX

        MOV AX, 0A000h      ; Load video segment to ES
        MOV ES, AX          ; and set Destination Index
        XOR DI, DI          ; to write data to A000h:0000h

        MOV CX, 14          ; Iterate through all plane masks
        MOV AX, 0100h       ; First plane mask is 01h
        MOV BX, 1           ; Initial pixel to write
LOOP_PIXEL:
        CALL    SET_PLANES      ; Set planes according to AH
        PUSH    BX          ; Save current pixels to [DATA+CX*2]
        MOV BX, CX          ; for debugging purposes
        SHL BX, 1
        MOV DX, ES:[DI]
        MOV [DATA + BX], DX
        POP BX
        OR  ES:[DI], BX     ; Add new pixel to video memory from BX
        SHL BX, 1           ; Shift BX so we'll activate a different pixel next time
        INC AH          ; Increment plane mask
        LOOP    LOOP_PIXEL

        XOR AX, AX          ; Wait for keypress
        INT 16h

        POP AX          ; Restore previous video mode
        XOR AH, AH
        INT 10h

        INT 20h         ; Exit program


; ----------------------------------------------------------------------
; SET_PLANES: Set video color plane mask.
;
; Inputs:
;   AH  - plane mask
;
; Outputs:
;   None.
; ----------------------------------------------------------------------
SET_PLANES:
        PUSH    AX
        PUSH    DX
        MOV DX, 03C4h
        MOV AL, 02h
        OUT DX, AX
        POP DX
        POP AX
        RET

DATA:

Any ideas why it doesn't work as I expect?


Solution 1:

Writing the word 0005h to ports 03CEh and 03CFh will select write mode 0. This is a complex mode that involves many features of the VGA but luckily for us most of these are reset when the video mode is set.
However your code still needs to do the following:

  • In order to fill the VGA's internal 32-bit latch, you must perform a read-before-write operation
  • Restricting output to a single or a few pixels is done using the BitMask register.

Next snippet displays a rainbow of 16 vertical lines that are 1 pixel wide:

  xor   di, di
  mov   ax, 0F02h            ; First plane mask is 15
  mov   cx, 8008h            ; First bitmask is 10000000b, Width=1
LOOP1:
  mov   dx, 03C4h
  out   dx, ax               ; MapMask register
  xchg  ax, cx
  mov   dx, 03CEh
  out   dx, ax               ; BitMask register
  xchg  ax, cx

  xor   bx, bx
.a:
  mov   dl, [es:di+bx]
  mov   byte [es:di+bx], 255
  add   bx, 40
  cmp   bx, 1600              ; Height = 40
  jb    .a

  ror   ch, 1                 ; Width = 1
  adc   di, 0
  dec   ah
  jns   LOOP1

And this snippet displays a rainbow of 16 vertical lines that are 2 pixels wide:

  mov   di, 13
  mov   ax, 0F02h            ; First plane mask is 15
  mov   cx, 0C008h           ; First bitmask is 11000000b, Width=2
LOOP2:
  mov   dx, 03C4h
  out   dx, ax               ; MapMask register
  xchg  ax, cx
  mov   dx, 03CEh
  out   dx, ax               ; BitMask register
  xchg  ax, cx

  xor   bx, bx
.a:
  mov   dl, [es:di+bx]
  mov   byte [es:di+bx], 255
  add   bx, 40
  cmp   bx, 800               ; Height = 20
  jb    .a

  ror   ch, 2                 ; Width = 2
  adc   di, 0
  dec   ah
  jns   LOOP2

And a third snippet displays a rainbow of 16 vertical lines that are 4 pixels wide:

  mov   di, 26
  mov   ax, 0F02h            ; First plane mask is 15
  mov   cx, 0F008h           ; First bitmask is 11110000b, Width=4
LOOP3:
  mov   dx, 03C4h
  out   dx, ax               ; MapMask register
  xchg  ax, cx
  mov   dx, 03CEh
  out   dx, ax               ; BitMask register
  xchg  ax, cx

  xor   bx, bx
.a:
  mov   dl, [es:di+bx]
  mov   byte [es:di+bx], 255
  add   bx, 40
  cmp   bx, 400               ; Height = 10
  jb    .a

  ror   ch, 4                 ; Width = 4
  adc   di, 0
  dec   ah
  jns   LOOP3

Precautions were taken so you can try all snippets together in the same program!