PPU split-screen scrolling

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
xem
Posts: 5
Joined: Tue Nov 24, 2020 7:18 am

PPU split-screen scrolling

Post by xem » Sun Dec 20, 2020 5:53 am

Hello!
I'm making a NES emulator (for fun), and I have a little issue with the split-screen scrolling in SMB:

Image

As soon as the game's X scroll exceeds 256px, the HUD on top flickers / blinks.

As far as I understand, this is because I "simply" compute my X scroll at the end of each scanline with the equation: "bit_0_of_PPUCTRL * 256 + first_write_of_PPUSCROLL";

I read other topics like this one and this one, and the wiki page of PPU scrolling and Loopy's docs, but I don't understand (yet) how I'm supposed to implement the real thing, with "v", "t", "x" and "w".

What I don't understand is which of these variables is the "final" X scrolling, the one I'm supposed to use as an horizontal offset (in the range [0-512]) when I'm rendering my scanlines?

Thanks!
Attachments
nes5.gif

User avatar
Dwedit
Posts: 4410
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: PPU split-screen scrolling

Post by Dwedit » Sun Dec 20, 2020 8:12 am

Please read this slowly, sentence by sentence, it's easy to skim and scan.

Okay, let's say you're writing out the address of a particular tile on your nametable.
You'd do Tile X + Tile Y * 32 + Nametable * 1024 + 0x2000.
Written in binary, that would be 10NNYYYYYXXXXX. XXXXX = 5 bits of tile X coordinate (Coarse X), YYYYY = 5 bits of tile Y coordinate (Coarse Y), NN = two bits of nametable number (00 = top left, 01 = top right, 10 = bottom left, 11 = bottom right)

Well it turns out that the NES's own PPU addressing system uses something really similar for how it internally handles scrolling.
Written in binary, it's yyyNNYYYYYXXXXX (yyy = pixel scroll for Y coordinate (fine scroll), al other bits just like above)

So the scrolling is done with two variables inside of the PPU.
One is the register that determines where to scroll back to at the end of the line, or at the beginning of the screen. We call this one "T".
The other is the register that determines where it's scrolled right now. We call this one "V".

Both "T" and "V" have the layout of yyyNNYYYYYXXXXX. Low 5 bits (XXXXX) are coarse X scroll, the next 5 bits (YYYYY) are coarse Y scroll, the next two bits (NN) are nametable selection, then the final 3 bits (yyy) are Fine Y scroll.

Also, there's pixel-level X scrolling (Fine X). Fine X takes effect immediately whenever it is changed. Fine X is not found in "T" or "V", it's separate.

At the end of the line (dot 257), all the X scroll related bits (including the nametable bit) are copied from "T" to "V". This is how it 'snaps back' the scroll so it can display the next scanline at the defined X scroll position.
And at the beginning of the frame (during prerender line), all the Y scroll related bits are copied from "T" to "V", and that's how it 'snaps back' the vertical scroll so it can start at the beginning again.

Now for the register summary:
$2000 ......NN, sets the NN bits in "T", as well as the other PPU control bits taking effect.
$2005 (first write) XXXXXxxx , Top 5 bits become XXXXX of "T", low 3 bits become the fine X scroll (updated immediately).
$2005 (second write) YYYYYyyy, Top 5 bits become YYYYY of "T", low 3 bits become yyy of "T" (fine Y scroll)
$2006 (first write) 00yyNNYY, sets the top 8 bits in "T". The top bit of fine Y scroll gets corrupted to 0 here, so we do a $2005 second write to make up for that.
$2006 (second write) YYYXXXXX, Sets the low 8 bits of "T", which includes the coarse X, and low 3 bits of coarse y. After doing this, "T" is copied to "V", allowing mid-screen vertical scrolling.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

xem
Posts: 5
Joined: Tue Nov 24, 2020 7:18 am

Re: PPU split-screen scrolling

Post by xem » Sun Dec 20, 2020 1:13 pm

Thanks a lot @Dwedit!
I read your answer 5 times and it's finally making sense to me, so now I'm implementing it!
But out of curiosity, do you know why this flickering happens on top of SMB's frames when we implement the scroll "naively" ?

I suspect it is due to the fact that "NN" bits are not reliable when read from PPUCTRL only, as writes to $2006 can also alter it. It that it?

User avatar
Dwedit
Posts: 4410
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: PPU split-screen scrolling

Post by Dwedit » Sun Dec 20, 2020 2:56 pm

I have no idea what was going on in the buggy footage, sorry, but if you do it correctly with "T" and "V", you don't get those bugs.

How was my explanation? Is there anything I could have improved or made clearer?
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

xem
Posts: 5
Joined: Tue Nov 24, 2020 7:18 am

Re: PPU split-screen scrolling

Post by xem » Mon Dec 21, 2020 12:57 am

The explanation was great! And it made me understand the wiki page of PPU scrolling too :)

I have implemented this:

- Write to $2000 updates T's NN
- Read $2002 resets w (the latch)
- First write to $2005 updates T's XXXXX, updates xxx and sets w
- Second write to $2005 updates T's YYYYY, updates T's yyy and clears w
- First write to $2006 updates T's YYYYY (the two high bits), updates T's NN, updates T's yyy (with high bit = 0) and sets w
- Second write to $2006 updates T's YYYYY (the three low bits), updates T's XXXXX, clears w, copies T in V
- Copy XXXX and low bit of NN from T to V at dot 257 of each scanline
- Copy YYYYY, yyy and high bit of NN from T to V at the pre-render scanline
- Compute X scroll = low bit of NN * 256 + XXXXX * 8 + xxx, Y scroll = high bit of NN * 256 + YYYYY * 8 + yyy
- Shouldn't $2007 reads and writes also affect V ?

Everything seems perfect, either in SMB (horizontal scroll) or Ice Climbers (vertical scroll)

Image

Thank you! You're amazing.

Post Reply