PPU scrolling: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
m (→‎References: changing this to See Also, but also noting this should really be a merge job)
(Consensus on talk is that everything after "The common case" is poorly written, and "Skinny" describes it better.)
Line 9: Line 9:
#Find the X and Y coordinates of the upper left corner of the visible area (the part seen by the "camera")
#Find the X and Y coordinates of the upper left corner of the visible area (the part seen by the "camera")
#Write the X coordinate to [[PPUSCROLL]] ($2005)
#Write the X coordinate to [[PPUSCROLL]] ($2005)
#Write the Y coordinate to [[PPUSCROLL]]
#Write the Y coordinate to PPUSCROLL
#Write the starting page (high order bit of X and Y) to bits 0 and 1 of [[PPUCTRL]] ($2000)
#Write the starting page (high order bit of X and Y) to bits 0 and 1 of [[PPUCTRL]] ($2000)


Line 15: Line 15:


By itself, this allows moving the camera within a usually two-screen area (see [[Mirroring]]), with horizontal and vertical wraparound if the camera goes out of bounds.
By itself, this allows moving the camera within a usually two-screen area (see [[Mirroring]]), with horizontal and vertical wraparound if the camera goes out of bounds.
To scroll over a larger area than the two screens that are already in VRAM, you figure out what columns or rows of the nametable are just coming into view, and you write that to VRAM before you set the scroll, as seen in the animation below.
To scroll over a larger area than the two screens that are already in VRAM, you figure out what columns or rows of the nametable are just coming into view, and you write that to VRAM before you set the scroll, as seen in the animation below.
The area that needs rewritten at any given time is sometimes called the "seam" of the scroll.
The area that needs rewritten at any given time is sometimes called the "seam" of the scroll.
[[File:SMB1 scrolling seam.gif|center]]
[[File:SMB1 scrolling seam.gif|center]]


== What the registers do ==
== Advanced ==
Most uses of scrolling need not use any other registers.
Raster effects such as split-screen scrolling, status bars, and 3D effects, on the other hand, need a more thorough understanding of the underlying mechanism.
 
Writing to [[PPUSCROLL]] and [[PPUADDR]] affects the same internal [[NES PPU|PPU]] registers, but in different ways. The same 15-bit VRAM address value is used both for the user to access the VRAM via [[PPUDATA]] ($2007) and for the PPU to make its own internal accesses to fetch name and attribute table data. There's also a 15-bit latch from which the VRAM address register is reloaded at various times. By convention, the VRAM address is often referred to as <code>Loopy_V</code>, and the latch as <code>Loopy_T</code>, since "<code>V</code>" and "<code>T</code>" were the mnemonics used by Loopy in his original document. In addition to the VRAM address and its corresponding latch, a fine X-scroll value {<code>Loopy_X</code>) is also utilized. This takes effect immediately, and controls which bit from the PPU's internal pixel shift registers will be selected.
 
Both PPUSCROLL and PPUADDR are designed to take two consecutive writes. Both registers share the same internal flip-flop for this purpose: the PPU treats it as a first write when it is false and as a second write when it is true. It toggles on any write to [[PPUSCROLL]] or [[PPUADDR]] and is set to false on reads from [[PPUSTATUS]] ($2002).
 
=== Writes to PPUSCROLL ===
The first write to [[PPUSCROLL]] controls the horizontal scroll offset. This is done by copying the lower three bits of the value written (pixel within tile) into <code>Loopy_X</code>, and the upper five bits (distance from left in tiles) into D4-D0 of <code>Loopy_T</code>. The coarse horizontal scroll offset will take effect when the next scanline is rendered, while the fine horizontal scroll offset in <code>Loopy_X</code> takes effect immediately.
 
The second write to [[PPUSCROLL]] controls the vertical scroll offset. The lower three bits of the value written (scanline within tile) are copied to D14-D12 of <code>Loopy_T</code>, and the upper five bits (distance from top in tiles) are copied to D9-D5 of <code>Loopy_T</code>. Since these bits are not reloaded into <code>Loopy_V</code> before rendering a scanline, they won't take effect until the next frame.
 
=== Writes to PPUADDR ===
The first write to [[PPUADDR]] sets the high address byte. Since the PPU's ''external'' address bus is only 14 bits in width, the top two bits of the value written are ignored. D14 of <code>Loopy_T</code> is cleared, while D13-D8 are loaded with the lower six bits of the value written.
 
The second [[PPUADDR]] write is copied in its entirety to D7-D0 of <code>Loopy_T</code>. More importantly, <code>Loopy_T</code> is then copied into <code>Loopy_V</code>. Games that perform scroll splits or extend vertical blanking use writes to [[PPUADDR]] instead of [[PPUSCROLL]] to handle scrolling at the split, since it is the only way to change the vertical scroll offsets during mid-frame rendering.
 
