Skip to content

VBlank Game Loop

Frame-synchronised game loop that waits for vertical blank using the VPOSR register. 50Hz timing on PAL systems.

Taught in Game 1, Unit 2 game-loopvblanktimingcustom-chips

Overview

Synchronise your game to the display refresh by polling the vertical position register. When VPOS reaches zero, you’re at the start of a new frame. This provides consistent 50Hz timing on PAL Amigas (60Hz on NTSC) without using interrupts.

Code

; =============================================================================
; VBLANK GAME LOOP - AMIGA
; Poll-based vertical blank synchronisation
; Taught: Game 1 (Signal), Unit 2
; CPU: Variable | Memory: ~40 bytes
; =============================================================================

CUSTOM      equ $dff000
VPOSR       equ $004            ; Vertical position register (offset)

            section code,code_c

start:
            lea     CUSTOM,a5           ; Custom chip base in A5

            ; --- System takeover ---
            move.w  #$7fff,INTENA(a5)   ; Disable all interrupts
            move.w  #$7fff,INTREQ(a5)   ; Clear pending interrupts
            move.w  #$7fff,DMACON(a5)   ; Disable all DMA

            ; --- Your setup code here ---
            bsr     init_game

            ; --- Install copper list ---
            lea     copperlist,a0
            move.l  a0,COP1LC(a5)
            move.w  d0,COPJMP1(a5)

            ; --- Enable DMA ---
            move.w  #$83a0,DMACON(a5)   ; Master + copper + sprites + bitplanes

; === MAIN LOOP ===
mainloop:
            bsr.s   wait_vblank
            bsr     read_input
            bsr     update_game
            bsr     update_graphics
            bra.s   mainloop

; Wait for vertical blank (VPOS = 0)
wait_vblank:
            move.l  #$1ff00,d1          ; Mask for vertical position
.wait:
            move.l  VPOSR(a5),d0        ; Read vertical position
            and.l   d1,d0               ; Isolate VPOS bits
            bne.s   .wait               ; Loop until VPOS = 0
            rts

; Placeholder routines
init_game:
            rts
read_input:
            rts
update_game:
            rts
update_graphics:
            rts

Complete example with exit handling:

mainloop:
            bsr.s   wait_vblank
            bsr     read_joystick
            bsr     move_player
            bsr     update_sprite

            ; Check left mouse button for exit
            btst    #6,$bfe001
            bne.s   mainloop            ; Loop if not pressed

            ; Exit - restore system and return to CLI
            bra     cleanup

Trade-offs

AspectCost
CPUBusy-waits during VBlank wait
Memory~20 bytes for wait routine
LimitationWastes CPU cycles while waiting

When to use: Simple games, demos, when you don’t need background processing.

When to avoid: Complex games that need to use wait time productively. Use interrupts instead.

How It Works

  1. The VPOSR register contains the current vertical beam position
  2. Bits 8-16 hold the vertical line number (0-311 on PAL)
  3. When VPOS = 0, the beam is at the top of the screen (VBlank)
  4. Poll until VPOS reaches 0, then run your frame logic

Frame Budget

On a PAL Amiga at 50Hz:

  • 20ms per frame
  • At 7.09MHz (68000), approximately 140,000 cycles per frame
  • VBlank itself is ~25 scanlines (~1.6ms)

If your game logic exceeds the frame budget, it will skip frames and slow down.

System Takeover Pattern

Always disable system interrupts and DMA before taking over:

            move.w  #$7fff,INTENA(a5)   ; Disable interrupts
            move.w  #$7fff,INTREQ(a5)   ; Clear pending
            move.w  #$7fff,DMACON(a5)   ; Disable DMA

Writing $7fff with bit 15 clear disables those bits.

Then enable only what you need:

            move.w  #$83a0,DMACON(a5)   ; Enable master + copper + sprites + bitplanes

Writing with bit 15 set enables those bits.

Patterns: Joystick Reading, Copper List Basics

Vault: Commodore Amiga