The skinny on NES scrolling: Difference between revisions
m (on scanline -1, v= t at dot 304, plus dot 258 copies the H position bits, AFAIK.) |
|||
Line 70: | Line 70: | ||
Individual bits written to a PPU register are colour-coded to reflect where they end up in ''t''. | Individual bits written to a PPU register are colour-coded to reflect where they end up in ''t''. | ||
Assume all 6502 code is run sequentially in the order shown, one instruction after the next. | |||
Finally, note that the number of bits for ''v'' and ''t'' is 15, while further up the page the documentation shows them as 16-bit. The 16th bit is unused (always 0). | |||
=== Original skinny example === | === Original skinny example === |
Revision as of 07:48, 2 June 2012
Preface
"The skinny on NES scrolling" was posted by loopy on 1999-04-13 to what eventually became the NESdev Yahoo! Group. It was the first to publicly tell how exactly how the PPU uses addresses written to its ports. After over a decade, it is still believed accurate. Some people get turned off by the fact that it's provided as monospaced text inside a zipfile, that addresses have nothing to distinguish them from years, and that the diagrams of what bits get copied where are allegedly difficult to read. What follows is this document, reformatted to web standards, with a few minor things made slightly clearer.
PPU registers
Games using complex raster effects require a complete understanding of how the various address registers inside the PPU work. Here are the related registers:
- v
- Current VRAM address (15 bits)
- t
- Temporary VRAM address (15 bits)
- x
- Fine X scroll (3 bits)
Registers v and t are 15 bits, but because emulators commonly store them in 16-bit machine words, they are shown with an extra bit that's never used.
The PPU uses the current VRAM address for both reading and writing PPU memory thru $2007, and for fetching nametable data to draw the background. As it's drawing the background, it updates the address to point to the nametable data currently being drawn. Bits 10-11 hold the nametable address minus $2000. Bits 12-14 are the Y offset of a scanline within a tile.
Stuff that affects register contents
In the following, d refers to the data written to the port, and A through H to individual bits of this value.
$2000 write:
t:....BA.. ........ = d:......BA
$2005 first write:
t:........ ...HGFED = d:HGFED... x: CBA = d:.....CBA
$2005 second write:
t:......HG FED..... = d:HGFED... t:.CBA.... ........ = d:.....CBA
$2006 first write:
t:..FEDCBA ........ = d:..FEDCBA t:.G...... ........ = 0
$2006 second write:
t:........ HGFEDCBA = d:HGFEDCBA v = t
At dot 258 of each scanline, if rendering is enabled, the PPU copies all bits related to horizontal position from t to v:
v:.....H.. ...EDCBA = t:.....H.. ...EDCBA
And at dot 304 of the pre-render scanline, if rendering is enabled, the PPU copies all bits from t to v:
v = t
Note: $2005 and $2006 share the toggle that selects between first/second writes. Reading $2002 will clear it.
All of this info agrees with the tests Loopy has run on an NES console. If there's something you don't agree with, please let the BBS know so that a member can verify it.
Wrapping around
You can think of bits 4-0 of the VRAM address as the "coarse x scroll"(*8) that the PPU increments as it draws. As it wraps from 31 to 0, bit 10 is switched. You should see how this causes horizontal wrapping between nametables (0,1) and (2,3).
You can think of bits 9-5 as the "coarse y scroll"(*8). This functions slightly different from the X. It wraps to 0 and bit 11 is switched when it's incremented from 29 instead of 31. There are some odd side effects from this. If you manually set the value above 29 (from either $2005 or $2006), the wrapping from 29 obviously won't happen, and attribute data will be used as nametable data. The "y scroll" still wraps to 0 from 31, but without switching bit 11. This explains why writing 240+ to 'Y' in $2005 appeared as a negative scroll value.
Examples
Below are examples using actual 6502 code, indicating (tracking) what happens to all relevant variables described above, both before and after the 6502 instructions are executed.
Individual bits written to a PPU register are colour-coded to reflect where they end up in t.
Assume all 6502 code is run sequentially in the order shown, one instruction after the next.
Finally, note that the number of bits for v and t is 15, while further up the page the documentation shows them as 16-bit. The 16th bit is unused (always 0).
Original skinny example
This is based completely off of the above Stuff that affects register contents section.
Before | Instructions | After | Notes | ||||
---|---|---|---|---|---|---|---|
t | v | x | t | v | x | ||
....... ........ | ....... ........ | ... | LDA #$00 (%00000000) STA $2000 |
...00.. ........ | ....... ........ | ... | |
...00.. ........ | ....... ........ | ... | LDA #$7D (%01111101) STA $2005 |
...00.. ...01111 | ....... ........ | 101 | |
...00.. ...01111 | ....... ........ | 101 | LDA #$5E (%01011110) STA $2005 |
1100001 01101111 | ....... ........ | 101 | |
1100001 01101111 | ....... ........ | 101 | LDA #$3D (%00111101) STA $2006 |
0111101 01101111 | ....... ........ | 101 | Bit 15 of t gets set to zero |
0111101 01101111 | ....... ........ | 101 | LDA #$F0 (%11110000) STA $2006 |
0111101 11110000 | 0111101 11110000 | 101 | After t is updated, contents of t copied into v |
2006-2005-2005-2006 example
This is based on Drag's example on the nesdev forum where writes to PPU registers are done in the order of $2006, $2005, $2005, $2006, and what the effect is.
To understand this example, you need to keep in mind the following note from the above section: Note: $2005 and $2006 share the toggle that selects between first/second writes. Reading $2002 will clear it. |
Before | Instructions | After | Notes | ||||
---|---|---|---|---|---|---|---|
t | v | x | t | v | x | ||
....... ........ | ....... ........ | ... | LDA #$27 (%00100111) STA $2006 |
0100111 ........ | ....... ........ | ... | Bit 15 of t set to zero |
0100111 ........ | ....... ........ | ... | LDA #$3E (%00111110) STA $2005 |
1100100 111..... | ....... ........ | ... | Behaviour of 2nd $2005 write |
1100100 111..... | ....... ........ | ... | LDA #$7D (%01111101) STA $2005 |
1100100 11101111 | ....... ........ | 101 | Behaviour of 1st $2005 write |
1100100 11101111 | ....... ........ | 101 | LDA #$99 (%10011001) STA $2006 |
1100100 10011001 | 1100100 10011001 | 101 | After t is updated, contents of t copied into v |