User:Bregalad/Scrolling sandbox: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(Early proposal, work in progress, incomplete, please don't comment until more work is being done on this one.)
 
m (Oops.)
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
This is my proposal for a new [[scrolling]] page. Information which is currently on the [[mirroring]] page should be moved here. Because scrolling is complicated, it'll need much time before this is ready however !! Please wait until the merging is complete before giving feedback or constructive critisism.
This is my proposal for a new [[scrolling]] page. Some information which is currently on the [[mirroring]] page should be moved here. On the other hand, complex scrolling techniques are moved to a new [[scroll split]] page. Feedback and constructive critisism is welcome.
 
Sections ''Unidirectional scrolling'' and ''Mirroring chart'' were just moved arround, other sections were significantly altered by myself (Bregalad).


''' !! CURRENTLY THIS PAGE IS A WORK IN PROGRESS !!'''
''' !! CURRENTLY THIS PAGE IS A WORK IN PROGRESS !!'''
= Info to be added from [[mirroring]] page =
Horizontal [[mirroring]] is most commonly used for games which only scroll vertically or in all directions.
Doing any horizontal scrolling using horizontal mirroring is hard to do smoothly because the data on the right of the screen is immediately show on the left due to mirroring. Clever use of hardware left-side screen clipping will hide all name table glitches, but because the attribute tables have a resolution of 2x2 tiles, there will always be attribute glitches on the left and/or the right side of the screen. The best possible way to hide it is to have 4 pixels with potentially wrong attributes on both sides, but most commercial games did worse than that having usually 8 or even more glitchy pixels, so that is why so many NES games have color glitches on the border of the screen.
Some televisions overscan up to 8 pixels on both left and right border, but most doesn't. Perfectionist programmers could use solid black sprites on the right border to hide attribute glitches and make the screen look symmetrical and hide absolutely all attribute glitches, as in the game Alfred Chicken, but very few games do this because it reduces the number of sprites per scanline to 7 and wastes a lot of OAM space (roughly 1/4 in 8x16 pixel sprite mode).
Vertical [[mirroring]] is most commonly used for games which only scroll horizontally. Games that scroll vertically (by any amount and without status bar) and that never scroll horizontally by more than one screen would use this mirroring (e.g. Lode Runner, Bomberman, Fire Emblem, Crystal Mines), so that they don't have to load anything when scrolling horizontally.
Of course it is also used for games which scroll in both directions without a status bar. Because data that is on the top/bottom of the screen will immediately show up on the other side, a clever use of NTSC overscan can make it glitch-less multidirectional scrolling, but glitches will appear on PAL televisions (and NTSC televisions with a overscan range which is a little off). The best possible way to hide glitches is to make 4 pixels with wrong tiles and 4 additional pixels with wrong color on both sides, but most commercial games did much worse than this, that's why they look so bad if overscan is disabled.
Perfectionist programmers could use raster split to hide glitches (and possibly also provide more blanking time to update VRAM) as in the games Jurassic Park and M.C. Kids, but it was rarely done because it complicates the code a lot for little benefits.
Single-screen [[mirroring]]'s main advantage is that it allows using a status bar at the top or bottom of the screen while also allowing the playfield to extend equally in any direction - this can be done by storing the status bar in one nametable, rendering the playfield in the other nametable, and switching mirroring (and scrolling parameters) at the appropriate screen location during rendering.
There is also a lot of other things that can be drastically simplified when using 1-screen mirroring: The formulas used to calculate PPU address of data to be updated to the screen are also significantly simpler, and if the status bar have a variable size or is scrolling, all this would be a headache without 1-screen mirroring.
When this mirroring is used to scroll horizontally, similar glitches and scrolling problems that those of horizontal mirroring will happen. However, as long as there is a status bar, no glitches will happen vertically since the data that falls off the bottom (or the top) of the screen will come in the area that is "hidden" by the status bar, regardless of overscan factors.
L- or X-shaped [[mirroring]] would facilitate changes in scrolling direction without having to flip between Horizontal and Vertical mirroring.
== Mirroring chart ==
This table lists the more simple and easy to understand mirroring and scrolling techniques. There are a huge variety of more complicated techniques. For a more comprehensive survey, see: [[List of games by mirroring technique]]
{| class="wikitable"
! Scrolling Type || Mirroring || Example Games || Comment
|-
| None
| Any
| ''Donkey Kong'', ''Tennis''
| With only a single fixed screen, any mirroring type can be used.
|-
| Horizontal Only
| Vertical
| ''Super Mario Bros.'', ''Gimmick!''
| A [[status bar]] at the top is easy to accomplish with a [[sprite-0 hit]] (see ''Super Mario Bros.'').
|-
| Vertical Only
| Horizontal
| ''Ice Climber'', ''Gun.Smoke''
| Without a status bar, horizontal mirroring is the best choice for vertical-only scrolling. With a status bar, vertical or single-screen mirroring give you a place in the nametable to render the status bar, and the scrolling seam should be hidden under the bar.
|-
| Alternating Horizontal/Vertical
| Mapper switches H/V
| ''Metroid'', ''Air Fortress''
| Motion is limited to a single axis at any given time, and the direction can only change when a new screen is reached.
|-
| Limited Bidirectional
| Horizontal/Vertical
| ''Super Mario Bros. 3'', ''Fire Emblem''
| By limiting one of the scrolling axes to only 2-screens wide, this makes unlimited scrolling in the other axis simple. With unlimited horizontal scrolling there will be unavoidable attribute glitches at one side of the screen (see ''Super Mario Bros. 3''), but with unlimited vertical scrolling this can be hidden by [[overscan]] in NTSC regions (see ''Fire Emblem'').
|-
| Unlimited Bidirectional
| Various
| ''Castlevania II'', ''Battletoads'', ''Crystalis'', ''Final Fantasy''
| Unlimited scrolling in both axes at once is an advanced technique requiring a game-specific solution.
|}
The best way to understand the mirroring techniques used in a game, use a debugging emulator to look at the nametables. [[Status bar|Status bars]] typically require a scrolling split at a timed location on the screen. This can be done most easily with a mapper based [[IRQ]], but can also be accomplished with a [[sprite-0 hit]] or other techniques.
= From current scrolling page =


'''Scrolling''' is the movement of the displayed portion of the map. Games scroll to show an area 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.
'''Scrolling''' is the movement of the displayed portion of the map. Games scroll to show an area 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 ==
== Unidirectional scrolling ==


Ordinarily, a program writes to two [[PPU registers]] to set the scroll position in its NMI handler:
Ordinarily, a program writes to two [[PPU registers]] to set the scroll position in its NMI handler:
Line 74: Line 18:


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 choose appropriate offscreen columns or rows of the nametable, 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, new tile and attribute data decompressed from leve data has to be filled in the nametables, 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]]