To perform such a split, games will perform a four-write sequence to [[PPUSCROLL]] and [[PPUADDR]].
It sets some bits more than once; only the last write to any given bit counts.
When you count only those bits that don't get overwritten by a later write, the sequence looks like this:[http://forums.nesdev.org/viewtopic.php?p=78593#p78593]
<pre>
2006/1 ---- NN-- (nametable select)
2005/2 VV-- -vvv (upper two bits of coarse V scroll, all bits of fine V scroll)
2005/1 ---- -hhh (fine horizontal scrolling) (takes effect immediately)
2006/2 VVVH HHHH (lower three bits of coarse V scroll, all bits of coarse H scroll)
</pre>
[http://forums.nesdev.org/viewtopic.php?p=64111#p64111 This forum post by tokumaru] gives example code, and [http://forums.nesdev.org/viewtopic.php?p=105762#p105762 this post] is the same thing with detailed comments.
 
====Timing====
For the above code (writes to [[PPUADDR]], [[PPUSCROLL]], [[PPUSCROLL]], [[PPUADDR]]):
 
*The first two writes only affect <code>Loopy_T</code>, so they can happen at any time.
*The third write (setting fine X) should happen at dot 256 or later. Since writes take 4 CPU cycles (12 PPU dots) to finish, the store to [[PPUSCROLL]] instruction should begin at dot 244 or later.
*The fourth write should happen before dot 304.  The store to [[PPUADDR]] instruction should begin before dot 292.
 
=== Writes to PPUCTRL ===
Writing to [[PPUCTRL]] also affects the VRAM address latch. The lower two bits of the value written (used for nametable selection) are copied to D11-D10 of <code>Loopy_T</code>.
 
===Use of <code>Loopy_V</code> during rendering===
At the beginning of each frame, the contents of <code>Loopy_T</code> are copied into <code>Loopy_V</code>, as long as background or sprites are enabled. This takes place continuously during the horizontal sync pulse, which spans PPU cycles 280 through 304 of the pre-render scanline [http://www.nesdev.org/bbs/viewtopic.php?t=999&highlight=loopyv+loopyt&sid=942bc1830103232074f681d1de931cf9]. So as long as you set the scroll position and enable rendering before cycle 304, scrolling should work properly.
 
Also, at PPU cycle 257 after each scanline is rendered (assuming sprites or background are enabled), the PPU copies the six bits of <code>Loopy_T</code> that control horizontal scroll position (D10 and D4-D0) into <code>Loopy_V</code>, since they were altered in <code>Loopy_V</code> during scanline rendering.


==See Also==
The reason you have to write PPUSCROLL last is because PPUSCROLL and PPUADDR actually write to the same register inside the PPU, just with the bits distributed differently.
*[[The skinny on NES scrolling]] - a detailed description of PPU scrolling with diagrams and examples
This was a cost-saving measure on Nintendo's part that also turned out to allow split-screen effects such as a moving playfield and still status bar.
[[The skinny on NES scrolling]] gives a detailed description of scrolling behavior with diagrams and examples.

Revision as of 23:17, 24 March 2015

Scrolling is the movement of the displayed portion of the map. Games scroll to show an area much larger than the 256x240 pixel screen. For example, areas in Super Mario Bros. may be up to 24 screens wide. The NES's first major improvement over its immediate predecessors (ColecoVision and Sega Mark 1) was pixel-level scrolling of playfields.

The common case

Ordinarily, a program writes to two PPU registers to set the scroll position in its NMI handler:

  1. Find the X and Y coordinates of the upper left corner of the visible area (the part seen by the "camera")
  2. Write the X coordinate to PPUSCROLL ($2005)
  3. Write the Y coordinate to PPUSCROLL
  4. Write the starting page (high order bit of X and Y) to bits 0 and 1 of PPUCTRL ($2000)

The scroll position written to PPUSCROLL is applied at the end of vertical blanking, just before rendering begins, therefore these writes need to occur before the end of vblank. Also, because writes to PPUADDR ($2006) can overwrite the scroll position, the two writes to PPUSCROLL must be done after any updates to VRAM using PPUADDR.

By itself, this allows moving the camera within a usually two-screen area (see Mirroring), with horizontal and vertical wraparound if the camera goes out of bounds. To scroll over a larger area than the two screens that are already in VRAM, you figure out what columns or rows of the nametable are just coming into view, and you write that to VRAM before you set the scroll, as seen in the animation below. The area that needs rewritten at any given time is sometimes called the "seam" of the scroll.

SMB1 scrolling seam.gif

Advanced

The reason you have to write PPUSCROLL last is because PPUSCROLL and PPUADDR actually write to the same register inside the PPU, just with the bits distributed differently. This was a cost-saving measure on Nintendo's part that also turned out to allow split-screen effects such as a moving playfield and still status bar. The skinny on NES scrolling gives a detailed description of scrolling behavior with diagrams and examples.