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!