=== Frequent pitfalls ===
=== Frequent pitfalls ===
; Don't take too long: If your NMI handler routine takes too long and PPUSCROLL ($2005) is not set before the end of vblank, the scroll will not be correctly applied this frame. Most games do not write more than 64 bytes to the nametable per NMI, more than this may require advanced techniques to fit this narrow window of time.
; Taking too long: If a NMI handler routine takes too long and PPUSCROLL ($2005) is not set before the end of vblank, the scroll will not be correctly applied the following frame. Most games do not write more than 64 bytes to VRAM per NMI, more than this may require on NTSC consoles advanced loop unrolling techniques to fit the narrow window of time offered by the VBlank period.
; Set the scroll last: PPUSCROLL must always be set after using PPUADDR ($2006). They have a shared internal register and using PPUADDR will overwrite the scroll position.
; Set the scroll last: PPUSCROLL ($2005) must always be set after using PPUADDR ($2006). They have a shared internal register and using PPUADDR ($2006) will overwrite the scroll position.


== PPU registers ==
=== PPU registers ===


If you aren't trying to split the screen, scrolling the background is as easy as writing the X and Y coordinates to $2005 and writing the high bit of both coordinates to $2000.
When split scrolling is required, some more advanced usage of $2005 and $2006 registers is needed. The complete information is is on a separate page : [[split scrolling]].
Programming or emulating a game that uses complex raster effects, on the other hand, requires a complete understanding of how the various address registers inside the PPU work.
Here are the related registers:
;v: Current VRAM address (15 bits)
;t: Temporary VRAM address (15 bits); can also be thought of as the address of the top left onscreen tile.
;x: Fine X scroll (3 bits)
;w: First or second write toggle (1 bit)


The PPU uses the current VRAM address for both reading and writing PPU memory thru $2007, and for fetching nametable data to draw the background.
== Multi-directional scrolling techniques ==
As it's drawing the background, it updates the address to point to the nametable data currently being drawn.
Bits 10-11 hold the base address of the nametable minus $2000.
Bits 12-14 are the Y offset of a scanline within a tile.


The 15 bit registers ''t'' and ''v'' are composed this way during rendering:
Scrolling in a direction in which the screen is not mirrored is usually easy, since there is a whole screen of buffer space to update data. On the other hand, dealing with scrolling updates is harder when the axis in the direction it is scrolled to is [[mirrored|mirroring]], because graphics leaving one side of the screen directly enters the other side.
yyy NN YYYYY XXXXX
||| || ||||| +++++-- coarse X scroll
||| || +++++-------- coarse Y scroll
||| ++-------------- nametable select
+++----------------- fine Y scroll


== Register controls ==
The rarely-available L- or X-shaped [[mirroring]] could facilitate changes in scrolling direction on screen-boundaries without having to flip between Horizontal and Vertical mirroring.


In the following, ''d'' refers to the data written to the port, and ''A'' through ''H'' to individual bits of a value.
=== Horiontal scrolling with horizontal [[mirroring]] ===


$2005 and $2006 share a common write toggle, so that the first write has one behaviour, and the second write has another.
Doing any horizontal scrolling using horizontal mirroring is hard to do smoothly because the data on the right of the screen is immediately show on the left due to mirroring. Clever use of hardware left-side screen clipping will hide all name table glitches, but because the attribute tables have a resolution of 2x2 tiles, there will always be attribute glitches on the left and/or the right side of the screen. Some televisions overscan up to 8 pixels on both left and right border, but most doesn't. PAL NESes will automatically clip the left and right 2 pixels. Several solutions exists:
After the second write, the toggle is reset to the first write behaviour. This toggle may be manually reset by reading
$2002.


=== $2000 write ===
* Ignore the problem: Colour glitches are accepted, and are minimized (see below)
t: ...BA.. ........ = d: ......BA
* Work arround the problem: Levels use only a single palette (as implemented by ''Wizard & Warriors II''), or have a level design where the colour scheme repeats itself vertically each screen. This seriously reduces the graphical possibilities.
* Fix the problem: Overlay 8 additional pixels of BG with sprites. This can be either solid-colour sprites designed to hide bachground graphics (as in the game ''Alfred Chicken''), or sprites reusing BG graphics and correct colours instead of the incorrect colour from the attribute table. Very few games do this because it reduces the number of (actual) sprites per scanline to 7 and wastes a lot of OAM space (roughly 1/4 in 8x16 pixel sprite mode).


=== $2002 read ===
If glitches are accepted they appear to at least 7 pixels due to techincal reasons (assuming left-clip is enabled via $2001). Several strategies exist however to make them less blatant:
w:                 = 0


