Stable frame timing for Albert
Posted: Mon Nov 04, 2019 3:24 pm
I was irritated, very much like an oyster with a bit of sand in its shell, by the fact that I didn't have a stable vsync to lock my game to. In practice it didn't really matter as the game feels fine for the most part, but I can tell - it does feel like there's a little bit of grit in the gears from time to time.
So just as an oyster makes a pearl by depositing a layer of mother-of-pearl around the aforementioned grain of sand, so I .. err, wrote some code.
It's real simple but effective. Set up a timer interrupt to fire at just under the frame rate, which is almost 20 milliseconds, or 50 times per second. Inside the timer interrupt busy-wait for the VDP's vsync bit to get set. Reset the timer. Repeat ad nauseum.
It might be possible to find a timer combination to exactly match the vsync, in which case we wouldn't need to reset the timer, but in practice I suspect there'd be a slight drift which could have consequences. So resetting the timer it is!
Anyway - here it is. Have fun!
So just as an oyster makes a pearl by depositing a layer of mother-of-pearl around the aforementioned grain of sand, so I .. err, wrote some code.
It's real simple but effective. Set up a timer interrupt to fire at just under the frame rate, which is almost 20 milliseconds, or 50 times per second. Inside the timer interrupt busy-wait for the VDP's vsync bit to get set. Reset the timer. Repeat ad nauseum.
It might be possible to find a timer combination to exactly match the vsync, in which case we wouldn't need to reset the timer, but in practice I suspect there'd be a slight drift which could have consequences. So resetting the timer it is!
Anyway - here it is. Have fun!
Code: Select all
; tight vsync demo
;
; assemble with BRASS - http://www.benryves.com/bin/brass/
;
; tabs = 4
VDP_DATA .equ $08 ; read/write data
VDP_REG .equ $09 ; write/address
VDP_STAT .equ $09 ; read
CTC_TMR2 .equ $2a
CTC_TMR3 .equ $2b
COL_BLACK .equ $01
COL_DBLUE .equ $04
; time constants to give us a 19.9 millisecond timer period
TC2 .equ 92
TC3 .equ 54
.org $100
ld sp,$7fff
di
ld hl,_irqhandler ; install own handler over the system's timer3 vector
ld ($fb06),hl
ld a,$1f ; disable interrupt + timer mode + prescaler 16 + rising edge + clk starts + time constant follows + reset + control
out (CTC_TMR2),a ; ctc channel 2 write timer config
ld a,TC2
out (CTC_TMR2),a ; write time constant
ld a,$df ; enable interrupt + counter mode + (n/a) + rising edge + clk starts + time constant follows + reset + control
out (CTC_TMR3),a ; ctc channel 3 write timer config
ld a,TC3
out (CTC_TMR3),a ; write time constant
ei
; awaaay we go.
; result should be thin blue line. the line starts at the time the
; timer irq fires, and ends when vsync bit is set in VDP.
; the aim is to tighten the timer such that absolute minimum of time
; is wasted waiting for the vblank.
jr $
_irqhandler:
di
exx ; doesn't save AF. ask me how i know.
push af
ld a,COL_DBLUE ; set border blue
out (VDP_REG),a
ld a,$87
out (VDP_REG),a
-: in a,(VDP_STAT) ; wait for VDP VSYNC bit
rla
jr nc,{-}
ld a,COL_BLACK ; set order black
out (VDP_REG),a
ld a,$87
out (VDP_REG),a
; if we had a stable timer config that didn't drift then
; we wouldn't need to reset the timer. but life's too short
; to wast finding one :D so...
;
; reset timer to fire in 19.9 milliseconds time
; (92*16)*54 = 79488 / 4000000 = 0.0199 sec
ld a,$1f
out (CTC_TMR2),a
ld a,TC2
out (CTC_TMR2),a
ld a,$df
out (CTC_TMR3),a
ld a,TC3
out (CTC_TMR3),a
pop af
exx
ei
reti