A television draws its picture one line at a time, top to bottom, and then spends a short while getting ready to draw the next one. That pause, the vertical blank, is the only time in each frame when the video hardware is not drawing anything. A game that waits for it, and does its screen changes then, never shows a half-finished picture, and it moves everything in steady steps of one frame: fifty a second in PAL countries, sixty in NTSC ones. Waiting for the vertical blank is what programmers mean by vsync.
The catch is that the gap is short. The Atari 2600’s programming guide allowed 70 lines out of 262 for everything that was not picture; a PAL Commodore 64 draws 284 visible lines out of 312; an NTSC NES has 20 lines of vertical blank, about 2,273 processor cycles. Much of the craft of 8-bit and 16-bit games was fitting the right work into that gap.
What goes wrong without it
The video chip reads its picture from memory as it draws. If the program changes that memory, or a scroll or colour register, while the chip is partway down the screen, the lines already drawn show the old frame and the lines still to come show the new one. A moving object drawn across the boundary appears torn, or flickers as it is caught half erased; a scroll change made mid-screen shears the picture.
Not every change has to wait. A change to a part of the screen the beam has already passed, or has not reached yet, is safe for the moment, so a program that knows where the beam is can work on one part of the screen while the chip draws another. Some hardware leaves no choice: the NES’s picture memory can be written safely only during vertical blank or with the picture switched off.
The Atari 2600: the program makes the frame
The 2600 has no frame buffer and no frame interrupt. Its program generates the television signal’s vertical timing itself. Atari’s Stella Programmer’s Guide spelled out the recipe: “3 vertical sync (VSYNC) lines to signal the TV set to start a new frame, 37 vertical blank (VBLANK) lines, 192 TV picture lines, and 30 overscan lines”, 262 in all. A PAL machine needs 312: the guide’s table gives 48 lines of vertical blank (including the three of sync), 228 of picture and 36 of overscan.
The 70 lines outside the picture are all the game gets for its logic. At 76 processor cycles a line, the guide counted “5320 machine cycles (70 lines X 76 machine cycles) for houskeeping and game logic”. During the picture, the program is busy feeding the chip each line as the beam draws it.
; one NTSC frame on the Atari 2600
frame:
; --- vertical sync, 3 lines ---
lda #2
sta VBLANK ; D1 set: blank the picture
sta VSYNC ; D1 set: start vertical sync
sta WSYNC ; halt the CPU until the end of the line
sta WSYNC
sta WSYNC ; three lines of sync
lda #0
sta VSYNC ; end vertical sync
; --- vertical blank, 37 lines ---
lda #43
sta TIM64T ; 43 x 64 = 2,752 cycles, just over 36 lines
; ... game logic here ...
vbl_wait:
lda INTIM
bne vbl_wait ; wait for the timer to run out
sta WSYNC ; finish the last blank line
lda #0
sta VBLANK ; picture on
; --- 192 picture lines: the kernel feeds the chip line by line ---
; --- 30 lines of overscan: more game logic, then jmp frame ---The timer lets the game logic take a variable amount of time and still keep the frame exactly 262 lines long. A PAL version changes the line counts, not the method.
Atari 400 and 800: the vertical blank interrupt
Atari’s home computers of 1979 announced the vertical blank with an interrupt, and the operating system ran its own housekeeping there. John Palevich, explaining his game SHOOT in COMPUTE! in September 1981, described what game writers did with it: “The VBI occurs every sixtieth of a second and is used by the Atari to keep the realtime clock, do attract mode, update all sorts of counters, and so forth. The video game maker uses this time to move the players, update the scores, fire the shots, decrement the time left, and do sound effects.” A game on those machines, as he laid it out, had “the setup, the main loop, and the end”, plus “two other parts”: the vertical blank interrupt and the display list interrupt (see Atari 8-bit).
Commodore 64
The VIC-II has no vertical blank interrupt as such; it has a raster interrupt that can be set for any line, and games choose a line outside the 200-line display window, which runs from line 51 to line 250. The television’s own blanking comes later. Christian Bauer’s description of the chip gives the blanked lines as 300 through to line 15 of the next frame on a PAL 6569, and lines 13 to 40 on an NTSC 6567. The lower border between them is still visible, but nothing the game draws appears there, so for a game the useful gap starts as soon as the beam leaves the display window.
A program can poll for it:
; wait until the raster reaches line 251, just below the display window
wait_frame:
lda #251
wait_line:
cmp $d012
bne wait_lineThe low byte 251 can only mean line 251 on both PAL and NTSC machines, so the ninth bit in $D011 need not be checked. If the code after the wait could finish within that same line, the next call would return at once, so a program that might be that quick waits for the line to change first (see The Game Loop).
Most games let the chip interrupt them instead. A handler installed through the KERNAL’s vector at $0314 acknowledges the interrupt by writing 1 to bit 0 of $D019 and leaves with JMP $EA31 or JMP $EA81, never with its own RTI, because the KERNAL has already saved the registers on the stack. Raster Interrupts gives complete, tested listings for both the KERNAL route and the hardware vector at $FFFE, and Interrupt-Driven Music a once-a-frame handler that plays a tune.
NES
The NES’s PPU raises a non-maskable interrupt at the start of vertical blank when bit 7 of PPUCTRL ($2000) is set. Games do the screen updates in the NMI handler, from buffers the main loop filled, and the main loop waits for the NMI before starting the next frame’s work:
nmi_count = $00 ; a byte of zero-page RAM
nmi: pha
; ... sprite DMA, palette, nametable and scroll writes ...
inc nmi_count ; tell the main loop a frame has passed
pla
rti
; main loop: game logic, then wait for the next NMI
main: jsr update_game
lda nmi_count
wait_nmi:
cmp nmi_count
beq wait_nmi
jmp mainThe counter must live in RAM, not in the cartridge’s ROM, or the handler’s INC does nothing. A handler that uses X or Y as well saves and restores them too.
The window is tight. On an NTSC NES vertical blank lasts 20 lines, about 2,273 CPU cycles, and the copy of the sprite table by DMA alone takes 513 or 514 of them; on a PAL machine it lasts 70 lines, about 7,459 cycles. The Game Loop entry shows how Metroid divides the work.
Amiga
The Amiga’s custom chips raise a level 3 interrupt, VERTB, at the start of every frame; the Hardware Reference Manual places it “at line 0 (start of vertical blank)”. The manual gives the minimum vertical blank as “20 horizontal scan lines for an NTSC system and 25 horizontal scan lines for a PAL system”, but a normal screen starts later, at line 44 ($2C in the display window start register DIWSTRT), so a game has more lines than that before the picture begins. The system’s own jobs in vertical blank are, in the manual’s words, “updating of various pointer registers, rewriting lists of Copper tasks when necessary, and other system-control operations”.
A game that has taken over the machine points the level 3 vector at its own handler and clears the request itself:
move.l #vblank,$6c.w ; level 3 interrupt vector
move.w #$7fff,$dff09a ; INTENA: switch every interrupt off
move.w #$c020,$dff09a ; INTENA: SET + INTEN + VERTB
main: move.w frame_count,d0
wait: cmp.w frame_count,d0 ; wait for the next vertical blank
beq.s wait
; ... one frame of game logic ...
bra.s main
vblank: move.w #$0020,$dff09c ; INTREQ: clear the VERTB request
addq.w #1,frame_count ; tell the main loop a frame has passed
rte
frame_count:
dc.w 0Bit 15 of INTENA decides whether the bits written as 1 are set or cleared, so $C020 switches the interrupt on and $4020 would switch it off. The Amiga can also start its frame work when the beam reaches the bottom of the picture instead, by having the Copper raise a level 3 COPER interrupt at a chosen line; the Game Loop entry shows both.
Frame rate and slowdown
Waiting for the vertical blank locks a game to the television: every movement is so many pixels per frame, so the same game runs a fifth faster on an NTSC machine than on a PAL one unless its programmers adjusted it. It also sets a hard budget. A frame whose work does not fit before the next vertical blank misses it, and the game waits a whole extra frame, so everything slows down.
Most 8-bit games accepted the slowdown. Some ran their logic less often than the screen. Lasse Öörni, who wrote C64 games as Cadaver, noted that “some games like Gauntlet 3 or Myth scroll at 50Hz, while updating the movement of sprites at a lower rate”, and recalled seeing Last Ninja 2’s characters “move with bigger steps in some CPU-intense scenes” (see Frameskipping and Interpolation). To draw into memory the display is not showing and swap the two at the vertical blank, see Double Buffering.
Sources
- Atari, Stella Programmer’s Guide (1979): the NTSC frame of 3, 37, 192 and 30 lines; 5,320 cycles outside the picture;
VSYNC,VBLANKandWSYNC;TIM64T; the PAL line counts. - John H. Palevich, “SHOOT”, COMPUTE! issue 16, September 1981, pp. 86–89: the Atari vertical blank and display list interrupts in a game.
- Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64 (1996), section 3.4: lines, visible lines and vertical blanking for each VIC-II type.
- Commodore, Commodore 64 Programmer’s Reference Guide (1982), pp. 150–152: the raster register and interrupt registers.
- NESdev Wiki, “Cycle reference chart” and “PPU registers” (web, secondary): vertical blank length, PPUCTRL bit 7, OAM DMA. https://www.nesdev.org/wiki/Cycle_reference_chart
- Amiga Hardware Reference Manual (Commodore-Amiga, Addison-Wesley, 1989): VERTB and the minimum vertical blank (chapter 7); the normal
DIWSTRTof$2C81(chapter 3);INTENAandINTREQand the SET/CLR bit (Appendix A). - Jorgo Schimanski, Spieleprogrammierung in Assembler auf dem Amiga (Heim-Verlag, 1991), chapter 2, pp. 8–13: the level 3 vector, enabling VERTB with
$C020and clearing it with$0020. - Lasse Öörni (Cadaver), “Frameskipping, interpolation and re-entrant IRQ code” (web, secondary). https://cadaver.github.io/rants/interp.html
The Atari 2600 and C64 listings were assembled with ACME; the 2600 frame was run in the Emu198x emulator and the C64 wait in VICE (x64sc) as PAL and NTSC machines. The NES listing was assembled with ca65 and the Amiga listing with vasm, and both were run in Emu198x.