Non-sequential VRAM updates

The VDP is best suited for accessing VRAM sequentially. Typical uses for this are loading tile sets and updating the name table. However there can be times when you need to access VRAM in a more random fashion. At first it would seem the only way to do this is by changing the VRAM write address for every new byte written, but that causes a lot of overhead.

If the data to be changed is within a 256-byte boundary, you only have to update the low byte of the address register after the full address has been set once. Because writing to the data port resets the 'command-pending' flag for the control port, you can repeatedly change the low address byte and write data in a loop.

Here's an example that resets the X position of 8 sprites at random offsets:

  ld bc, $08BF
  ld hl, $7F80
  out (c), l
  out (c), h       ; Set VRAM address to $3F80 (sprite table)
  ld hl, table     ; Table of sprite offets to modify
 loop:
  ld a, (hl)       ; Get offset
  inc l
  out (c), a       ; Set new address at $3Fxx
  dec c
  out (c), 0       ; Reset X position
  out (c), 0       ; Reset Y position
  inc c
  djnz loop

 table: .db $80, $DE, $88, $C6, $CE, $D4, $B2, $AC

NOTE: This technique does not work on the Sega MegaDrive / Sega Genesis in Mark-III compatibility mode.

Another situation is when some VRAM data needs be updated between other data that you don't want to modify. This would include updating single bitplanes out of a group of tiles, or just updating the high or low byte of several name table entries.

To skip VRAM bytes, you can read from the data port to automatically increase the address register. The data read should be discarded – a faster way that spares a register from being changed is to use the undocumented in f, (c) instruction, which only updates the Z80 processor flags.

Here's an example to set bitplane 3 of four tiles to $C3:

  ld bc, $20BF
  ld hl, $4020 ; Set VRAM address to $0020
  out (c), l
  out (c), h
  dec c
  ld a, $C3    ; New bitplane #3 value
 loop:
  in f, (c)    ; Skip bitplane #0
  in f, (c)    ; Skip bitplane #1
  in f, (c)    ; Skip bitplane #2
  out (c), a   ; Update bitplane #3
  djnz loop

Fast VRAM updates

The fastest way to update VRAM is by using the 'out (c), x' instructions. You can abuse the macro features of your assembler to repeatedly use these instructions in large sequences.

For example, let's say we want write a pattern of alternating 4x4 tiles to the entire name table. One group will be a low priority tile using pattern $68, the other will be high priority using pattern $69. The code would be:

 ld c, $BE
 ld de, $0010
 ld hl, $6869

 .rept 3
   .rept 4
     .rept 4
         .rept 4
         out (c), d
         out (c), h
         .endr
         .rept 4
         out (c), e
         out (c), l
         .endr
     .endr    
   .endr
   .rept 4
     .rept 4
         .rept 4
         out (c), e
         out (c), l
         .endr
         .rept 4
         out (c), d
         out (c), h
         .endr
     .endr
   .endr    
 .endr

This technique has limited application due to the small number of values you can output (using DE, HL, BC, A, 0) but can be useful in some situations.

Large VRAM updates

VRAM is usually updated during VBlank, where the screen is turned off and the Z80 has unrestricted access. For an NTSC SMS you get about 70 lines worth of VBlank time, but that may not always be enough.

You can get more time to access VRAM by turning the screen off outside of VBlank. To keep the display from looking too ugly it's desirable to do this immediately before and after VBlank, so the display appears letterboxed. For example, you could add 16 lines of VBlank time by turning off the screen for lines 0-7 and 183-191, cutting out the first and last rows.

From a programming standpoint, it's easier to disable the frame interrupt and manage the 'extended' VBlank using line interrupts.

This technique is very useful on the Game Gear. Despite having only 144 scanlines per frame, the VDP still internally manages a 262-line display with 192 lines used for the display portion, so VRAM cannot be accessed during that time. This means 48 scanlines of potential transfer time are lost.

To fix this, you'd trigger a line interrupt at scanline 24 (first line of the LCD display) to turn the screen on, and another at line 168 to turn the screen off. The rest of the display can be used for typical VBlank tasks and VRAM updates, giving 118 lines total.

See also




Return to top
0.173s