I'm working on a game for DOS which uses video mode 13h.
I've always had issues with screen tearing, but until today I've been ignoring the problem. I assumed it was going to be a challenge to fix since it would involve delaying pixel writes for some precise amount of time. But it was actually a really simple fix.
All you have to do is wait for the vertical retrace bit (bit 3) of the VGA status byte, available at port 0x3da in color mode, to be newly set.
So I just had to modify this old procedure, which writes my frame buffer to the VGA pixel buffer starting at A000:0000:
WRITE_FRAME PROC
;WRITES ALL 64,000 PIXELS (32,000 WORDS) IN THE FRAME BUFFER TO VIDEO MEMORY
push es
push di
push ds
push si
push cx
mov cx, frame
mov ds, cx
xor si, si ;ds:si -> frame buffer (source)
mov cx, vidMemSeg
mov es, cx
xor di, di ;es:di -> video memory (destination)
mov cx, (scrArea)/2 ;writing 32,000 words of pixels
rep movsw ;write the frame
pop cx
pop si
pop ds
pop di
pop es
ret
WRITE_FRAME ENDP
And here's the modified procedure that waits for the vertical retrace bit to be newly set:
WRITE_FRAME PROC
;WRITES ALL 64,000 PIXELS (32,000 WORDS) IN THE FRAME BUFFER TO VIDEO MEMORY
push es
push di
push ds
push si
push ax
push cx
push dx
mov cx, frame
mov ds, cx
xor si, si ;ds:si -> frame buffer (source)
mov cx, vidMemSeg
mov es, cx
xor di, di ;es:di -> video memory (destination)
mov cx, (scrArea)/2 ;writing 32,000 words of pixels
;If vert. retrace bit is set, wait for it to clear
mov dx, 3dah ;dx <- VGA status register
VRET_SET:
in al, dx ;al <- status byte
and al, 8 ;is bit 3 (vertical retrace bit) set
jnz VRET_SET ;If so, wait for it to clear
VRET_CLR: ;When it's cleared, wait for it to be set
in al, dx
and al, 8
jz VRET_CLR ;loop back till vert. retrace bit is newly set
rep movsw ;write the frame
pop dx
pop cx
pop ax
pop si
pop ds
pop di
pop es
ret
WRITE_FRAME ENDP
It's not completely perfect. There's still a little jitter, especially when the background behind the sprite is scrolling up or down, but it doesn't hurt to look at anymore.
My question is, why does this work?
My guess is that when the vertical retrace bit is set, the pixels have already been read into the VGA card's memory, and it is currently in the process of writing it's already loaded pixels. However, when the vertical retrace bit is cleared, it is in the process of loading the pixels from A000:0000 into local memory. It uses DMA for this, right?
So, it's only safe to write to A000:0000 when the VGA card is writing pixels (bit set), and not loading pixels in (bit cleared)
Or am I totally wrong?