PPU scrolling: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(Parially removed GBA-style reigster names (exept when the normal adress were alredy present))
(The common case)
Line 1: Line 1:
'''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 near the end of vertical blanking, after it has made any updates to VRAM:
#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 Y coordinate to PPUSCROLL
#Write the starting page (high order bit of X and Y) to bits 0 and 1 of PPUCTRL ($2000)
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.
[[File:SMB1 scrolling seam.gif|256px|frame|Updating at the "seam"]]
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.
== What the registers do ==
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 <code>PPUSCROLL</code> ($2005) and <code>PPUADDR</code> ($2006) 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 <code>PPUDATA</code> ($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.
Writing to <code>PPUSCROLL</code> ($2005) and <code>PPUADDR</code> ($2006) 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 <code>PPUDATA</code> ($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 <code>$2005</code> and <code>$2006</code> are designed to take two consecutive writes. Both registers share the same internal flip-flop for this purpose, and the toggle may be reset by reading <code>PPUSTAT</code> ($2002).
Both <code>$2005</code> and <code>$2006</code> are designed to take two consecutive writes. Both registers share the same internal flip-flop for this purpose, and the toggle may be reset by reading <code>PPUSTAT</code> ($2002).


==Writes to <code>$2005</code>==
=== Writes to PPUSCROLL ===
The first write to <code>$2005</code> controls the horizontal scroll offset. This is done by copying the lower three bits of the value written into <code>Loopy_X</code>, and the upper five bits 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 first write to PPUSCROLL controls the horizontal scroll offset. This is done by copying the lower three bits of the value written into <code>Loopy_X</code>, and the upper five bits 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 <code>$2005</code> controls the vertical scroll offset. The lower three bits of the value written are copied to D14-D12 of <code>Loopy_T</code>, and the upper three bits 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.
The second write to <code>$2005</code> controls the vertical scroll offset. The lower three bits of the value written are copied to D14-D12 of <code>Loopy_T</code>, and the upper three bits 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 <code>$2006</code>==
=== Writes to PPUADDR ===
The first write to <code>$2006</code> 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 first write to <code>$2006</code> 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 <code>$2006</code> 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>. Many commercial games use writes to <code>$2006</code> instead of <code>$2005</code> to handle scrolling, since it is the only way to change the vertical scroll offsets during mid-frame rendering.
The second <code>$2006</code> 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>. Many commercial games use writes to <code>$2006</code> instead of <code>$2005</code> to handle scrolling, since it is the only way to change the vertical scroll offsets during mid-frame rendering.


==Writes to <code>$2000</code>==
===Writes to PPUCTRL ===
Writing to the <code>PPUCTRL</code> register at $2000 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>.
Writing to the <code>PPUCTRL</code> register at $2000 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==
===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 on PPU cycle #304 of the pre-render scanline [http://www.nesdev.org/bbs/viewtopic.php?t=999&highlight=loopyv+loopyt&sid=942bc1830103232074f681d1de931cf9]. Also, before each scanline is rendered (assuming sprites or background are enabled), D10 and D4-D0 of <code>Loopy_T</code> is copied into <code>Loopy_V</code>, since they were altered in <code>Loopy_V</code> during scanline 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 on PPU cycle #304 of the pre-render scanline [http://www.nesdev.org/bbs/viewtopic.php?t=999&highlight=loopyv+loopyt&sid=942bc1830103232074f681d1de931cf9]. Also, before each scanline is rendered (assuming sprites or background are enabled), D10 and D4-D0 of <code>Loopy_T</code> is copied into <code>Loopy_V</code>, since they were altered in <code>Loopy_V</code> during scanline rendering.


==References==
==References==
*[http://nesdev.parodius.com/loopyppu.zip the skinny on NES scrolling (loopy)]
*[http://nesdev.parodius.com/loopyppu.zip the skinny on NES scrolling (loopy)]

Revision as of 19:11, 3 July 2010

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 near the end of vertical blanking, after it has made any updates to VRAM:

  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)

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.

Updating at the "seam"

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.

What the registers do

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 ($2005) and PPUADDR ($2006) affects the same internal 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 Loopy_V, and the latch as Loopy_T, since "V" and "T" 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 {Loopy_X) is also utilized. This takes effect immediately, and controls which bit from the PPU's internal pixel shift registers will be selected.

Both $2005 and $2006 are designed to take two consecutive writes. Both registers share the same internal flip-flop for this purpose, and the toggle may be reset by reading PPUSTAT ($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 into Loopy_X, and the upper five bits into D4-D0 of Loopy_T. The coarse horizontal scroll offset will take effect when the next scanline is rendered, while the fine horizontal scroll offset in Loopy_X takes effect immediately.

The second write to $2005 controls the vertical scroll offset. The lower three bits of the value written are copied to D14-D12 of Loopy_T, and the upper three bits are copied to D9-D5 of Loopy_T. Since these bits are not reloaded into Loopy_V before rendering a scanline, they won't take effect until the next frame.

Writes to PPUADDR

The first write to $2006 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 Loopy_T is cleared, while D13-D8 are loaded with the lower six bits of the value written.

The second $2006 write is copied in its entirety to D7-D0 of Loopy_T. More importantly, Loopy_T is then copied into Loopy_V. Many commercial games use writes to $2006 instead of $2005 to handle scrolling, since it is the only way to change the vertical scroll offsets during mid-frame rendering.

Writes to PPUCTRL

Writing to the PPUCTRL register at $2000 also affects the VRAM address latch. The lower two bits of the value written (used for nametable selection) are copied to D11-D10 of Loopy_T.

Use of Loopy_V during rendering

At the beginning of each frame, the contents of Loopy_T are copied into Loopy_V, as long as background or sprites are enabled. This takes place on PPU cycle #304 of the pre-render scanline [1]. Also, before each scanline is rendered (assuming sprites or background are enabled), D10 and D4-D0 of Loopy_T is copied into Loopy_V, since they were altered in Loopy_V during scanline rendering.

References