loading...
All Genesis articles | Back to top

SEGA Genesis: Scrolling

The SEGA Genesis has dedicated scrolling hardware, with different scroll modes. In this post, I'll show how to do vertically synchronized scrolling on the Genesis.

Line scroll enables awesome parallax effects
Full screen scrolling in Mega Turrican

Scroll Modes

In general, scrolling is the result of changing an offset of some displayed graphics.

The Genesis VDP has support for both vertical and horizontal scrolling, and they seem to be handled in different ways, but here I will only focus on horizontal scrolling. Horizontal scrolling can be performed in 3 different modes on the Genesis:

The full screen scroll is simple enough, every pixel is offset horizontally with the same amount (see the Mega Turrican GIF for an example). It only requires updating one number every frame, the offset.

The line scroll is more powerful, as every line on the screen can be offset with a different value. This allows for some crazy pseudo-3D effects, like the parallax scrolling in Thunder Force IV.

The cell scroll is similar to the line scroll, but instead of every line being offset with a potentially different value, the offsets are 8 lines apart, requiring less data to be updated every frame.


Full Screen Scrolling

Here follows an example of scrolling in full screen mode, incrementing a single horizontal scroll offset every frame. To do this, we:

The two first items require setting up VDP registers.


Scroll Mode

The full screen scroll mode is located in VDP register $0B. It has a few bits that I'll ignore for this example, and just set the two horizontal scroll bits. They can have these binary values:

 * `00`: Full screen scroll
 * `11`: Line scroll
 * `10`: Cell scroll

For this example, we want to enable 'Full screen scroll' and set all other bits to 0, which just yields a bit fat 0:

    0000 0000

So, we end up with setting the VDP register $0B to 0. As explained earlier, VDP registers are set using this bit pattern:

    Bits    [10?R RRRR DDDD DDDD]
    R order [...4 3210 .... ....] register
    D order [.... .... 7654 3210] data
    -------------------------------------------
    VDP register $0B (0 1011) = $0 (0000 0000)
    Bits: [10.. .... .... ....]
          [10.0 1011 .... ....] register $0B
          [10.0 1011 0000 0000] data
          [1000 1011 0000 0000] add zeroes
    Hex:      8    B    0    0

To set the horizontal scroll mode to full screen scroll, we end up with this line:

    move.w #$8B00,vdp_control

Next, let's set the scroll offset.

Scroll Offset Address

Biomechanic monstrocity from Wings of Wor

The horizontal scroll offset is just a single number in our example, but could be a table with entries for each line, if we were using line scroll mode. We tell the VDP of where our number is located by writing the address to VDP register $0D.

I arbitrarily picked the VRAM address $D000, so we want to 'set VDP register $0D = $D000'. Scroll tables addresses must be a multiple of $400, and is specified using only the 6 most significant bits:

    Bits: [?MAA AAAA]
    - ? is ignored (set to 0)
    - M enables 128 KB mode (set to 0)
    - A is bits 15-10 of the address

We start out with this:

    [.0.. ....]

And our address $D000, which in binary is 1101000000000000, is specified using the 6 most significant bits: 110100. Like this:

    [.011 0100]

Add a zero, and now we have our data byte:

    [0011 0100]
        3    4

We can now make the full command word:

    Bits    [10?R RRRR DDDD DDDD]
    R order [...4 3210 .... ....] register
    D order [.... .... 7654 3210] data
    -------------------------------------------
    VDP register $0D (0 1101) = $34 (0011 0100)
    Bits: [10.. .... .... ....]
          [10.0 1101 .... ....] register $0D
          [10.0 1101 0011 0100] data = $34
          [1000 1101 0011 0100] add zeroes
    Hex:      8    D    3    4

So, to set the scroll table address to $D000, we use:

    move.w #$8D34,vdp_control

Writing Scroll Table to VRAM

So far, all I did was update the VDP registers. In order to actually update the scroll offset, I have to update the contents of VRAM address $D000. We do this like explained in an earlier post:

    Bits    [BBAA AAAA AAAA AAAA 0000 0000 BBBB 00AA]
    B order [10.. .... .... .... .... .... 5432 ....] oper. type
    A order [..DC BA98 7654 3210 .... .... .... ..FE] address
    -------------------------------------------------------------
    VRAM Write (00 0001) to addr $D000 (1101 0000 0000 0000):
            [01.. .... .... .... .... .... 0000 ....] oper. type
            [0101 0000 0000 0000 .... .... 0000 ..11] VRAM address
            [0101 0000 0000 0000 0000 0000 0000 0011] add zeroes
      Hex:      5    0    0    0    0    0    0    3

Now we have our 32-bit command word, and can write it to the VDP Control Port to set up our VRAM write:

    vdp_control = $C00004
    move.l #$50000003,vdp_control   ; Set up VDP write to VRAM address $D000

Now that the VRAM write operation is set up, we can write the scroll offset to vdp_data:

    move.w #0,vdp_data            ; scroll offset = 0

Smooth Scrolling

The final code was ridiculously short compared to the work that went into it:

    vdp_control = $C00004
    vdp_data    = $C00000
  
    initScrolling:
        ; Full screen scroll mode
        move.w #$8B00,vdp_control
        ; Scroll data is in $D000
        move.w #$8D43,vdp_control
    
    mainLoop:
        ; do stuff...
  
        jsr WaitVBlankEnd
    
        ; Set up VDP write to VRAM address $D000
        move.l #$50000003,vdp_control
        move.w d4,vdp_data     ; write d4 to VDP
        add.w  #1,d4        ; d4 (x scroll) += 1
    
        jmp MainLoop

References