Yet another question about scrolling

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Yet another question about scrolling
by on (#67464)
I have read many times in several places about how VRAM can only be read/written during VBlank. I am still a little confused about this.

My question is, when do I use data written to 2005/2006 for scrolling and when do I just write to 2005/2006 and ignore the data as scroll data? I know that sometimes what is written to those locations is used for scrolling, but not always. I've been wrestling with this issue for some time now and I'm ready to get past it.

I really would like to know how to handle data when it's being written to 2005 and 2006. It is my understanding that VBlank and BGEnable and SPREnable are somehow involved, but I'm just to sure how.

I'm not sure if either of these are going down the right path, but here is some pseudo code that probably better explains my confusion:

Code:
if((addr == 0x2005) && !vblank)
{ //place scroll 'data' into 't' }

or

if((addr == 0x2005) && (bgEnabled || spritesEnabled))
{ //place scroll 'data' into 't' }

//and then the same idea with 2006


Thanks :)

EDIT: I guess posting some real code might help, so here it is. This is inside of my setMemoryByte() function.

Code:
            // Screen Scroll Offset
            else if (intAddr == 0x2005)
            {
                if (!PPULatchToggle)
                {
                    // Offset X
                    t &= 0xFFE0;
                    t |= ((byteOne & 0xF8) >> 3);

                    fineX = (byte)(byteOne & 0x07);
                }
                else
                {
                    // Offset Y
                    t &= 0xFC1F;
                    t |= ((byteOne & 0xF8) << 2);
                    t &= 0x8FFF;
                    t |= ((byteOne & 0x07) << 12);
                }

                PPULatchToggle = !PPULatchToggle;
            }

            else if (intAddr == 0x2006)
            {
                if (!PPULatchToggle)
                {
                    // Copy byte into temporary location
                    intPPUAccess = byteOne;

                   #region Scrolling Data
                   // Get first 2006 write bits
                   t &= 0xC0FF;
                   t |= ((byteOne & 0x3F) << 8);

                   // Clear top two bits
                   t &= 0x3FFF;
                   #endregion
                }
                else if (PPULatchToggle)
                {
                    intPPUAccess = byteOne + intPPUAccess * 16 * 16;

                   #region Scrolling Data
                   // Get second 2006 write bits
                   t &= 0xFF00;
                   t |= byteOne;
                   #endregion
                }

                PPULatchToggle = !PPULatchToggle;
            }

by on (#67465)
You should always let $2005/$2006 writes affect 't' and 'v', no matter what the PPU is doing. If you emulate correctly the way the PPU makes use of 'v' while rendering, scroll changes should work without problems in case the PPU is rendering. If the PPU is not rendering, 'v' will be used by the game to access VRAM normally.

What I mean is that there is no need to treat $2005/$2006 writes differently at different times, as long as your CPU and PPU are "running in parallel" and your PPU code makes use of 'v' for rendering like a real NES does.

by on (#67466)
tokumaru wrote:
You should always let $2005/$2006 writes affect 't' and 'v', no matter what the PPU is doing. If you emulate correctly the way the PPU makes use of 'v' while rendering, scroll changes should work without problems in case the PPU is rendering. If the PPU is not rendering, 'v' will be used by the game to access VRAM normally.

What I mean is that there is no need to treat $2005/$2006 writes differently at different times, as long as your CPU and PPU are "running in parallel" and your PPU code makes use of 'v' for rendering like a real NES does.


And by the ppu 'not rendering', you mean not writing lines 0-239 of the visible frame, right?

Also, does the 'data' that is written to 2005/2006 actually get written to the memory location 2005/2006 all the time as well? And isn't there a special case where the 2006 writes are used by 2007 during scrolling?

by on (#67467)
tineras wrote:
And by the ppu 'not rendering', you mean not writing lines 0-239 of the visible frame, right?

"Not rendering" is either VBlank or forced blanking (SPR and BKG off).

Quote:
Also, does the 'data' that is written to 2005/2006 actually get written to the memory location 2005/2006 all the time as well?

There is no memory mapped to $2005/$2006, whatever is written there is routed to the PPU registers and not stored in any RAM location.

Quote:
And isn't there a special case where the 2006 writes are used by 2007 during scrolling?

$2007 has nothing to do with scrolling (except for the fact that accessing it increments the VRAM address). Writes to $2006, like I said before, modify the VRAM address, which is used for rendering, so they always change the scroll.

When a program "sets the scroll" it's doing nothing more than specifying the exact point where rendering will begin (i.e. the point that will show up at the top left corner of the screen). When rendering starts, the PPU will use the last value set for the scroll to begin the rendering of the screen, but at any point during rendering the scroll can be changed to force the PPU to render another part of the name tables.

The "special case" you mentioned is not special at all, the registers behave the same way all the time. The thing is that writes to $2005 do not affect the VRAM address 'v' directly, they only modify the temporary register 't'. At the beginning of the frame, 't' is copied to 'v', but during rendering only the horizontal part of 't' is copied to 'v' every scanline, so if a program wants to modify the vertical scroll during rendering, it has to use $2006, which directly manipulates 'v'. This is "special" from the point of view of the programmer, who usually uses $2005 for setting the scroll, but from the point of view of the emulator or the NES there is nothing special about it.

by on (#67468)
tokumaru wrote:
tineras wrote:
And by the ppu 'not rendering', you mean not writing lines 0-239 of the visible frame, right?

"Not rendering" is either VBlank or forced blanking (SPR and BKG off).

Quote:
Also, does the 'data' that is written to 2005/2006 actually get written to the memory location 2005/2006 all the time as well?

There is no memory mapped to $2005/$2006, whatever is written there is routed to the PPU registers and not stored in any RAM location.

Quote:
And isn't there a special case where the 2006 writes are used by 2007 during scrolling?

$2007 has nothing to do with scrolling (except for the fact that accessing it increments the VRAM address). Writes to $2006, like I said before, modify the VRAM address, which is used for rendering, so they always change the scroll.

When a program "sets the scroll" it's doing nothing more than specifying the exact point where rendering will begin (i.e. the point that will show up at the top left corner of the screen). When rendering starts, the PPU will use the last value set for the scroll to begin the rendering of the screen, but at any point during rendering the scroll can be changed to force the PPU to render another part of the name tables.

The "special case" you mentioned is not special at all, the registers behave the same way all the time. The thing is that writes to $2005 do not affect the VRAM address 'v' directly, they only modify the temporary register 't'. At the beginning of the frame, 't' is copied to 'v', but during rendering only the horizontal part of 't' is copied to 'v' every scanline, so if a program wants to modify the vertical scroll during rendering, it has to use $2006, which directly manipulates 'v'. This is "special" from the point of view of the programmer, who usually uses $2005 for setting the scroll, but from the point of view of the emulator or the NES there is nothing special about it.


I appreciate your reply. This is pretty much how I have things setup right now. But I still experiencing some scrolling issues. I posted a video below to show an example. I believe some of these issues are just timing related.

Also, a clip of SMB is included, the issues are probably not related to scrolling, but any input would be appreciated.

Misc games with scroll issues:
http://www.youtube.com/watch?v=uCG4POGDJJQ

SMB Issues:
http://www.youtube.com/watch?v=89zuJmT084E

Thanks :)

by on (#67469)
Those look like CPU bugs.

by on (#67470)
Dwedit wrote:
Those look like CPU bugs.


I am able to pass all of the Kevtris' nestest tests and all but one of the NEStress CPU tests (Fail: CPU RAM Mirror <-- not sure why).

Do you have any suggestions on which tests I could move on to? Or should I just start going through Blargg's tests one by one?

by on (#67471)
Also, I see the "weave" bug for scanline-by-scanline scrolling. Remember that fine X is updated "immediately", but coarse X is only updated right before Hblank time (clock 251, I think).
So games tend to write to 2005 during hblank time, so the fine X is for the upcoming scanline, but the coarse X is for the scanline after that.

Excitebike and Kung Fu appeared to have timing errors.