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.
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:
- Full screen scroll: The whole screen is scrolled uniformly
- Line scroll: Every line is scrolled differently according to a table
- Cell scroll: Every 8 lines is scrolled differently according to a table
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:
- Set full screen scroll mode
- Set VRAM address of horizontal scroll offset
- Increment and update scroll offset every frame
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
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