=== $2005 first write (''w'' is 0) ===
* Make the various BG palettes use the same luminosity pattern and only changing the hues, that way the area will be wrong colored will at least have correct luminosity, and as such, look less wrong.
t: ....... ...HGFED = d: HGFED...
* Make the glitches in the direction the player is not facing (as seen in ''Kirby's Adventure'').
x:              CBA = d: .....CBA
* Share the glitches on both borders, so the total area of graphics showing with the wrong colour will be minimized. For example 4 pixels on the right side and 3 pixels on the left side. On PAL consoles, this will reduce to 2 and 3 respectively.
w:                  = 1


=== $2005 second write (''w'' is 1) ===
Games with such glitches typically does a poor job at minimizing the amount of glitches, so they give a bad impression as to what is actually possible.
t: CBA..HG FED..... = d: HGFEDCBA
w:                  = 0


=== $2006 first write (''w'' is 0) ===
This diagram shows how the glitches can be minimized for a 16-pixel horizontal scroll cycle, each line representing a different fine horizontal scroll value.
t: .FEDCBA ........ = d: ..FEDCBA
t: X...... ........ = 0
w:                  = 1


=== $2006 second write (''w'' is 1) ===
<nowiki>Fine
  t: ....... HGFEDCBA = d: HGFEDCBA
HScroll
  v                  = t
  0  --------|------------------------
  w:                  = 0
  1  #-------|------------------------
  2  ##------|------------------------
  3  ###-----|------------------------
  4  ####----|------------------------
  5  #####---|------------------------
  6  ######--|------------------------
  7  #######-|------------------------
  8  ~~~~~~~~|------------------------  (NT update)
  9  #~~~~~~~|~-----------------------
10  ##~~~~~~|~~----------------------
11  ###~~~~~|~~~---------------------
12  xxxx----|--------------------~~~~  (AT update)
  13  xxxxx---|---------------------~~~
  14  xxxxxx--|----------------------~~
  15  xxxxxxx-|-----------------------~
  0   --------|------------------------  (NT update)
      ^^^^^^^^                      ^^--- Hidden on PAL machines
      Hidden part (by hardware through $2001)
 
- Correct tile displayed with correct colorus
~ Correct tile displayed with wrong colours
x Wrong tile displayed with correct colours
# Wrong tile displayed with wrong colours</nowiki>


=== At dot 256 of each scanline ===
=== Vertical scrolling with vertical [[mirroring]] ===
::If rendering is enabled, the PPU increments the vertical position in ''v''. The effective Y scroll coordinate is incremented, which is a complex operation that will correctly skip the attribute table memory regions, and wrap to the next nametable appropriately. See [[#Wrapping around|Wrapping around]] below.


=== At dot 257 of each scanline ===
Because data that is on the top/bottom of the screen will immediately show up on the other side, a clever use of NTSC [[overscan]] can make it glitch-less multidirectional scrolling, but glitches will appear on PAL televisions (and NTSC televisions with a overscan range which is a little off). Because the vast majority of commercial games were developped in NTSC countries, they tend to consider the vertical overscan as a safe buffer zone for scroll updates.
::If rendering is enabled, the PPU copies all bits related to horizontal position from ''t'' to ''v'':
v: ....F.. ...EDCBA = t: ....F.. ...EDCBA


=== During dots 280 to 304 of the pre-render scanline (end of vblank) ===
However, as with the case of horizontal scrolling with horizontal mirroring, it is desirable to minimize the unavoidable glitches. The best possible way to hide glitches is to make 4 pixels with wrong tiles and 4 additional pixels with wrong color on both sides. Games with such glitches typically does a poor job at minimizing the amount of glitches, so they give a bad impression as to what is actually possible.
::If rendering is enabled, at the end of vblank, shortly after the horizontal bits are copied from ''t'' to ''v'' at dot 257, the PPU will repeatedly copy the vertical bits from ''t'' to ''v'' from dots 280 to 304, completing the full initialization of ''v'' from ''t'':
v: IHGF.ED CBA..... = t: IHGF.ED CBA.....


=== Between dot 328 of a scanline, and 256 of the next scanline ===
Perfectionist programmers could use raster split to hide glitches, and possibly also provide more blanking time to update VRAM as well as hiding the unavoidable sprite poping in the top border. This is seen in the games ''Jurassic Park'' and ''M.C. Kids'', but it was rarely done because it complicates the code a lot for little benefits.
::If rendering is enabled, the PPU increments the horizontal position in ''v'' many times across the scanline, it begins at dots 328 and 336, and will continue through the next scanline at 8, 16, 24... 240, 248, 256 (every 8 dots across the scanline until 256). The effective X scroll coordinate is incremented, which will wrap to the next nametable appropriately. See [[#Wrapping around|Wrapping around]] below.


=== $2007 reads and writes ===
This diagram shows how the glitches can be minimized for a 16-pixel vertical scroll cycle, each column representing a different fine vertical scroll value.
::Outside of rendering, reads from or writes to $2007 will add either 1 or 32 to ''v'' depending on the VRAM increment bit set via $2000. During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled), it will update ''v'' in an odd way, triggering a [[#Coarse X increment|coarse X increment]] and a [[#Y increment|Y increment]] simultaneously (with normal wrapping behavior). Internally, this is caused by the carry inputs to various sections of ''v'' being set up for rendering, and the $2007 access triggering a "load next value" signal for '''all''' of ''v'' (when not rendering, the carry inputs are set up to linearly increment ''v'' by either 1 or 32). This behavior is not affected by the status of the increment bit. The Young Indiana Jones Chronicles uses this for some effects to adjust the Y scroll during rendering, and also Burai Fighter (U) to draw the scorebar. If the $2007 access happens to coincide with a standard VRAM address increment (either horizontal or vertical), it will presumably ''not'' double-increment the relevant counter.


=== Explanation ===
<nowiki>Fine
VScroll 0123456789ABCDEF0
        -###~~~~-xxx----- < Hidden by
        --##~~~~--xx----- < NTSC average
        ---#~~~~---x----- < Overscan
        ----~~~~--------- <
        -----~~~--------- <
        ------~~--------- <
        -------~--------- <
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        ----------------- <
        --------~-------- <
        --------~~------- <
        --------~~~------ <
        ----x---~~~~#---- <
        ----xx--~~~~##--- < Hidden by
        ----xxx-~~~~###-- < NTSC average
        ----xxxx~~~~####- < Overscan
    ^(NT update)
        ^(AT update)
    ^(NT update)


* The implementation of scrolling has two components. There are two fine offsets, specifying what part of an 8x8 tile each pixel falls on, and two coarse offsets, specifying which tile. Because each tile corresponds to a single byte addressable by the PPU, during rendering the coarse offsets reuse the same VRAM address register (''v'') that is normally used to send and receive data from the PPU. Because of this reuse, the two registers $2005 and $2006 both offer control over ''v'', but $2005 is mapped in a more obscure way, designed specifically to be convenient for scrolling.
  - Correct tile displayed with correct colorus
* $2006 is simply to set the VRAM address register. This is why the second write will immediately set ''v''; it is expected you will immediately use this address to send data to the PPU via $2007. The PPU memory space is only 14 bits wide, but ''v'' has an extra bit that is used for scrolling only. The first write to $2006 will clear this extra bit (for reasons not known).
  ~ Correct tile displayed with wrong colours
* $2005 is designed to set the scroll position before the start of the frame. This is why it does not immediately set ''v'', so that it can be set at precisely the right time to start rendering the screen.
  x Wrong tile displayed with correct colours
* The high 5 bits of the X and Y scroll settings sent to $2005, when combined with the 2 nametable select bits sent to $2000, make a 12 bit address for the next tile to be fetched within the nametable address space $2000-2FFF. If set before the end of vblank, this 12 bit address gets loaded directly into ''v'' precisely when it is needed to fetch the tile for the top left pixel to render.
  # Wrong tile displayed with wrong colours</nowiki>
* The low 3 bits of X sent to $2005 (first write) control the fine pixel offset within the 8x8 tile. The low 3 bits goes into the separate ''x'' register, which just selects one of 8 pixels coming out of a set of shift registers. This fine X value does not change during rendering; the only thing that changes it is a $2005 first write.
* The low 3 bits of Y sent to $2005 (second write) control the vertical pixel offset within the 8x8 tile. The low 3 bits goes into the high 3 bits of the ''v'' register, where during rendering they are not used as part of the PPU memory address (which is being overridden to use the nametable space $2000-2FFF). Instead they count the lines until the coarse Y memory address needs to be incremented (and wrapped appropriately when nametable boundaries are crossed).


See also: [[PPU rendering#Frame timing diagram|PPU Frame timing]]
=== Status bars and single-screen mirroring ===


=== Summary ===
Single-screen [[mirroring]]'s main advantage is that it allows using a status bar at the top or bottom of the screen while also allowing the playfield to extend equally in any direction. Typically the status bar is stored in one nametable and the playfield in the other. Scrolling parameters and nametables are switched the appropriate screen location during rendering.
The following diagram illustrates how several different actions may update the various internal registers related to scrolling. See [[#Examples|Examples]] below for usage examples.


{| class="wikitable"
Due to the screen layout being entierely linearly adressable, the calculation of PPU addresses of name and attribute tables updates are significantly simpler, leading to typically faster, more efficient scrolling routines. A variable sized or self-scrolling status bar can relatively easily be created thanks to 1-screen mirroring.
!rowspan="2" | Action
!colspan="4" style="background-color:palegreen" | Before
!rowspan="2" | Instructions
!colspan="4" style="background-color:lightcoral" | After
!rowspan="2" | Notes
|-
!style="background-color:palegreen" | t
!style="background-color:palegreen" | v
!style="background-color:palegreen" | x
!style="background-color:palegreen" | w
!style="background-color:lightcoral" | t
!style="background-color:lightcoral" | v
!style="background-color:lightcoral" | x
!style="background-color:lightcoral" | w
|-
! $2000 write
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | .
|style="font-family:monospace; white-space:nowrap;" | LDA #$00 (%000000<span style="background-color:lime">00</span>)<br />STA $2000
|style="font-family:monospace; white-space:nowrap;" | ...<span style="background-color:lime">00</span>.. ........
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | .
|
|-
! $2002 read
|style="font-family:monospace; white-space:nowrap;" | ...00.. ........
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | .
|style="font-family:monospace; white-space:nowrap;" | LDA $2002
|style="font-family:monospace; white-space:nowrap;" | ...00.. ........
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">0</span>
| Resets paired write latch ''w'' to 0.
|-
! $2005 write 1
|style="font-family:monospace; white-space:nowrap;" | ...00.. ........
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | 0
|style="font-family:monospace; white-space:nowrap;" | LDA #$7D (%<span style="background-color:lime">01111</span><span style="background-color:yellow">101</span>)<br />STA $2005
|style="font-family:monospace; white-space:nowrap;" | ...00.. ...<span style="background-color:lime">01111</span>
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:yellow">101</span>
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">1</span>
|
|-
! $2005 write 2
|style="font-family:monospace; white-space:nowrap;" | ...00.. ...01111
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | 1
|style="font-family:monospace; white-space:nowrap;" | LDA #$5E (%<span style="background-color:lime">01</span><span style="background-color:yellow">011</span><span style="background-color:cyan">110</span>)<br />STA $2005
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:cyan">110</span>00<span style="background-color:lime">01</span> <span style="background-color:yellow">011</span>01111
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">0</span>
|
|-
! $2006 write 1
|style="font-family:monospace; white-space:nowrap;" | 1100001 01101111
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | 0
|style="font-family:monospace; white-space:nowrap;" | LDA #$3D (%00<span style="background-color:lime">111101</span>)<br />STA $2006
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:lightcoral">0</span><span style="background-color:lime">111101</span> 01101111
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">1</span>
|Bit 14 (15th bit) of ''t'' gets set to zero
|-
! $2006 write 2
|style="font-family:monospace; white-space:nowrap;" | 0111101 01101111
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | 1
|style="font-family:monospace; white-space:nowrap;" | LDA #$F0 (%<span style="background-color:lime">11110000</span>)<br />STA $2006
|style="font-family:monospace; white-space:nowrap;" | 0111101 <span style="background-color:lime">11110000</span>
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:yellow">0111101</span> <span style="background-color:yellow">11110000</span>
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">0</span>
|After ''t'' is updated, contents of ''t'' copied into ''v''
|}


== Wrapping around ==
The same graphical glitches as discussed above under horizontal mirroring will happen. However, as long as there is a status bar, glitches are fully avoidable vertically since the data that falls off the bottom (or the top) of the screen will come in the area that is "hidden" by the status bar, regardless of overscan factors.


The following pseudocode examples explain how wrapping is performed when incrementing components of ''v''. This code is written for clarity, and is not optimized for speed.
== Mirroring chart ==
 
This table lists the most common mirroring and scrolling techniques. There are a huge variety of more complicated techniques. For a more comprehensive survey, see: [[List of games by mirroring technique]]
=== Coarse X increment ===
 
The coarse X component of ''v'' needs to be incremented when the next tile is reached.
Bits 0-4 are incremented, with overflow toggling bit 10. This means that
bits 0-4 count from 0 to 31 across a single nametable, and bit 10 selects the current nametable horizontally.
 
if ((''v'' & 0x001F) == 31) // if coarse X == 31
  ''v'' &= ~0x001F          // coarse X = 0
  ''v'' ^= 0x0400          // switch horizontal nametable
else
  ''v'' += 1                // increment coarse X
 
=== Y increment ===
 
If rendering is enabled, fine Y is incremented at dot 256 of each scanline,
overflowing to coarse Y,
and finally adjusted to wrap among the nametables vertically.
 
Bits 12-14 are fine Y. Bits 5-9 are coarse Y. Bit 11 selects the vertical nametable.
 
if ((''v'' & 0x7000) != 0x7000)        // if fine Y < 7
  ''v'' += 0x1000                      // increment fine Y
else
  ''v'' &= ~0x7000                    // fine Y = 0
  int ''y'' = (''v'' & 0x03E0) >> 5        // let ''y'' = coarse Y
  if (''y'' == 29)
    ''y'' = 0                          // coarse Y = 0
    ''v'' ^= 0x0800                    // switch vertical nametable
  else if (''y'' == 31)
    ''y'' = 0                          // coarse Y = 0, nametable not switched
  else
    ''y'' += 1                        // increment coarse Y
  ''v'' = (v & ~0x03E0) | (''y'' << 5)    // put coarse Y back into ''v''
 
Row 29 is the last row of tiles in a nametable. To wrap to the next nametable when incrementing coarse Y from 29, the vertical nametable is switched by toggling bit 11, and coarse Y wraps to row 0.
 
Coarse Y can be set out of bounds (> 29), which will cause the PPU to read the attribute data stored there as tile data. If coarse Y is incremented from 31, it will wrap to 0, but the nametable will not switch. For this reason, a write >= 240 to $2005 may appear as a "negative" scroll value, where 1 or 2 rows of attribute data will appear before the nametable's tile data is reached. (Some games use this to move the top of the nametable out of the [[Overscan]] area.)
 
=== Tile and attribute fetching ===
 
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:
 
  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:
 
  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
 
== Examples ==
 
=== Single scroll ===
 
If only one scroll setting is needed for the entire screen, this can be done by writing $2000 once, and $2005 twice before the end of vblank.
 
# The low two bits of $2000 select which of the four nametables to use.
# The first write to $2005 specifies the X scroll, in pixels.
# The second write to $2005 specifies the Y scroll, in pixels.
 
This should be done after writes to $2006 are completed, because they overwrite the ''t'' register. The ''v'' register will be completely copied from ''t'' at the end of vblank, setting the scroll.
 
Note that the series of two writes to $2005 presumes the toggle that specifies which write is taking place. If the state of the toggle is unknown, reset it by reading from $2002 before the first write to $2005.
 
Instead of writing $2000, the first write to $2006 can be used to select the nametable, if this happens to be more convenient (usually it is not because it will toggle ''w'').
 
=== Split X scroll ===
 
The X scroll can be changed at the end of any scanline when the horizontal components of ''v'' get reloaded from ''t'': Simply make writes to $2000/$2005 before the end of the line.
 
# The first write to $2005 alters the horizontal scroll position. The fine ''x'' register (sub-tile offset) gets updated immediately, but the coarse horizontal component of ''t'' (tile offset) does not get updated until the end of the line.
# An optional second write to $2005 is inconsequential; the changes it makes to ''t'' will be ignored at the end of the line. However, it will reset the write toggle ''w'' for any subsequent splits.
# Write to $2000 if needed to set the high bit of X scroll, which is controlled by bit 0 of the value written. Writing $2000 changes [[PPUCTRL|other rendering properties]] as well, so make sure the other bits are set appropriately.
 
Like the single scroll example, reset the toggle by reading $2002 if it is in an unknown state. Since a write to $2005 and a read from $2002 are equally expensive in both bytes and time, whether you use one or the other to prepare for subsequent screen splits is up to you.
 
The first write to $2005 should usually be made as close to the end of the line as possible, but before the start of hblank when the coarse x scroll is copied from ''t'' to ''v''. Because about 4 pixels of timing jitter are normally unavoidable, $2005 should be written a little bit early (once hblank begins, it is too late). The resulting glitch at the end of the line can be concealed by a line of one colour pixels, or a sprite. To eliminate the glitch altogether, the following more advanced X/Y scroll technique could be used to update ''v'' during hblank instead.
 
=== Split X/Y scroll ===
 
Cleanly setting the complete scroll position (X and Y) mid-screen takes four writes:
# Nametable number << 2 (that is: $00, $04, $08, or $0C) to $2006
# Y to $2005
# X to $2005
# Low byte of nametable address to $2006, which is ((Y & $F8) << 2) | (X >> 3)
 
The last two writes should occur during horizontal blanking to avoid visual errors.
 
==== Details ====
 
 
To split both the X and Y scroll on a scanline, we must perform four writes to $2006 and $2005 alternately in order to completely reload ''v''. Without the second write to $2006, only the horizontal portion of ''v'' will loaded from ''t'' at the end of the scanline. By writing twice to $2006, the second write causes an immediate full reload of ''v'' from ''t'', allowing you to update the vertical scroll in the middle of the screen.
 
The writes to PPU registers are done in the order of $2006, $2005, $2005, $2006. This order of writes is important, understanding that the write toggle for $2005 is shared with $2006. As always, if the state of the toggle is unknown before beginning, read $2002 to reset it.
 
In this example we will perform two writes to each of $2005 and $2006. We will set the X scroll (X), Y scroll (Y), and nametable select (N) by writes to $2005 and $2006. This diagram shows where each value fits into the four register writes.
 
N: %01
X: %01111101 = $7D
Y: %00111110 = $3E
 
$2005.1 = X                                                          = %01111101 = $7D
$2005.2 = Y                                                          = %00111110 = $3E
$2006.1 = ((Y & %11000000) >> 6) | ((Y & %00000011) << 4) | (N << 2) = %00010100 = $14
$2006.2 = ((X & %11111000) >> 3) | ((Y & %00111000) << 2)            = %11101111 = $EF
 
However, since there is a great deal of overlap between the data sent to $2005 and $2006, only the last write to any particular bit of ''t'' matters. This makes the first write to $2006 mostly redundant, and we can simplify its setup significantly:
 
$2006.1 = N << 2                                                    = %00000100 = $04
 
There are other redundancies in the writes to $2005, but since it is likely the original X and Y values are already available, these can be left as an exercise for the reader.


{| class="wikitable"
{| class="wikitable"
!colspan="4" style="background-color:palegreen" | Before
! Scrolling Type || Mirroring || Example Games || Comment
!rowspan="2" | Instructions
|-
!colspan="4" style="background-color:lightcoral" | After
| None
!rowspan="2" | Notes
| Any
| ''Donkey Kong'', ''Tennis''
| With only a single fixed screen, any mirroring type can be used.
|-
|-
!style="background-color:palegreen" | t
| Horizontal Only
!style="background-color:palegreen" | v
| Vertical
!style="background-color:palegreen" | x
| ''Super Mario Bros.'', ''Gimmick!''
!style="background-color:palegreen" | w
| A [[status bar]] at the top is easy to accomplish with a [[sprite-0 hit]] (see ''Super Mario Bros.'').
!style="background-color:lightcoral" | t
!style="background-color:lightcoral" | v
!style="background-color:lightcoral" | x
!style="background-color:lightcoral" | w
|-
|-
|style="font-family:monospace; white-space:nowrap;" | ....... ........
| Vertical Only
|style="font-family:monospace; white-space:nowrap;" | ....... ........
| Horizontal
|style="font-family:monospace; white-space:nowrap;" | ...
| ''Ice Climber'', ''Gun.Smoke''
|style="font-family:monospace; white-space:nowrap;" | 0
| Without a status bar, horizontal mirroring is the best choice for vertical-only scrolling. With a status bar, vertical or single-screen mirroring gives a place in the nametable to render the status bar, and the scrolling seam should be hidden under the bar.
|style="font-family:monospace; white-space:nowrap;" | LDA #$04 (%00<span style="background-color:lime">000100</span>)<br />STA $2006
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:lightcoral">0</span><span style="background-color:lime">000100</span> ........
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">1</span>
| Bit 14 of ''t'' set to zero
|-
|-
|style="font-family:monospace; white-space:nowrap;" | 0000100 ........
| Alternating Horizontal/Vertical
|style="font-family:monospace; white-space:nowrap;" | ....... ........
| Mapper switches H/V
|style="font-family:monospace; white-space:nowrap;" | ...
| ''Metroid'', ''Air Fortress''
|style="font-family:monospace; white-space:nowrap;" | 1
| Motion is limited to a single axis at any given time, and the direction can only change when a new screen is reached.
|style="font-family:monospace; white-space:nowrap;" | LDA #$3E (%<span style="background-color:lime">00</span><span style="background-color:yellow">111</span><span style="background-color:cyan">110</span>)<br />STA $2005
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:cyan">110</span>01<span style="background-color:lime">00</span> <span style="background-color:yellow">111</span>.....
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">0</span>
| Behaviour of 2nd $2005 write
|-
|-
|style="font-family:monospace; white-space:nowrap;" | 1100100 111.....
| Limited Bidirectional
|style="font-family:monospace; white-space:nowrap;" | ....... ........
| Horizontal/Vertical
|style="font-family:monospace; white-space:nowrap;" | ...
| ''Super Mario Bros. 3'', ''Fire Emblem''
|style="font-family:monospace; white-space:nowrap;" | 0
| By limiting one of the scrolling axes to only 2-screens wide, this makes unlimited scrolling in the other axis simpler. With unlimited horizontal scrolling there will be unavoidable attribute glitches at one side of the screen (see ''Super Mario Bros. 3''), but with unlimited vertical scrolling this can be hidden by [[overscan]] in NTSC regions (see ''Fire Emblem'').
|style="font-family:monospace; white-space:nowrap;" | LDA #$7D (%<span style="background-color:lime">01111</span><span style="background-color:yellow">101</span>)<br />STA $2005
|style="font-family:monospace; white-space:nowrap;" | 1100100 111<span style="background-color:lime">01111</span>
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:yellow">101</span>
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">1</span>
| Behaviour of 1st $2005 write
|-
|-
|style="font-family:monospace; white-space:nowrap;" | 1100100 11101111
| Unlimited Bidirectional
|style="font-family:monospace; white-space:nowrap;" | ....... ........
| Various
|style="font-family:monospace; white-space:nowrap;" | 101
| ''Castlevania II'', ''Battletoads'', ''Crystalis'', ''Final Fantasy''
|style="font-family:monospace; white-space:nowrap;" | 1
| Unlimited scrolling in both axes at once is an advanced technique requiring a game-specific solution.
|style="font-family:monospace; white-space:nowrap;" | LDA #$EF (%<span style="background-color:lime">11101111</span>)<br />STA $2006
|style="font-family:monospace; white-space:nowrap;" | 1100100 <span style="background-color:lime">11101111</span>
|style="font-family:monospace; white-space:nowrap;" | 1100100 11101111
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:bisque">0</span>
|After ''t'' is updated, contents of ''t'' copied into ''v''
|}
|}


Timing for this series of writes is important. Because the Y scroll in ''v'' will be incremented at dot 256, you must either set it to the intended Y-1 before dot 256, or set it to Y after dot 256. Many games that use split scrolling have a visible glitch at the end of the line by timing it early like this.
The best way to understand the mirroring techniques used in a game, use a debugging emulator to look at the nametables. [[Status bar|Status bars]] typically require a scrolling split at a timed location on the screen. This can be done most easily with a mapper based [[IRQ]], but can also be accomplished with a [[sprite-0 hit]] or other techniques.
 
Alternatively you can set the intended Y after dot 256. The last two writes ($2005.1 / $2006.2) can be timed to fall within hblank to avoid any visible glitch. Hblank begins after dot 256, and ends at dot 320 when the first tile of the next line is fetched.
 
Because this method sets ''v'' immediately, it can be used to set the scroll in the middle of the line. This is not normally recommended, as the difficulty of exact timing and interaction of tile fetches makes it difficult to do cleanly.
 
=== Quick coarse X/Y split ===
 
Since it is the write to $2006 when ''w''=1 that transfers the contents of ''t'' to ''v'', it is not strictly necessary to perform all 4 writes as above, so long as one is willing to accept some trade-offs.
 
For example, if you only write to $2006 twice, you can update coarse X, coarse Y, N, and the bottom 2 bits of fine y. The top bit of fine y is cleared, and fine x is unchanged.
 
$2006's contents are in the same order as ''t'', so you can affect the bits as:
    First      Second
/¯¯¯¯¯¯¯¯¯\ /¯¯¯¯¯¯¯\
0 0yy NN YY YYY XXXXX
  ||| || || ||| +++++-- coarse X scroll
  ||| || ++-+++-------- coarse Y scroll
  ||| ++--------------- nametable select
  +++------------------ fine Y scroll
 
<!-- === Render restart with x scroll ===
!!! This has been commented out because the carry-over behavior on t hasn't been verified !!!
 
One could also write to $2005 and then $2006, which would allow you to set fine x, coarse X, and the bottom 3 bits of coarse Y. Fine y, N, and the top 2 bits of coarse Y would be unchanged from the last time ''t'' was written to—possibly at the start of rendering at the top of the screen.
 
{| class="wikitable"
!colspan="3" style="background-color:palegreen" | Before
!rowspan="2" | Instructions
!colspan="3" style="background-color:lightcoral" | After
!rowspan="2" | Notes
|-
!style="background-color:palegreen" | t
!style="background-color:palegreen" | v
!style="background-color:palegreen" | x
!style="background-color:lightcoral" | t
!style="background-color:lightcoral" | v
!style="background-color:lightcoral" | x
|-
|style="font-family:monospace; white-space:nowrap;" | 1100100 000.....
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | ...
|style="font-family:monospace; white-space:nowrap;" | LDA #$05 (%<span style="background-color:lime">00000</span><span style="background-color:yellow">101</span>)<br />STA $2005
|style="font-family:monospace; white-space:nowrap;" | 1100100 000<span style="background-color:lime">00000</span>
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | <span style="background-color:yellow">101</span>
| Behaviour of 1st $2005 write
|-
|style="font-family:monospace; white-space:nowrap;" | 1100100 00000000
|style="font-family:monospace; white-space:nowrap;" | ....... ........
|style="font-family:monospace; white-space:nowrap;" | 101
|style="font-family:monospace; white-space:nowrap;" | LDA #$EF (%<span style="background-color:lime">11101111</span>)<br />STA $2006
|style="font-family:monospace; white-space:nowrap;" | 1100100 <span style="background-color:lime">11101111</span>
|style="font-family:monospace; white-space:nowrap;" | 1100100 11101111
|style="font-family:monospace; white-space:nowrap;" | 101
|After ''t'' is updated, contents of ''t'' copied into ''v''
|}
-->
== References ==
* [http://nesdev.org/loopyppu.zip The skinny on NES scrolling] original document by loopy, 1999-04-13
* [http://forums.nesdev.org/viewtopic.php?p=78593#p78593 Drag's X/Y scrolling example] from the forums
* [[:File:Vramaddr.jpg|VRAM address register]] chip photograph analysis by Quietust

Latest revision as of 14:49, 17 April 2017

This is my proposal for a new scrolling page. Some information which is currently on the mirroring page should be moved here. On the other hand, complex scrolling techniques are moved to a new scroll split page. Feedback and constructive critisism is welcome.

Sections Unidirectional scrolling and Mirroring chart were just moved arround, other sections were significantly altered by myself (Bregalad).

 !! CURRENTLY THIS PAGE IS A WORK IN PROGRESS !!

Scrolling is the movement of the displayed portion of the map. Games scroll to show an area 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.

Unidirectional scrolling

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, new tile and attribute data decompressed from leve data has to be filled in the nametables, 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

Frequent pitfalls

Taking too long
If a NMI handler routine takes too long and PPUSCROLL ($2005) is not set before the end of vblank, the scroll will not be correctly applied the following frame. Most games do not write more than 64 bytes to VRAM per NMI, more than this may require on NTSC consoles advanced loop unrolling techniques to fit the narrow window of time offered by the VBlank period.
Set the scroll last
PPUSCROLL ($2005) must always be set after using PPUADDR ($2006). They have a shared internal register and using PPUADDR ($2006) will overwrite the scroll position.

PPU registers

When split scrolling is required, some more advanced usage of $2005 and $2006 registers is needed. The complete information is is on a separate page : split scrolling.

Multi-directional scrolling techniques

Scrolling in a direction in which the screen is not mirrored is usually easy, since there is a whole screen of buffer space to update data. On the other hand, dealing with scrolling updates is harder when the axis in the direction it is scrolled to is mirroring, because graphics leaving one side of the screen directly enters the other side.

The rarely-available L- or X-shaped mirroring could facilitate changes in scrolling direction on screen-boundaries without having to flip between Horizontal and Vertical mirroring.

Horiontal scrolling with horizontal mirroring

Doing any horizontal scrolling using horizontal mirroring is hard to do smoothly because the data on the right of the screen is immediately show on the left due to mirroring. Clever use of hardware left-side screen clipping will hide all name table glitches, but because the attribute tables have a resolution of 2x2 tiles, there will always be attribute glitches on the left and/or the right side of the screen. Some televisions overscan up to 8 pixels on both left and right border, but most doesn't. PAL NESes will automatically clip the left and right 2 pixels. Several solutions exists:

  • Ignore the problem: Colour glitches are accepted, and are minimized (see below)
  • Work arround the problem: Levels use only a single palette (as implemented by Wizard & Warriors II), or have a level design where the colour scheme repeats itself vertically each screen. This seriously reduces the graphical possibilities.
  • Fix the problem: Overlay 8 additional pixels of BG with sprites. This can be either solid-colour sprites designed to hide bachground graphics (as in the game Alfred Chicken), or sprites reusing BG graphics and correct colours instead of the incorrect colour from the attribute table. Very few games do this because it reduces the number of (actual) sprites per scanline to 7 and wastes a lot of OAM space (roughly 1/4 in 8x16 pixel sprite mode).

If glitches are accepted they appear to at least 7 pixels due to techincal reasons (assuming left-clip is enabled via $2001). Several strategies exist however to make them less blatant:

  • Make the various BG palettes use the same luminosity pattern and only changing the hues, that way the area will be wrong colored will at least have correct luminosity, and as such, look less wrong.
  • Make the glitches in the direction the player is not facing (as seen in Kirby's Adventure).
  • Share the glitches on both borders, so the total area of graphics showing with the wrong colour will be minimized. For example 4 pixels on the right side and 3 pixels on the left side. On PAL consoles, this will reduce to 2 and 3 respectively.

Games with such glitches typically does a poor job at minimizing the amount of glitches, so they give a bad impression as to what is actually possible.

This diagram shows how the glitches can be minimized for a 16-pixel horizontal scroll cycle, each line representing a different fine horizontal scroll value.

Fine
 HScroll
  0   --------|------------------------
  1   #-------|------------------------
  2   ##------|------------------------
  3   ###-----|------------------------
  4   ####----|------------------------
  5   #####---|------------------------
  6   ######--|------------------------
  7   #######-|------------------------
  8   ~~~~~~~~|------------------------  (NT update)
  9   #~~~~~~~|~-----------------------
 10   ##~~~~~~|~~----------------------
 11   ###~~~~~|~~~---------------------
 12   xxxx----|--------------------~~~~  (AT update)
 13   xxxxx---|---------------------~~~
 14   xxxxxx--|----------------------~~
 15   xxxxxxx-|-----------------------~
  0   --------|------------------------  (NT update)
      ^^^^^^^^                       ^^--- Hidden on PAL machines
      Hidden part (by hardware through $2001)
  
 - Correct tile displayed with correct colorus
 ~ Correct tile displayed with wrong colours
 x Wrong tile displayed with correct colours
 # Wrong tile displayed with wrong colours

Vertical scrolling with vertical mirroring

Because data that is on the top/bottom of the screen will immediately show up on the other side, a clever use of NTSC overscan can make it glitch-less multidirectional scrolling, but glitches will appear on PAL televisions (and NTSC televisions with a overscan range which is a little off). Because the vast majority of commercial games were developped in NTSC countries, they tend to consider the vertical overscan as a safe buffer zone for scroll updates.

However, as with the case of horizontal scrolling with horizontal mirroring, it is desirable to minimize the unavoidable glitches. The best possible way to hide glitches is to make 4 pixels with wrong tiles and 4 additional pixels with wrong color on both sides. Games with such glitches typically does a poor job at minimizing the amount of glitches, so they give a bad impression as to what is actually possible.

Perfectionist programmers could use raster split to hide glitches, and possibly also provide more blanking time to update VRAM as well as hiding the unavoidable sprite poping in the top border. This is seen in the games Jurassic Park and M.C. Kids, but it was rarely done because it complicates the code a lot for little benefits.

This diagram shows how the glitches can be minimized for a 16-pixel vertical scroll cycle, each column representing a different fine vertical scroll value.

Fine
 VScroll 0123456789ABCDEF0
        -###~~~~-xxx----- < Hidden by
        --##~~~~--xx----- < NTSC average
        ---#~~~~---x----- < Overscan
        ----~~~~--------- <
        -----~~~--------- <
        ------~~--------- <
        -------~--------- <
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        -----------------
        ----------------- <
        --------~-------- <
        --------~~------- <
        --------~~~------ <
        ----x---~~~~#---- <
        ----xx--~~~~##--- < Hidden by
        ----xxx-~~~~###-- < NTSC average
        ----xxxx~~~~####- < Overscan
	    ^(NT update)
	        ^(AT update)
		    ^(NT update)

  - Correct tile displayed with correct colorus
  ~ Correct tile displayed with wrong colours
  x Wrong tile displayed with correct colours
  # Wrong tile displayed with wrong colours

Status bars and single-screen mirroring

Single-screen mirroring's main advantage is that it allows using a status bar at the top or bottom of the screen while also allowing the playfield to extend equally in any direction. Typically the status bar is stored in one nametable and the playfield in the other. Scrolling parameters and nametables are switched the appropriate screen location during rendering.

Due to the screen layout being entierely linearly adressable, the calculation of PPU addresses of name and attribute tables updates are significantly simpler, leading to typically faster, more efficient scrolling routines. A variable sized or self-scrolling status bar can relatively easily be created thanks to 1-screen mirroring.

The same graphical glitches as discussed above under horizontal mirroring will happen. However, as long as there is a status bar, glitches are fully avoidable vertically since the data that falls off the bottom (or the top) of the screen will come in the area that is "hidden" by the status bar, regardless of overscan factors.

Mirroring chart

This table lists the most common mirroring and scrolling techniques. There are a huge variety of more complicated techniques. For a more comprehensive survey, see: List of games by mirroring technique

Scrolling Type Mirroring Example Games Comment
None Any Donkey Kong, Tennis With only a single fixed screen, any mirroring type can be used.
Horizontal Only Vertical Super Mario Bros., Gimmick! A status bar at the top is easy to accomplish with a sprite-0 hit (see Super Mario Bros.).
Vertical Only Horizontal Ice Climber, Gun.Smoke Without a status bar, horizontal mirroring is the best choice for vertical-only scrolling. With a status bar, vertical or single-screen mirroring gives a place in the nametable to render the status bar, and the scrolling seam should be hidden under the bar.
Alternating Horizontal/Vertical Mapper switches H/V Metroid, Air Fortress Motion is limited to a single axis at any given time, and the direction can only change when a new screen is reached.
Limited Bidirectional Horizontal/Vertical Super Mario Bros. 3, Fire Emblem By limiting one of the scrolling axes to only 2-screens wide, this makes unlimited scrolling in the other axis simpler. With unlimited horizontal scrolling there will be unavoidable attribute glitches at one side of the screen (see Super Mario Bros. 3), but with unlimited vertical scrolling this can be hidden by overscan in NTSC regions (see Fire Emblem).
Unlimited Bidirectional Various Castlevania II, Battletoads, Crystalis, Final Fantasy Unlimited scrolling in both axes at once is an advanced technique requiring a game-specific solution.

The best way to understand the mirroring techniques used in a game, use a debugging emulator to look at the nametables. Status bars typically require a scrolling split at a timed location on the screen. This can be done most easily with a mapper based IRQ, but can also be accomplished with a sprite-0 hit or other techniques.