This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Skinny scrolling questions 2013

Skinny scrolling questions 2013
by on (#105753)
So, I went over our Wiki article, The skinny on NES scrolling and gave it a pass to try and make it easier to understand, and give more practical instructions on how to scroll on the NES. I'd also appreciate a proof-read, and any corrections that need to be made.

I ended up wondering about a few things, which I mention on the discussion page:

1. When exactly does the Y scroll increment get done? Is this before / after / or concurrent with the reloading of v from t? I looked at the nintendulator source, and it seemed to be doing it on pixel 250, but that can't be right, can it? Wouldn't that Y-shift the right edge of the screen? Or is the last tile always already fetched by then?

A corollary of that is that, if I am thinking about this correctly, you should set the Y scroll value to be Y-1 when you make your writes, since it's about to be incremented. Is this correct? (I would like to add a proper explanation of this to the example/guide.)

Also, could someone with a good grasp of the Y scroll increment and wrap logic write it into the article? All we've got now are a bunch of statements from Loopy's document, which appear to be true, but require understanding of the increment behaviour to really know why they happen.

Attributes are another thing not really touched on by this document, though it's probably answered elsewhere in the wiki in PPU timing documents or something, but an explanation of how v gets mangled to look up attributes might be useful as well. Also, if attributes are fetched early in the scanline and can't be updated mid-scanline, this should probably be mentioned. (Tile fetch timing should also be relevant for the same reason.)

2. The fine x gets reloaded at the start/end of the line from a temporary register not listed in the document, doesn't it? If it didn't, it would be critical where exactly in the previous scanline you made the first $2005 write, which is not the case, as far as I have seen when trying it on the NES. Am I wrong about this?

Edit: Seems to be irrelevant, I may have been thinking about it wrong, but whether it's a reload or not, doesn't matter, we could call the reload register [i]x[i/]

3. Making scroll writes during hblank. Some threads have suggested this, but is it possible, and how? When I've tried to do scrolling in the past, I could never seem to get timings that accurate, so it was always necessary to pull it back from the right edge and accept some amount of glitching on the end of the line. Glitches like this seem to be very normal for games with split scrolling... can it actually be avoided? Is it bad advice to suggest timing it during hblank?

Edit: I've realized that you can't use hblank if only using $2005 to do X scroll, but theoretically the full 2006/2005/2005/2006 cycle should work fine in hblank, as long as it doesn't interfere with tile/attribute fetches... when do those happen?
Re: Skinny scrolling questions 2013
by on (#105754)
The Y scroll increment happen somewhere (I think cycle 250 if Nintendulator's source says so it should be right), no matter if you write to $2006 or not.
1)
There is 2 ways to correctly set the Y scroll...
Either you write the fine scroll Y-1 before the cycle 250, or you write the fine scroll Y after cycle 250. In both cases you have to make sure the jittering will never make your 2nd $2006 write before/after 250 else you'll have a shaking image.

2)
The fine X scroll takes effect immediately. You could modify it as many times you want in a scanline. It's just not practical because of jittering, so this is why few (if any) games or tech demoes ever did this.

3)
You can do scroll writes anytimes really. There is nothing preventing you to execute a "sta $2005" or "sta $2006" instruction no matter where the PPU is in it's fame. Glitches are hard to avoid because of jittering (once again...) But normally you can avoid them it's just it requires patience and trial-and-error testing.
Re: Skinny scrolling questions 2013
by on (#105755)
I think you misunderstand what I mean about fine X. Yes, it does appear to be set immediately by $2005, but it also appears to be correctly reloaded at the beginning of the scanline. If it wasn't the regular jitter of where exactly in the scanline it gets set would also be jittering the fine X.

Or... am I just thinking of fine X wrong? Is it a 3-bit counter that gets incremented every pixel, or is it some kind of offset that is added to the "real" counter register?

Edit: now that I am thinking about the hblank issue, since v is reloaded at pixel 256, not after hblank, you can't really do X scrolling via $2005 during hblank, since it will will set fine X immediately, but coarse X will wait a line. I guess it should be fine for X/Y scrolling though. I will update it to explain this, thanks.
Re: Skinny scrolling questions 2013
by on (#105756)
I don't know if fine X gets reloaded, but in fact it makes no difference in functionality at all.
Since writing to $2005.1 buffers the top 5 bits, which are applied after next HBlank and the low 3 bits takes effect immediately. Then it makes no difference if you consider it buffers all 8 bits.
Re: Skinny scrolling questions 2013
by on (#105758)
Well, the 5 coarse X bits are buffered in t, that part is fine.

I guess the way to think about it is that the 3 bits of fine X do not change unless you write them. Maybe they get added to the lowest 3 bits of the line's pixel counter to determine when to advance the tile? My confusion was that I was thinking of fine X itself as the counter, since that's what the fine Y bits in v appear to be doing (at least, in nintendulator). If that was the case, it would need a reload every line, because if you were altering the counter directly via $2005 it would get out of phase. But even if fine X is the counter, there still needs to be an unchanging 3 bits to reload it from, so we could just call that fine X instead, so I guess it doesn't matter, either way there is something that correctly corresponds to the concept of the 3-bit fine X register.

So you're right, it doesn't make a difference, I guess.
Re: Skinny scrolling questions 2013
by on (#105759)
Fine X doesn't get "reloaded" periodically the way v gets reloaded from t. It's the select on a set of four 8 to 1 muxes coming out of the background tile reader.
Re: Skinny scrolling questions 2013
by on (#105760)
rainwarrior wrote:
I looked at the nintendulator source, and it seemed to be doing it on pixel 250, but that can't be right, can it? Wouldn't that Y-shift the right edge of the screen? Or is the last tile always already fetched by then?

Sounds right to me, the last tile's patterns have probably already been loaded by then.

Quote:
A corollary of that is that, if I am thinking about this correctly, you should set the Y scroll value to be Y-1 when you make your writes, since it's about to be incremented. Is this correct?

I don't think so, since ideally the final $2006 write will happen during HBlank, after the PPU has incremented/reloaded the VRAM address.

Quote:
The fine x gets reloaded at the start/end of the line from a temporary register not listed in the document, doesn't it?

I don't think the fine X scroll needs to be reloaded, since it's not modified by the PPU during rendering.

Quote:
If it didn't, it would be critical where exactly in the previous scanline you made the first $2005 write, which is not the case, as far as I have seen when trying it on the NES. Am I wrong about this?

AFAIK, changes to the fine X scroll take effect immediately. Remember that $2005/$2006 share the 1st/2nd write latch, so the "first" $2005 write will actually look like the second one to the PPU if it comes after the first write to $2006.

Quote:
Making scroll writes during hblank. Some threads have suggested this, but is it possible, and how? When I've tried to do scrolling in the past, I could never seem to get timings that accurate, so it was always necessary to pull it back from the right edge and accept some amount of glitching on the end of the line.

I can assure you that clean splits are possible, I have done it with this code. Everything before the last 2 PPU writes do not affect rendering, and can be performed anywhere in the scanline. Only the last 2 PPU writes are timing-sensitive, and that's just 8 cycles out of the 28 of HBlank, so they're not that hard to fit at all (even when you account for the latency or IRQs and sprite hits).

Quote:
Glitches like this seem to be very normal for games with split scrolling... can it actually be avoided?

For some reason lots of commercial games have glitched screen splits, but they can definitely be avoided. It seems that most developers back then didn't fully understand the $2005/$2006 behavior, and that building games just for adjusting this kind of timing issue wasn't a priority.
Re: Skinny scrolling questions 2013
by on (#105761)
tokumaru wrote:
rainwarrior wrote:
Making scroll writes during hblank. Some threads have suggested this, but is it possible, and how? When I've tried to do scrolling in the past, I could never seem to get timings that accurate, so it was always necessary to pull it back from the right edge and accept some amount of glitching on the end of the line.

I can assure you that clean splits are possible, I have done it with this code. Everything before the last 2 PPU writes do not affect rendering, and can be performed anywhere in the scanline. Only the last 2 PPU writes are timing-sensitive, and that's just 8 cycles out of the 28 of HBlank, so they're not that hard to fit at all (even when you account for the latency or IRQs and sprite hits).


Yeah, I realized after thinking about it that when I was having problems with this was when I was trying to do X-only scroll with just $2005 writes, which I realize now can't be hidden in hblank because the reload of v happens too early. I have added an explanation of this to the article.
Re: Skinny scrolling questions 2013
by on (#105762)
Just in case the code I linked to is not clear, here's a somewhat commented version:

Code:
VRAM address: 0yyyNNYY YYYXXXXX
yyy: fine Y scroll;
NN: name table index;
YYYYY: coarse Y scroll;
XXXXX: coarse X scroll;
xxx: fine X scroll (not part of the address);

;****************

   ;start setting the scroll before HBlank (48 cycles)
   lda ScrollX+1
   lsr
   lda ScrollY+1
   rol
   asl
   asl
   sta $2006 ;bits that matter: ****NN**
   lda ScrollY+0
   sta $2005 ;bits that matter: YY***yyy
   asl
   asl
   and #%11100000
   ldx ScrollX+0
   sta ScrollX+0
   txa
   lsr
   lsr
   lsr
   ora ScrollX+0

   ;finish setting the scroll during HBlank (11 cycles)
   stx $2005 ;bits that matter: *****xxx
   sta $2006 ;bits that matter: YYYXXXXX
   stx ScrollX+0
   
;****************
   
NOTE: ScrollX+0 is temporarilly used to create the second $2006 byte, but restored at the end.
Re: Skinny scrolling questions 2013
by on (#105767)
Happening at dot 251 isn't really that odd, because of when the fetches happen to fill the 16-pixel wide shift register. The fetches are basically done 16 dots earlier than you think they are.
These are the fetches the PPU does:
0,1: fetch tile
2,3: fetch attribute byte
4,5: fetch pattern byte 1
6,7: fetch pattern byte 2
Then when they're done, you have 8 pixels ready for the shift register.
So 16 pixels are buffered in the shift register before anything gets drawn.
Fine scroll just picks an offset into the shift register to draw pixels from.

The fetches for pixel #0 start at dot 320, then pixel #8 starts at dot 328, then pixel #16 starts at dot 0, pixel #24 starts at dot 8, etc. By the time you reach dot 251, you're doing the attribute fetch for the final tile on the right side of the screen. After that's been fetched, the 'scroll address' isn't used for anything else on that scanline, so it's safe to increment. Then you later reach dot 320, and you start fetching stuff for the next scanline.

critical timings: Y scroll increments at dot 251, X scroll relatches at dot 257.

If you want to do scroll raster effects, you do the 2006/2005/2005/2006 write thing.
The first 2006/2005 writes can happen any time after dot 257 of the previous scanline, because they won't have any visible effect on the currently rendering scanline.
The second 2005 write must finish at dot 256 or later to avoid a fine scroll artifact
The second 2006 write must finish before dot 320 to make the next fetches work correctly.
So you actually got quite a spread of time to do the scroll writes.
The NES's PPU doing a Y scroll increment at dot 251 will not affect your four write sequence at all, since it's applying it to the 'vram address', and that gets overwritten by your second write to 2006.
Re: Skinny scrolling questions 2013
by on (#105769)
Technically, doesn't the change to fine Y at X=251 affect which of the eight rows in the (unseen) 34th tile is fetched at X=253 and 255?
Re: Skinny scrolling questions 2013
by on (#105789)
If you have an NTSC NES and a Powerpak, I would suggest playing around with the unfinished tech demo I linked to in this post to get some more understanding of how the updates work , as I think it illustrates some of the known (and unknown) effects quite well. Though some of the peculiarities I think we will not get an answer to until the reverse engineering of the 2C02 based on the die is fully completed...

(the code splits the screen twice during the scanline rendering rather than in hblank, in order to create a freely scrolling window in the middle of the screen. It starts up in a stable position, but is very sensitive to glitches when you start moving the window around. Think those can be fixed with even more tricky code though)
Re: Skinny scrolling questions 2013
by on (#105798)
Okay, all this information has been added to the article. I have one final question.

I added a section on the relationship between the v register and tile/attribute fetches. In particular I am confused about the attribute fetch, because it seems to me it should just be a simple bit-packing, but nintendulator's source has this 256 byte AttribLoc table to help look up the address, which makes me wonder if it's more complicated than that. There are no comments about AttribLoc in the source.

So, is the following explanation correct?

http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling#Tile_and_attribute_fetching

Quote:
The high bits of v are used for fine Y during rendering, and addressing nametable data only requires 12 bits, with the high 2 CHR addres lines fixed to the 0x2000 region. The address to be fetched during rendering can be deduced from v in the following way:
Code:
 tile address      = 0x2000 | (v & 0x0FFF)
 attribute address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)

The low 12 bits of the attribute address are composed in the following way:
Code:
 NN 1111 YYY XXX
 || |||| ||| +++-- high 3 bits of coarse X (x/4)
 || |||| +++------ high 3 bits of coarse Y (y/4)
 || ++++---------- attribute offset (960 bytes)
 ++--------------- nametable select

Re: Skinny scrolling questions 2013
by on (#105845)
I have a related question (and no devcart to test on hardware, and no computer fast enough to make Visual2c02 not painful)-

Does anything affect t other than the writes the CPU makes? Specifically, one could write to $2005 then $2006, which would load new values into x and the lower 8 bits of t, with the top 7 bits of t unchanged. Unchanged from what? The initial scroll at the top of the screen? 0?
Re: Skinny scrolling questions 2013
by on (#105846)
Yes.
Every $2006.2 write will reload all counters immediately.
$2005.1 writes reload the fine HScroll.
Otherwise only the coarse H scroll is reloaded at the begininging of all scanline.
All counters are reloaded at the end of VBlank.

The later 3only if you are not in forced blank mode ($2001 is not set to 0).

That's basically the only thing you have to remember really. Everything else can be easily deducted from these. I never understood the "skinny scrolling" doccument, but never had problem to change the scrolling midframe.
Re: Skinny scrolling questions 2013
by on (#105849)
lidnariq wrote:
Does anything affect t other than the writes the CPU makes?

AFAIK, the PPU only copies bits from t to v, it never does anything to t unless the CPU writes to $2005/$2006.

Quote:
Unchanged from what? The initial scroll at the top of the screen? 0?

The previous scroll value (which would be the initial scroll at the top of the screen if you didn't do anything mid-frame), most likely. I'll see if I can try this the next time I play with my PowerPak.
Re: Skinny scrolling questions 2013
by on (#106125)
Another question: does the OAM DMA affect v at all, or does it have its own way of addressing the PPU bus?
Re: Skinny scrolling questions 2013
by on (#106131)
OAM DMA doesn't use the VRAM address, it's a bunch of repeated writes to 2004. It increments the OAM address 256 times.
Re: Skinny scrolling questions 2013
by on (#106155)
Oh right, I dunno what I was thinking. I guess it's been a long while since I uploaded OAM data any other way besides DMA.