NES scrolling explained (Loopy's docs explained)

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

NES scrolling explained (Loopy's docs explained)

Post by Disch »

Loopy's scroll docs, while great, are somewhat difficult to understand at first. Since that post in the "What drove koitsu away from nesdev" thread where people showed confusion about the docs and wanted clarification, I figured I'd print this up to see if I could help.

You gotta know a few things:

1) The PPU address you set with $2006 is the same address the PPU uses to fetch tiles for rendering. In effect, it holds the address of the next tile to draw... which is why changing $2006 during rendering will muck up the screen, even if you don't write anything to $2007. This also means that the address is constantly changing during rendering (more details later). This address will hereon be referred to as "LoopyV".

2) In addition to that main address... there's also a temporary value which holds the desired scroll address which the PPU uses to refresh LoopyV with from time to time (like say, every scanline to reset the X Scroll). This temporary value is another value entirely and may contain a completely different value than what's in LoopyV. Hereon, this temporary address will be referred to as "LoopyT".

3) Both LoopyV and LoopyT have an effective range of $0000-7FFF... so they're 15-bits wide (they may actually be 16-bits wide, but it's impossible to set the high bit... and it doesn't do anything anyway so whatever).

4) LoopyT does NOTHING... aside from reload LoopyV at certain times. If you want to actually affect where the PPU draws from (ie, change the scrolling)... or change the address so you can write to $2007... LoopyV is what you need to change. LoopyV does it all. This is why you can't use just $2005 to change the vertical scroll mid-frame (since $2005 never changes LoopyV) and why clever alternating $2006/$2005 writes are required...since the second $2006 write is the only way to set LoopyV aside from the scroll reset at frame start.


I won't get into first/second writes and the toggle used to keep track of which is which. If someone is still confused about that you can ask in this thread and I'll explain, but right now I'll just assume you know.


In Loopy's docs:

'v' represents LoopyV
't' represents LoopyT
'x' represents the fine Horizontal scroll (0-7 pixels)
'd' represents the data being written to the register

These letters are usually followed by a colon, then various 0's and 1's to indicate the bits involved in this operation. 0 bits are to be ignored.... and '1' bits are to be involved in the write.

So now you might have looked at loopy's scroll doc to see the following and went "omgwtf":

Code: Select all

2000 write:
        t:0000110000000000=d:00000011
But it's really very simple... He's just saying the low 2 bits of the data written to $2000 set bits 10 and 11 of LoopyT. To put this in a more friendly layout:

Code: Select all

t ->   ....XX..........
           ^^
           ||
           ||
d -> ......XX
The '.' or '0' bits in my and loopy's explainations are irrelevent to scrolling. The unused LoopyT bits here remain unchanged from their previous state, and the other 'd' bits go on to do other things (see a $2000 register description for details on that)

Hopefully that should clear up his system of write explainations.

One thing that should be mentioned:

Code: Select all

t:0111000000000000=d:00000111
These bits (bits 12, 13, 14) in LoopyV determine the fine-vertical scroll (as I will try to explain below).


LoopyV and LoopyT during rendering:

Note... everything I talk about here assumed the PPU is active (BG or Sprite rendering enabled... if both are disabled, the PPU is "off", and LoopyV is NEVER changed (except on the second $2006 write))

As I briefly stated before, the PPU will constantly update LoopyV as it fetches tiles to render. This is so it knows where to get the next tile to draw. The logic for this may be a little hard to follow... but stick with me.

At Frame Start: On frame start, the PPU will do what I sometimes call "reset the scroll". At the end of VBlank, LoopyT (which probably contains what you just wrote to $2005 for your desired scroll values) gets copied over to LoopyV. This is the address where the PPU will start rendering tiles from.

As tiles are rendered: When 1 tile is fetched and rendered, LoopyV is incremented by 1 so that the next tile will be loaded next. When it hits the right edge of the nametable, it wraps around to the start of the next nametable. Example:
0000, 0001, 0002, 0003 .... 001E, 001F, 0400, 0401 .... 041E, 041F, 0000, 0001 ... etc

At the end of the scanline: Two things are done once a full scanline has been fetched/rendered. The Y position needs to increment, and the X scroll needs to reset to its original position. X scroll resetting is done by reloading the X-related bits from LoopyT back into LoopyV... as portrayed by Loopy's doc:

Code: Select all

scanline start (if background and sprites are enabled):
        v:0000010000011111=t:0000010000011111
Y position is incremented.. this is a bit more complicated than X scrolling. Fine Y scroll (previous mentioned as bits 12, 13, 14) in incremented first, and when it wraps at 8, THEN the coarse Y is incremented, changing the nametable byte to be fetched to the next row of tiles. So somewhat put this in example form:

0000, 1000, 2000 .... 6000, 7000, 0020, 1020 ... 7020, 0040 .. etc

Since nametables aren't as tall as they are wide, coarse Y scrolling is cut off after 29 ($3A0), rather than 31 ($3E0):

0380, 03A0, 0800, 0820 ... 0B80, 0BA0, 0000, 0020 ... etc

If you manage to set the coarse Y scrolling manually so that it's above 29, it will still wrap at 31, however the nametable (bit 11) will not be changed:

03C0, 03E0, 0000, 0020 ... 0380, 03A0, 0800 ... etc


That's about it! I know it's pretty funky but it isn't all that hard once you get the idea. Feel free to ask Qs and I'll answer as best I can.
drk421
Posts: 329
Joined: Sun Nov 14, 2004 11:24 am
Contact:

Post by drk421 »

Thanks!

You might want to add that to he wiki,

http://nesdev.com/wiki/
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Boy, if I had read this a couple of years ago...!

It took me ages to understand the 1s (in d) meant bits that would end up where the other 1s are.

At first I thought the doc was describing some sort of example, that I coudn't see where it was going...
Guest

0's + 1's

Post by Guest »

Just a little point, but in assembler / hardware docs, it's traditional to
represent "don't care" bits as x (lower case, yes).

So, xxxx11xx would make more sense, I think, and be more readable
than using 0s. To me, a set of 1s and 0s is a binary value.

Anyway, just a readability tip from a visitor.

Sam.
User avatar
daparrag
Posts: 1
Joined: Fri Dec 07, 2018 5:20 am

Re: NES scrolling explained (Loopy's docs explained)

Post by daparrag »

Hi guys!!

I just have one simple questions:

you mention that :

At the end of the scanline: Two things are done once a full scanline has been fetched/rendered. The Y position needs to increment, and the X scroll needs to reset to its original position. X scroll resetting is done by reloading the X-related bits from LoopyT back into LoopyV... as portrayed by Loopy's doc:

for horizontal address update:

Code: Select all

scanline start (if background and sprites are enabled):
        v:0000010000011111=t:0000010000011111
for vertical address update:

Code: Select all

scanline start (if background and sprites are enabled):
       v:111101111100000=t:111101111100000

why is it 0x041F for horizontal address update?
why is it 0x7BE0 for vertical address update?.

tanks you!!! :lol: :lol: :lol:
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NES scrolling explained (Loopy's docs explained)

Post by tokumaru »

These are not values, these are masks. They represent which bits are copied from T to V and which retain their old values in V.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: NES scrolling explained (Loopy's docs explained)

Post by tepples »

The mask 0x041F contains all bits of X position: .....8.. ...76543 (except fine X)
The mask 0x7BE0 contains all bits of Y position: .2108.76 543.....

It might be clearer in the revised HTML version of Loopy's docs.
Post Reply