Difference between revisions of "PPU Registers"
(→NMITIMEN - Interrupt Enable Flags) |
|||
Line 148: | Line 148: | ||
This writes to CGRAM, effectively setting the palette colors. Accesses to CGRAM are handled just like accesses to the low table of OAM, see $2104 for details. Note that the color values are stored in BGR order. | This writes to CGRAM, effectively setting the palette colors. Accesses to CGRAM are handled just like accesses to the low table of OAM, see $2104 for details. Note that the color values are stored in BGR order. | ||
− | = | + | = To reformat = |
+ | === OBSEL - Object Size and Character Address === | ||
− | + | <pre>$2101 wb++?- | |
− | + | sssnnbbb | |
− | <pre> | + | sss = Object size: |
− | + | 000 = 8x8 and 16x16 sprites | |
− | + | 001 = 8x8 and 32x32 sprites | |
− | + | 010 = 8x8 and 64x64 sprites | |
− | + | 011 = 16x16 and 32x32 sprites | |
− | + | 100 = 16x16 and 64x64 sprites | |
− | + | 101 = 32x32 and 64x64 sprites | |
− | + | 110 = 16x32 and 32x64 sprites ('undocumented') | |
− | + | 111 = 16x32 and 32x32 sprites ('undocumented') | |
− | + | nn = Name Select | |
− | + | bbb = Name Base Select (Addr>>14)</pre> | |
− | + | See the section “[[sprites|SPRITES]]” below for details. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </pre> | ||
− | + | === OAMADDL - OAM Address low byte === | |
− | == | + | === OAMADDH - OAM Address high bit and Obj Priority === |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <pre>2102 wl++?- | |
− | + | 2103 wh++?- | |
− | + | p------b aaaaaaaa | |
− | <pre> | + | p = Obj Priority activation bit |
− | + | b aaaaaaaa = OAM address</pre> | |
− | + | When Obj Priority activation bit is set, an Obj other than Sprite 0 may be given priority. See the section “[[sprites|SPRITES]]” below for details. | |
− | ------ | ||
− | </pre> | ||
− | |||
− | + | OAM address can be thought of in two ways, depending on your conception of OAM. If you consider OAM as a 544-byte table, baaaaaaaa is the word address into that table. If you consider OAM to be a 512-byte table and a 32-byte table, b is the table selector and aaaaaaaa is the word address in the table. See the section “[[sprites|SPRITES]]” below for details. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | + | The internal OAM address is invalidated when scanlines are being rendered. This invalidation is deterministic, but we do not know how it is determined. Thus, the last value written to these registers is reloaded into the internal OAM address at the beginning of V-Blank if that occurs outside of a force-blank period. This is known as ‘OAM reset’. ‘OAM reset’ also occurs on certain writes to $2100. |
− | + | ||
− | <pre> | + | Writing to either $2102 or $2103 resets the entire internal OAM Address to the values last written to this register. E.g., if you set $104 to this register, write 4 bytes, then write $1 to $2103, the internal OAM address will point to word 4, not word 6. |
− | + | ||
− | ---- ---- | + | === OAMDATA - Data for OAM write === |
− | + | ||
− | |||| |||| | + | <pre>2104 wb++-- |
− | |+++ ++++-- | + | dddddddd</pre> |
− | +---------- | + | Note that OAM writes are done in an odd manner, in particular the low table of OAM is not affected until the high byte of a word is written (however, the high table is affected immediately). Thus, if you set the address, then alternate writes and reads, OAM will never be affected until you reach the high table! |
− | </pre> | + | |
− | + | Similarly, if you set the address to 0, then write 1, 2, read, then write 3, OAM will end up as “01 02 01 03”, rather than “01 02 xx 03” as you might expect. | |
+ | |||
+ | Technically, this register CAN be written during H-blank (and probably mid-scanline as well). However, due to OAM address invalidation the actual OAM byte written will probably not be what you expect. Note that writing during force-blank will only work as expected if that force-blank was begun during V-Blank, or (probably) if $2102/3 have been reset during that force-blank period. | ||
+ | |||
+ | See the section “[[sprites|SPRITES]]” below for details. | ||
+ | |||
+ | === MOSAIC - Screen Pixelation === | ||
+ | |||
+ | <pre>2106 wb+++- | ||
+ | xxxxDCBA | ||
+ | A/B/C/D = Affect BG1/BG2/BG3/BG4 | ||
+ | xxxx = pixel size, 0=1x1, F=16x16</pre> | ||
+ | The mosaic filter goes over the BG and covers each x-by-x square with the upper-left pixel of that square, with the top of the first row of squares on the ‘starting scanline’. If this register is set during the frame, the ‘starting scanline’ is the current scanline, otherwise it is the first visible scanline of the frame. I.e. if even scanlines are completely red and odd scanlines are completely blue, setting the xxxx=1 mid-frame will make the rest of the screen either completely red or completely blue depending on whether you set xxxx on an even or an odd scanline. | ||
+ | |||
+ | XXX: It seems that writing the same value to this register does not reset the ‘starting scanline’, but which changes do reset it? | ||
+ | |||
+ | Note that mosaic is applied after scrolling, but before any clip windows, color windows, or math. So the XxX block can be partially clipped, and it can be mathed as normal with a non-mosaiced BG. But scrolling can’t make it partially one color and partially another. | ||
+ | |||
+ | Modes 5-6 should ‘double’ the expansion factor to expand half-pixels. This actually makes xxxx=0 have a visible effect, since the even half-pixels (usually on the subscreen) hide the odd half-pixels. The same thing happens vertically with interlace mode. | ||
+ | |||
+ | Mode 7, of course, is weird. BG1 mosaics about like normal, as long as you remember that the Mode 7 transformations have no effect on the XxX blocks. BG2 uses bit A to control ‘vertical mosaic’ and bit B to control ‘horizontal mosaic’, so you could be expanding over 1xX, Xx1, or XxX blocks. This can get really interesting as BG1 still uses bit A as normal, so you could have the BG1 pixels expanded XxX with high-priority BG2 pixels expanded 1xX on top of them. | ||
+ | |||
+ | See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | |||
+ | === BG1HOFS - BG1 Horizontal Scroll === | ||
+ | |||
+ | === M7HOFS - Mode 7 BG Horizontal Scroll === | ||
+ | |||
+ | === BG1VOFS - BG1 Vertical Scroll === | ||
+ | |||
+ | === M7VOFS - Mode 7 BG Vertical Scroll === | ||
+ | |||
+ | <pre>210d ww+++- | ||
+ | ww+++- | ||
+ | 210e ww+++- | ||
+ | ww+++- | ||
+ | ------xx xxxxxxxx | ||
+ | ---mmmmm mmmmmmmm | ||
+ | |||
+ | x = The BG offset, 10 bits. | ||
+ | m = The Mode 7 BG offset, 13 bits two's-complement signed.</pre> | ||
+ | These are actually two registers in one (or would that be “4 registers in 2”?). Anyway, writing $210d will write both BG1HOFS which works exactly like the rest of the BGnxOFS registers below ($210f-$2114), and M7HOFS which works with the M7* registers ($211b-$2120) instead. | ||
+ | |||
+ | Modes 0-6 use BG1xOFS and ignore M7xOFS, while Mode 7 uses M7xOFS and ignores BG1HOFS. See the appropriate sections below for details, and note the different formulas for BG1HOFS versus M7HOFS. | ||
+ | |||
+ | === BG2HOFS - BG2 Horizontal Scroll === | ||
+ | |||
+ | === BG2VOFS - BG2 Vertical Scroll === | ||
+ | |||
+ | === BG3HOFS - BG3 Horizontal Scroll === | ||
+ | |||
+ | === BG3VOFS - BG3 Vertical Scroll === | ||
+ | |||
+ | === BG4HOFS - BG4 Horizontal Scroll === | ||
+ | |||
+ | === BG4VOFS - BG4 Vertical Scroll === | ||
+ | |||
+ | <pre>210f ww+++- | ||
+ | 2110 ww+++- | ||
+ | 2111 ww+++- | ||
+ | 2112 ww+++- | ||
+ | 2113 ww+++- | ||
+ | 2114 ww+++- | ||
+ | ------xx xxxxxxxx</pre> | ||
+ | Note that these are “write twice” registers, first the low byte is written then the high. Current theory is that writes to the register work like this: | ||
+ | |||
+ | <pre>BGnHOFS = (Current<<8) | (Prev1&~7) | (Prev2&7); | ||
+ | Prev1 = Current; | ||
+ | Prev2 = Current; | ||
+ | or | ||
+ | BGnVOFS = (Current<<8) | Prev1; | ||
+ | Prev1 = Current;</pre> | ||
+ | Note that there is only one Prev1 shared by all eight BGnxOFS registers, and only one Prev2 shared by the four BGnHOFS registers. These are NOT shared with the M7* registers (not even M7xOFS and BG1xOFS). | ||
+ | |||
+ | <pre>x = The BG offset, at most 10 bits (some modes effectively use as few as 8).</pre> | ||
+ | Note that all BGs wrap if you try to go past their edges. Thus, the maximum offset value in BG Modes 0-6 is 1023, since you have at most 64 tiles (if x/y of BGnSC is set) of 16 pixels each (if the appropriate bit of BGMODE is set). | ||
+ | |||
+ | Horizontal scrolling scrolls in units of full pixels no matter if we’re rendering a 256-pixel wide screen or a 512-half-pixel wide screen. However, vertical scrolling will move in half-line increments if interlace mode is active. | ||
+ | |||
+ | See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | === M7SEL - Mode 7 Settings === | ||
+ | |||
+ | <pre>211a wb++?- | ||
+ | rc----yx | ||
+ | r = Playing field size^ | ||
+ | c = Empty space fill, when bit 7 is set: | ||
+ | 0 = Transparent. | ||
+ | 1 = Fill with character 0. Note that the fill is matrix transformed like all other Mode 7 tiles. | ||
+ | x/y = Horizontal/Veritcal mirroring. If the bit is set, flip the 256x256 pixel 'screen' in that direction.</pre> | ||
+ | ^When clear, the playing field is 1024x1024 pixels (so the tilemap completely fills it). When set, the playing field is much larger, and the ‘empty space’ fill is controlled by bit 6. See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | === M7A - Mode 7 Matrix A (also used with $2134/6) === | ||
+ | |||
+ | === M7B - Mode 7 Matrix B (also used with $2134/6) === | ||
+ | |||
+ | === M7C - Mode 7 Matrix C === | ||
+ | |||
+ | === M7D - Mode 7 Matrix D === | ||
+ | |||
+ | <pre>211b ww+++- | ||
+ | 211c ww+++- | ||
+ | 211d ww+++- | ||
+ | 211e ww+++- | ||
+ | aaaaaaaa aaaaaaaa</pre> | ||
+ | Note that these are “write twice” registers, first the low byte is written then the high. Current theory is that writes to the register work like this: | ||
+ | |||
+ | <pre>Reg = (Current<<8) | Prev; | ||
+ | Prev = Current;</pre> | ||
+ | Note that there is only one Prev shared by all these registers. This Prev is NOT shared with the BGnxOFS registers, but it IS shared with the M7xOFS registers. These set the matrix parameters for Mode 7. The values are an 8-bit fixed point, i.e. the value should be divided by 256.0 when used in calculations. See below for more explanation. The product <code>A*(B>>8)</code> may be read from registers $2134/6. There is supposedly no important delay. It may not be operative during Mode 7 rendering. See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | === M7X - Mode 7 Center X === | ||
+ | |||
+ | === M7Y - Mode 7 Center Y === | ||
+ | |||
+ | <pre>211f ww+++- | ||
+ | 2120 ww+++- | ||
+ | ---xxxxx xxxxxxxx</pre> | ||
+ | Note that these are “write twice” registers, like the other M7* registers. See above for the write semantics. The value is 13 bit two’s-complement signed. The matrix transformation formula is: | ||
+ | |||
+ | <pre>[ X ] [ A B ] [ SX + M7HOFS - CX ] [ CX ] | ||
+ | [ ] = [ ] * [ ] + [ ] | ||
+ | [ Y ] [ C D ] [ SY + M7VOFS - CY ] [ CY ]</pre> | ||
+ | Note: SX/SY are screen coordinates. X/Y are coordinates in the playing field from which the pixel is taken. If $211a bit 7 is clear, the result is then restricted to <code>0<=X<=1023</code> and <code>0<=Y<=1023</code>. If $211a bits 6 and 7 are both set and X or Y is less than 0 or greater than 1023, use the low 3 bits of each to choose the pixel from character 0. The bit-accurate formula seems to be something along the lines of: | ||
+ | |||
+ | <pre> #define CLIP(a) (((a)&0x2000)?((a)|~0x3ff):((a)&0x3ff)) | ||
+ | |||
+ | X[0,y] = ((A*CLIP(HOFS-CX))&~63) | ||
+ | + ((B*y)&~63) + ((B*CLIP(VOFS-CY))&~63) | ||
+ | + (CX<<8) | ||
+ | Y[0,y] = ((C*CLIP(HOFS-CX))&~63) | ||
+ | + ((D*y)&~63) + ((D*CLIP(VOFS-CY))&~63) | ||
+ | + (CY<<8) | ||
+ | |||
+ | X[x,y] = X[x-1,y] + A | ||
+ | Y[x,y] = Y[x-1,y] + C | ||
+ | |||
+ | (In all cases, X[] and Y[] are fixed point with 8 bits of fraction)</pre> | ||
+ | See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | === W12SEL - Window Mask Settings for BG1 and BG2 === | ||
+ | |||
+ | === W34SEL - Window Mask Settings for BG3 and BG4 === | ||
+ | |||
+ | === WOBJSEL - Window Mask Settings for OBJ and Color Window === | ||
+ | |||
+ | <pre>2123 wb+++- | ||
+ | 2124 wb+++- | ||
+ | 2125 wb+++- | ||
+ | ABCDabcd | ||
+ | d = Window 1 Inversion for BG1/BG3/OBJ | ||
+ | c = Enable window 1 for BG1/BG3/OBJ | ||
+ | b = Window 2 Inversion for BG1/BG3/OBJ | ||
+ | a = Enable window 2 for BG1/BG3/OBJ | ||
+ | D = Window 1 Inversion for BG2/BG4/Color^^ | ||
+ | C = Enable window 1 for BG2/BG4/Color^ | ||
+ | B = Window 2 Inversion for BG2/BG4/Color^^ | ||
+ | A = Enable window 2 for BG2/BG4/Color^</pre> | ||
+ | ^When the bit is set, the corresponding window will affect the corresponding background (subject to the settings of $212e/f). | ||
+ | |||
+ | ^^When the bit is set, “W” should be replaced by “~W” (not-W) in the window combination formulae below. See the section “WINDOWS” below for more details. | ||
+ | |||
+ | === WH0 - Window 1 Left Position === | ||
+ | |||
+ | === WH1 - Window 1 Right Position === | ||
+ | |||
+ | === WH2 - Window 2 Left Position === | ||
+ | |||
+ | === WH3 - Window 2 Right Position === | ||
+ | |||
+ | <pre>2126 wb+++- | ||
+ | 2127 wb+++- | ||
+ | 2128 wb+++- | ||
+ | 2129 wb+++- | ||
+ | xxxxxxxx</pre> | ||
+ | These set the offset of the appropriate edge of the appropriate window. Note that if the left edge is greater than the right edge, the window is considered to have no range at all (and thus “W” always is false). See the section “WINDOWS” below for more details. | ||
+ | |||
+ | === WBGLOG - Window mask logic for BGs === | ||
+ | |||
+ | === WOBJLOG - Window mask logic for OBJs and Color Window === | ||
+ | |||
+ | <pre>212a wb+++- | ||
+ | 44332211 | ||
+ | 212b wb+++- | ||
+ | ----ccoo | ||
+ | |||
+ | 44/33/22/11/oo/cc = Mask logic for BG1/BG2/BG3/BG4/OBJ/Color | ||
+ | This specified the window combination method, using standard boolean operators: | ||
+ | 00 = OR | ||
+ | 01 = AND | ||
+ | 10 = XOR | ||
+ | 11 = XNOR</pre> | ||
+ | Consider two variables, W1 and W2, which are true for pixels between the appropriate left and right bounds as set in $2126-$2129 and false otherwise. Then, you have the following possibilities: (replace “W#” with “~W#”, depending on the Inversion settings of $2123-$2125) Neither window enabled => nothing masked. One window enabled => Either W1 or W2, as appropriate. Both windows enabled => W1 op W2, where “op” is as above. Where the function is true, the BG will be masked. See the section “WINDOWS” below for more details. | ||
+ | |||
+ | === TM - Main Screen Designation === | ||
+ | |||
+ | === TS - Subscreen Designation === | ||
+ | |||
+ | <pre>212c wb+++- | ||
+ | 212d wb+++- | ||
+ | ---o4321 | ||
+ | |||
+ | 1/2/3/4/o = Enable BG1/BG2/BG3/BG4/OBJ for display on the main (or sub) screen.</pre> | ||
+ | See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | === TMW - Window Mask Designation for the Main Screen === | ||
+ | |||
+ | === TSW - Window Mask Designation for the Subscreen === | ||
+ | |||
+ | <pre>212e wb+++- | ||
+ | 212f wb+++- | ||
+ | ---o4321 | ||
+ | 1/2/3/4/o = Enable window masking for BG1/BG2/BG3/BG4/OBJ on the main (or sub) screen.</pre> | ||
+ | See the section “[[backgrounds|BACKGROUNDS]]” below for details. | ||
+ | |||
+ | === CGWSEL - Color Addition Select === | ||
+ | |||
+ | <pre>2130 wb+++- | ||
+ | ccmm--sd | ||
+ | cc = Clip colors to black before math | ||
+ | 00 => Never | ||
+ | 01 => Outside Color Window only | ||
+ | 10 => Inside Color Window only | ||
+ | 11 => Always | ||
+ | mm = Prevent color math | ||
+ | 00 => Never | ||
+ | 01 => Outside Color Window only | ||
+ | 10 => Inside Color Window only | ||
+ | 11 => Always | ||
+ | s = Add subscreen (instead of fixed color) | ||
+ | d = Direct color mode for 256-color BGs</pre> | ||
+ | See the sections “[[backgrounds|BACKGROUNDS]]”, “WINDOWS”, and “RENDERING THE SCREEN” below for details. | ||
+ | |||
+ | === CGADSUB - Color math designation === | ||
+ | |||
+ | <pre>2131 wb+++- | ||
+ | shbo4321 | ||
+ | s = Add/subtract select | ||
+ | 0 => Add the colors | ||
+ | 1 => Subtract the colors | ||
+ | h = Half color math.^ | ||
+ | 4/3/2/1/o/b = Enable color math on BG1/BG2/BG3/BG4/OBJ/Backdrop</pre> | ||
+ | ^ When set, the result of the color math is divided by 2 (except when $2130 bit 1 is set and the fixed color is used, or when color is clipped). See the sections “[[backgrounds|BACKGROUNDS]]”, “WINDOWS”, and “RENDERING THE SCREEN” below for details. | ||
+ | |||
+ | === COLDATA - Fixed Color Data === | ||
+ | |||
+ | <pre>2132 wb+++- | ||
+ | bgrccccc | ||
+ | b/g/r = Which color plane(s) to set the intensity for. | ||
+ | ccccc = Color intensity.</pre> | ||
+ | So basically, to set an orange you’d do something along the lines of: LDA #$3f STA $2132 LDA #$4f STA $2132 LDA #$80 STA $2132 | ||
+ | |||
+ | See the sections “[[backgrounds|BACKGROUNDS]]” and “WINDOWS” below for details. | ||
+ | |||
+ | === SETINI - Screen Mode/Video Select === | ||
+ | |||
+ | <pre>2133 wb+++- | ||
+ | se--poIi | ||
+ | s = "External Sync".^ | ||
+ | e = Mode 7 EXTBG ("Extra BG").^^ | ||
+ | p = Enable pseudo-hires mode.^^^ | ||
+ | o = Overscan mode.^^^^ | ||
+ | I = OBJ Interlace.^^^^^ | ||
+ | i = Screen interlace.^^^^^^</pre> | ||
+ | ^Used for superimposing “sfx” graphics, whatever that means. Usually 0. Not much is known about this bit. Interestingly, the SPPU1 chip has a pin named “EXTSYNC” (or not-EXTSYNC, since it has a bar over it) which is tied to Vcc. | ||
+ | |||
+ | ^^When this bit is set, you may enable BG2 on Mode 7. BG2 uses the same tile and character data as BG1, but interprets the high bit of the color data as a priority for the pixel. Various sources report additional effects for this bit, possibly related to bit 7. For example, “Enable the Data Supplied From the External Lsi.”, whatever that means. Of course, maybe that’s a typo and it’s supposed to apply to bit 7 instead. | ||
+ | |||
+ | ^^^This creates a 512-pixel horizontal resolution by taking pixels from the subscreen for the even-numbered pixels (zero based) and from the main screen for the odd-numbered pixels. Color math behaves just as with Mode 5/6 hires. The interlace bit still has no effect. Mosaic operates as normal (not like Mode 5/6). The ‘subscreen’ pixel is clipped (by windows) when the main-screen pixel to the LEFT is clipped, not when the one to the RIGHT is clipped as you’d expect. What happens with pixel column 0 is unknown. Enabling this bit in Modes 5 or 6 has no effect. | ||
+ | |||
+ | ^^^^When set, 239 lines will be displayed instead of the normal 224. This also means V-Blank will occur that much later, and be shorter. All that happens is that extra lines get added to the display, and it seems the TV will like to move the display up 8 pixels. Overscan: The bit only matters at the very end of the frame, if you change the setting on line 0xE0 before the normal NMI trigger point then it’s the same as if you had it on all frame. Note that this affects both the NMI trigger point and when HDMA stops for the frame. If you turn the bit off at the very beginning of scanline X (for 0xE1<=X<=0xF0), NMI will occur on line X and the last HDMA transfer will occur on line X-1. However, on my TV at least, the display will remain in the normal no-overscan position for lines E1-EC, it will move up only one pixel for line ED, and it will lose vertical sync for lines EF-F4! Turning the bit on, only line E1 gives any effect: NMI will occur on line E2, although the last HDMA will still occur on line E0. Anything else acts like you left the bit off the whole time. Note, however, that if you wait too long after the beginning of the scanline then you will get no effect. Even if there is no visible effect, the overscan setting still affects VRAM writes. In particular, executing <code>LDA #'-' / STA &#36;2118 / LDA r2133 / STA &#36;2133 / LDA #'+' / STA &#36;2118</code> during the E1-F0 period will write only + or only - to VRAM, depending on whether the overscan bit was set to 0 or 1. | ||
+ | |||
+ | ^^^^^When set regardless of BG mode, the OBJ will be interlaced (see bit 0 below), and thus will appear half-height. Note that this only controls whether obj are drawn as normal or not; the interlace signal is only output to the TV based on bit 0 below. | ||
+ | |||
+ | ^^^^^^When set in BG mode 5 (and probably 6), the effective screen height will be 448 (or 478) pixles, rather than 224 (or 239). When set in any other mode, the screen will just get a bit jumpy. However, toggling the tilemap each field would simulate the increased screen height (much like pseudo-hires simulares hires). In hardware, setting this bit makes the SNES output a normal interlace signal rather than always forcing one frame. | ||
+ | |||
+ | See the sections “[[backgrounds|BACKGROUNDS]]” and “[[sprites|SPRITES]]” below for details. | ||
+ | |||
+ | === MPYL - Multiplication Result low byte === | ||
+ | |||
+ | === MPYM - Multiplication Result middle byte === | ||
+ | |||
+ | === MPYH - Multiplication Result high byte === | ||
+ | |||
+ | <pre>2134 r l+++? | ||
+ | 2135 r m+++? | ||
+ | 2136 r h+++? | ||
+ | xxxxxxxx xxxxxxxx xxxxxxxx</pre> | ||
+ | This is the 2’s compliment product of the 16-bit value written to $211b and the 8-bit value most recently written to $211c. There is supposedly no important delay. It may not be operative during Mode 7 rendering. | ||
+ | |||
+ | === SLHV - Software Latch for H/V Counter === | ||
+ | |||
+ | <pre>2137 b++++ | ||
+ | --------</pre> | ||
+ | When read, the H/V counter (as read from $213c and $213d) will be latched to the current X and Y position if bit 7 of $4201 is set. The data actually read is open bus. | ||
+ | |||
+ | === OAMDATAREAD* - Data for OAM read === | ||
+ | |||
+ | 2138 r w++?- xxxxxxxx | ||
+ | |||
+ | OAM reads are straightforward: the current byte as set in $2102/3 and incremented by reads from this register and writes to $2104 will be returned. Note that writes to the lower table are not affected so logically. See register $2104 and the section “[[sprites|SPRITES]]” below for details. Also, note that OAM address invalidation probably affects the address read by this register as well. | ||
+ | |||
+ | === VMDATALREAD* - VRAM Data Read low byte === | ||
+ | |||
+ | === VMDATAHREAD* - VRAM Data Read high byte === | ||
+ | |||
+ | <pre>2139 r l++?- | ||
+ | 213a r h++?- | ||
+ | xxxxxxxx xxxxxxxx</pre> | ||
+ | Simply, this reads data from VRAM. The address is incremented when either $2139 or $213a is read, depending on the setting of bit 7 of $2115. Actually, the reading is more complex. When either of these registers is read, the appropriate byte from a word-sized buffer is returned. A word from VRAM is loaded into this buffer just ''before'' the VRAM address is incremented. The actual data read and the amount of the increment depend on the low 4 bits of $2115. The effect of this is that a ‘dummy read’ is required after setting $2116-7 before you start getting the actual data. The interaction between these registers and $2118/9 is unknown. See the sections “[[backgrounds|BACKGROUNDS]]” and “[[sprites|SPRITES]]” below for details. | ||
+ | |||
+ | === CGDATAREAD* - CGRAM Data read === | ||
+ | |||
+ | <pre>213b r w++?- | ||
+ | -bbbbbgg gggrrrrr</pre> | ||
+ | This reads from CGRAM. Accesses to CGRAM are handled just like accesses to the low table of OAM, see $2138 for details. Note that the color values are stored in BGR order. The ‘-’ bit is PPU2 Open Bus. | ||
+ | |||
+ | === OPHCT - Horizontal Scanline Location === | ||
+ | |||
+ | === OPVCT - Vertical Scanline Location === | ||
+ | |||
+ | <pre>213c r w++++ | ||
+ | 213d r w++++ | ||
+ | -------x xxxxxxxx</pre> | ||
+ | These values are latched by reading $2137 when bit 7 of $4201 is set, or by clearing-and-setting bit 7 of $4201 either by writing $4201 or by pin 6 of Controller Port 2 (the latch occurs on the 1->0 transition). Note that the value read is only 9 bits: bits 1-7 of the high byte are PPU2 Open Bus. Each register keeps seperate track of whether to return the low or high byte. The high/low selector is reset to ‘low’ when $213f is read (the selector is NOT reset when the counter is latched). H Counter values range from 0 to 339, with 22-277 being visible on the screen. V Counter values range from 0 to 261 in NTSC mode (262 is possible every other frame when interlace is active) and 0 to 311 in PAL mode (312 in interlace?), with 1-224 (or 1-239(?) if overscan is enabled) visible on the screen. | ||
+ | |||
+ | === STAT77 - PPU Status Flag and Version === | ||
+ | |||
+ | <pre>213e r b++++ | ||
+ | trm-vvvv | ||
+ | t = Time Over Flag.^ | ||
+ | r = Range Over Flag.^^ | ||
+ | m = "Master/slave mode select".^^^ | ||
+ | - = PPU1 Open Bus. | ||
+ | vvvv = 5c77 chip version number. So far, we've only encountered version 1.</pre> | ||
+ | ^If more than 34 sprite-tiles (e.g. a 16x16 sprite has 2 sprite-tiles) were encountered on a single line, this flag will be set. The flag is reset at the end of V-Blank. See the section “[[sprites|SPRITES]]” below for details. | ||
+ | |||
+ | ^^If more than 32 sprites were encountered on a single line, this flag will be set. The flag is reset at the end of V-Blank. See the section “[[sprites|SPRITES]]” below for details. Note that the above two flags are set whether or not OBJ are actually enabled at the time. | ||
+ | |||
+ | ^^^Little is known about this bit. Current theory is that it indicates the status of the “MASTER” pin on the S-PPU1 chip, which in the normal SNES is always Gnd. We always seem to read back 0 here. | ||
+ | |||
+ | === STAT78 - PPU Status Flag and Version === | ||
+ | |||
+ | <pre>213f r b++++ | ||
+ | fl-pvvvv | ||
+ | f = Interlace Field.^ | ||
+ | l = External latch flag.^^ | ||
+ | - = PPU2 Open Bus. | ||
+ | p = NTSC/Pal Mode.^^^ | ||
+ | vvvv = 5C78 chip version number. So far, we've encountered at least 2 and 3. Possibly 1 as well.</pre> | ||
+ | ^This will toggle every V-Blank. | ||
+ | |||
+ | ^^When the PPU counters are latched, this flag gets set. The flag is reset on read, but only when $4201 bit 7 is set. | ||
+ | |||
+ | ^^^If this is a PAL SNES, this bit will be set, otherwise it will be clear. | ||
+ | |||
+ | Note: as a side effect of reading this register, the high/low byte selector for $213c/d is reset to ‘low’. |
Revision as of 17:07, 17 February 2019
Contents
- 1 Address Bus B Registers
- 1.1 INIDISP - Screen Display ($2100)
- 1.2 BGMODE - BG Mode and Character Size ($2105)
- 1.3 $2107-$210A BG 1-4 Tilemap Address and Size (BG1SC, BG2SC, BG3SC, BG4SC)
- 1.4 $210B / $210C - BG1+2 / BG3+4 Chr Address (BG12NBA / BG34NBA)
- 1.5 $2115 - Video Port Control (VMAIN)
- 1.6 $2116 / $2117 - VRAM Address low/high bytes (VMADDL/VMADDH)
- 1.7 $2118 / $2119 - VRAM Data Write low/high bytes (VMDATAL/VMDATAH)
- 1.8 $2121 - CGRAM Address (CGADD)
- 1.9 $2122 - CGRAM Data write (CGDATA)
- 2 To reformat
- 2.1 OBSEL - Object Size and Character Address
- 2.2 OAMADDL - OAM Address low byte
- 2.3 OAMADDH - OAM Address high bit and Obj Priority
- 2.4 OAMDATA - Data for OAM write
- 2.5 MOSAIC - Screen Pixelation
- 2.6 BG1HOFS - BG1 Horizontal Scroll
- 2.7 M7HOFS - Mode 7 BG Horizontal Scroll
- 2.8 BG1VOFS - BG1 Vertical Scroll
- 2.9 M7VOFS - Mode 7 BG Vertical Scroll
- 2.10 BG2HOFS - BG2 Horizontal Scroll
- 2.11 BG2VOFS - BG2 Vertical Scroll
- 2.12 BG3HOFS - BG3 Horizontal Scroll
- 2.13 BG3VOFS - BG3 Vertical Scroll
- 2.14 BG4HOFS - BG4 Horizontal Scroll
- 2.15 BG4VOFS - BG4 Vertical Scroll
- 2.16 M7SEL - Mode 7 Settings
- 2.17 M7A - Mode 7 Matrix A (also used with $2134/6)
- 2.18 M7B - Mode 7 Matrix B (also used with $2134/6)
- 2.19 M7C - Mode 7 Matrix C
- 2.20 M7D - Mode 7 Matrix D
- 2.21 M7X - Mode 7 Center X
- 2.22 M7Y - Mode 7 Center Y
- 2.23 W12SEL - Window Mask Settings for BG1 and BG2
- 2.24 W34SEL - Window Mask Settings for BG3 and BG4
- 2.25 WOBJSEL - Window Mask Settings for OBJ and Color Window
- 2.26 WH0 - Window 1 Left Position
- 2.27 WH1 - Window 1 Right Position
- 2.28 WH2 - Window 2 Left Position
- 2.29 WH3 - Window 2 Right Position
- 2.30 WBGLOG - Window mask logic for BGs
- 2.31 WOBJLOG - Window mask logic for OBJs and Color Window
- 2.32 TM - Main Screen Designation
- 2.33 TS - Subscreen Designation
- 2.34 TMW - Window Mask Designation for the Main Screen
- 2.35 TSW - Window Mask Designation for the Subscreen
- 2.36 CGWSEL - Color Addition Select
- 2.37 CGADSUB - Color math designation
- 2.38 COLDATA - Fixed Color Data
- 2.39 SETINI - Screen Mode/Video Select
- 2.40 MPYL - Multiplication Result low byte
- 2.41 MPYM - Multiplication Result middle byte
- 2.42 MPYH - Multiplication Result high byte
- 2.43 SLHV - Software Latch for H/V Counter
- 2.44 OAMDATAREAD* - Data for OAM read
- 2.45 VMDATALREAD* - VRAM Data Read low byte
- 2.46 VMDATAHREAD* - VRAM Data Read high byte
- 2.47 CGDATAREAD* - CGRAM Data read
- 2.48 OPHCT - Horizontal Scanline Location
- 2.49 OPVCT - Vertical Scanline Location
- 2.50 STAT77 - PPU Status Flag and Version
- 2.51 STAT78 - PPU Status Flag and Version
Address Bus B Registers
INIDISP - Screen Display ($2100)
$2100 | Byte
|
☐ Read | ☑ Write
|
Access during: |
☑ Forced blank |
☑ Vertical blank |
☑ Horizontal blank |
☑ Rendering |
7 bit 0 ---- ---- x--- bbbb | |||| | ++++-- Screen brightness, F=max, 0="off" | +---------- Force blank on when set.
Note that force blank CAN be disabled mid-scanline. However, this can result in glitched graphics on that scanline, as the internal rendering buffers will not have been updated during force blank. Current theory is that BGs will be glitched for a few tiles (depending on how far in advance the PPU operates), and OBJ will be glitched for the entire scanline. Also, writing this register on the first line of V-Blank (225 or 240, depending on overscan) when force blank is currently active causes the OAM Address Reset to occur.
BGMODE - BG Mode and Character Size ($2105)
$2105 | Byte
|
☐ Read | ☑ Write
|
Access during: |
☑ Forced blank |
☑ Vertical blank |
☑ Horizontal blank |
☐ Rendering |
7 bit 0 ---- ---- DCBA emmm |||| |||| |||| |+++-- BG Mode |||| | |||| +----- Mode 1 BG3 priority bit |||| ++++------- BG character size for BG1/BG2/BG3/BG4
Mode BG depth OPT Priorities 1 2 3 4 Front -> Back -=-------=-=-=-=----=---============--- 0 2 2 2 2 n 3AB2ab1CD0cd 1 4 4 2 n 3AB2ab1C 0c * if e set: C3AB2ab1 0c 2 4 4 y 3A 2B 1a 0b 3 8 4 n 3A 2B 1a 0b 4 8 2 y 3A 2B 1a 0b 5 4 2 n 3A 2B 1a 0b 6 4 y 3A 2 1a 0 7 8 n 3 2 1a 0 7+EXTBG 8 7 n 3 2B 1a 0b
If the BG character size for BG1/BG2/BG3/BG4 bit is set, then the BG is made of 16x16 tiles. Otherwise, 8x8 tiles are used. However, note that Modes 5 and 6 always use 16-pixel wide tiles, and Mode 7 always uses 8x8 tiles. See the section “BACKGROUNDS” below for details.
“OPT” means “Offset-per-tile mode”. For the priorities, numbers mean sprites with that priority. Letters correspond to BGs (A=1, B=2, etc), with upper/lower case indicating tile priority 1/0. See the section “BACKGROUNDS” below for details.
Mode 7’s EXTBG mode allows you to enable BG2, which uses the same tilemap and character data as BG1 but interprets bit 7 of the pixel data as a priority bit. BG2 also has some oddness to do with some of the per-BG registers below. See the Mode 7 section under BACKGROUNDS for details.
$2107-$210A BG 1-4 Tilemap Address and Size (BG1SC, BG2SC, BG3SC, BG4SC)
2107 wb++?- 2108 wb++?- 2109 wb++?- 210a wb++?- 7 bit 0 ---- ---- aaaa aayx |||| |||| |||| |||+-- Tilemap horizontal mirroring |||| ||+--- Tilemap vertical mirroring |||| || +++++++---- Tilemap address in VRAM (Addr << 10)
All tilemaps are 32x32 tiles. If x and y are both unset, there is one tilemap at Addr. If x is set, a second tilemap follows the first that should be considered “to the right of” the first. If y is set, a second tilemap follows the first that should be considered “below” the first. If both are set, then a second follows “to the right”, then a third “below”, and a fourth “below and to the right”.
See the section “BACKGROUNDS” below for more details.
$210B / $210C - BG1+2 / BG3+4 Chr Address (BG12NBA / BG34NBA)
210b wb++?- 210c wb++?- 7 bit 0 ---- ---- bbbb aaaa |||| |||| |||| ++++-- Base address for BG1/3 (Addr << 12) ++++------- Base address for BG2/4 (Addr << 12)
Example: Saving $63
into $210B
makes the PPU look for the Tileset for BG2
at $6000
in the VRAM
and for BG1
at $3000
.
See the section “BACKGROUNDS” for details.
$2115 - Video Port Control (VMAIN)
$2115 wb++?- 7 bit 0 ---- ---- M... RRII | |||| | ||++-- Address increment amount | || 00 = Normal increment by 1 | || 01 = Increment by 32 | || 10 = Increment by 128 | || 11 = Increment by 128 | || | ++----- Address remapping | 00 = No remapping | 01 = Remap addressing aaaaaaaaBBBccccc => aaaaaaaacccccBBB | 10 = Remap addressing aaaaaaaBBBcccccc => aaaaaaaccccccBBB | 11 = Remap addressing aaaaaaBBBccccccc => aaaaaacccccccBBB | +----------- Address increment mode: 0 = increment after writing $2118/reading $2139 1 = increment after writing $2119/reading $213A Note: A word write stores low first, then high. Thus, if you’re storing a word value to $2118/9, you’ll probably want to set 1 here.
The “remap” modes basically implement address translation. If $2116/7 are set to #$0003, then word address #$0018 will be written instead, and $2116/7 will be incremented to $0004.
$2116 / $2117 - VRAM Address low/high bytes (VMADDL/VMADDH)
2116 wl++?- 2117 wh++?-
This sets the address for $2118/9 and $2139/a. Note that this is a word address, not a byte address! See the sections “BACKGROUNDS” and “SPRITES” below for details.
$2118 / $2119 - VRAM Data Write low/high bytes (VMDATAL/VMDATAH)
2118 wl++-- 2119 wh++--
This writes data to VRAM. The writes take effect immediately(?), even if no increment is performed. The address is incremented when one of the two bytes is written; which one depends on the setting of bit 7 of register $2115. Keep in mind the address translation bits of $2115 as well. The interaction between these registers and $2139/a is unknown. See the sections “BACKGROUNDS” and “SPRITES” below for details.
$2121 - CGRAM Address (CGADD)
2121 wb+++-
This sets the word address (i.e. color) which will be affected by $2122 and $213b.
Writing “0” to $2121 will change the “currently selected color index” used by $2122, to 0. Upon writing a color to $2122, the color will be stored into the array index selected by $2121, which in this case would be 0 - if you wrote 0 to $2121 before writing a color to $2122.
Keep in mind the color index accessed by $2121 will automatically increment by 1 after writing a color to $2122. This is an effect generated by $2122 after being used in case you want to write specific colors in a series.
$2122 - CGRAM Data write (CGDATA)
2122 ww+++- 15 8 7 0 ---- ---- ---- ---- -bbb bbgg gggr rrrr
This writes to CGRAM, effectively setting the palette colors. Accesses to CGRAM are handled just like accesses to the low table of OAM, see $2104 for details. Note that the color values are stored in BGR order.
To reformat
OBSEL - Object Size and Character Address
$2101 wb++?- sssnnbbb sss = Object size: 000 = 8x8 and 16x16 sprites 001 = 8x8 and 32x32 sprites 010 = 8x8 and 64x64 sprites 011 = 16x16 and 32x32 sprites 100 = 16x16 and 64x64 sprites 101 = 32x32 and 64x64 sprites 110 = 16x32 and 32x64 sprites ('undocumented') 111 = 16x32 and 32x32 sprites ('undocumented') nn = Name Select bbb = Name Base Select (Addr>>14)
See the section “SPRITES” below for details.
OAMADDL - OAM Address low byte
OAMADDH - OAM Address high bit and Obj Priority
2102 wl++?- 2103 wh++?- p------b aaaaaaaa p = Obj Priority activation bit b aaaaaaaa = OAM address
When Obj Priority activation bit is set, an Obj other than Sprite 0 may be given priority. See the section “SPRITES” below for details.
OAM address can be thought of in two ways, depending on your conception of OAM. If you consider OAM as a 544-byte table, baaaaaaaa is the word address into that table. If you consider OAM to be a 512-byte table and a 32-byte table, b is the table selector and aaaaaaaa is the word address in the table. See the section “SPRITES” below for details.
The internal OAM address is invalidated when scanlines are being rendered. This invalidation is deterministic, but we do not know how it is determined. Thus, the last value written to these registers is reloaded into the internal OAM address at the beginning of V-Blank if that occurs outside of a force-blank period. This is known as ‘OAM reset’. ‘OAM reset’ also occurs on certain writes to $2100.
Writing to either $2102 or $2103 resets the entire internal OAM Address to the values last written to this register. E.g., if you set $104 to this register, write 4 bytes, then write $1 to $2103, the internal OAM address will point to word 4, not word 6.
OAMDATA - Data for OAM write
2104 wb++-- dddddddd
Note that OAM writes are done in an odd manner, in particular the low table of OAM is not affected until the high byte of a word is written (however, the high table is affected immediately). Thus, if you set the address, then alternate writes and reads, OAM will never be affected until you reach the high table!
Similarly, if you set the address to 0, then write 1, 2, read, then write 3, OAM will end up as “01 02 01 03”, rather than “01 02 xx 03” as you might expect.
Technically, this register CAN be written during H-blank (and probably mid-scanline as well). However, due to OAM address invalidation the actual OAM byte written will probably not be what you expect. Note that writing during force-blank will only work as expected if that force-blank was begun during V-Blank, or (probably) if $2102/3 have been reset during that force-blank period.
See the section “SPRITES” below for details.
MOSAIC - Screen Pixelation
2106 wb+++- xxxxDCBA A/B/C/D = Affect BG1/BG2/BG3/BG4 xxxx = pixel size, 0=1x1, F=16x16
The mosaic filter goes over the BG and covers each x-by-x square with the upper-left pixel of that square, with the top of the first row of squares on the ‘starting scanline’. If this register is set during the frame, the ‘starting scanline’ is the current scanline, otherwise it is the first visible scanline of the frame. I.e. if even scanlines are completely red and odd scanlines are completely blue, setting the xxxx=1 mid-frame will make the rest of the screen either completely red or completely blue depending on whether you set xxxx on an even or an odd scanline.
XXX: It seems that writing the same value to this register does not reset the ‘starting scanline’, but which changes do reset it?
Note that mosaic is applied after scrolling, but before any clip windows, color windows, or math. So the XxX block can be partially clipped, and it can be mathed as normal with a non-mosaiced BG. But scrolling can’t make it partially one color and partially another.
Modes 5-6 should ‘double’ the expansion factor to expand half-pixels. This actually makes xxxx=0 have a visible effect, since the even half-pixels (usually on the subscreen) hide the odd half-pixels. The same thing happens vertically with interlace mode.
Mode 7, of course, is weird. BG1 mosaics about like normal, as long as you remember that the Mode 7 transformations have no effect on the XxX blocks. BG2 uses bit A to control ‘vertical mosaic’ and bit B to control ‘horizontal mosaic’, so you could be expanding over 1xX, Xx1, or XxX blocks. This can get really interesting as BG1 still uses bit A as normal, so you could have the BG1 pixels expanded XxX with high-priority BG2 pixels expanded 1xX on top of them.
See the section “BACKGROUNDS” below for details.
BG1HOFS - BG1 Horizontal Scroll
M7HOFS - Mode 7 BG Horizontal Scroll
BG1VOFS - BG1 Vertical Scroll
M7VOFS - Mode 7 BG Vertical Scroll
210d ww+++- ww+++- 210e ww+++- ww+++- ------xx xxxxxxxx ---mmmmm mmmmmmmm x = The BG offset, 10 bits. m = The Mode 7 BG offset, 13 bits two's-complement signed.
These are actually two registers in one (or would that be “4 registers in 2”?). Anyway, writing $210d will write both BG1HOFS which works exactly like the rest of the BGnxOFS registers below ($210f-$2114), and M7HOFS which works with the M7* registers ($211b-$2120) instead.
Modes 0-6 use BG1xOFS and ignore M7xOFS, while Mode 7 uses M7xOFS and ignores BG1HOFS. See the appropriate sections below for details, and note the different formulas for BG1HOFS versus M7HOFS.
BG2HOFS - BG2 Horizontal Scroll
BG2VOFS - BG2 Vertical Scroll
BG3HOFS - BG3 Horizontal Scroll
BG3VOFS - BG3 Vertical Scroll
BG4HOFS - BG4 Horizontal Scroll
BG4VOFS - BG4 Vertical Scroll
210f ww+++- 2110 ww+++- 2111 ww+++- 2112 ww+++- 2113 ww+++- 2114 ww+++- ------xx xxxxxxxx
Note that these are “write twice” registers, first the low byte is written then the high. Current theory is that writes to the register work like this:
BGnHOFS = (Current<<8) | (Prev1&~7) | (Prev2&7); Prev1 = Current; Prev2 = Current; or BGnVOFS = (Current<<8) | Prev1; Prev1 = Current;
Note that there is only one Prev1 shared by all eight BGnxOFS registers, and only one Prev2 shared by the four BGnHOFS registers. These are NOT shared with the M7* registers (not even M7xOFS and BG1xOFS).
x = The BG offset, at most 10 bits (some modes effectively use as few as 8).
Note that all BGs wrap if you try to go past their edges. Thus, the maximum offset value in BG Modes 0-6 is 1023, since you have at most 64 tiles (if x/y of BGnSC is set) of 16 pixels each (if the appropriate bit of BGMODE is set).
Horizontal scrolling scrolls in units of full pixels no matter if we’re rendering a 256-pixel wide screen or a 512-half-pixel wide screen. However, vertical scrolling will move in half-line increments if interlace mode is active.
See the section “BACKGROUNDS” below for details.
M7SEL - Mode 7 Settings
211a wb++?- rc----yx r = Playing field size^ c = Empty space fill, when bit 7 is set: 0 = Transparent. 1 = Fill with character 0. Note that the fill is matrix transformed like all other Mode 7 tiles. x/y = Horizontal/Veritcal mirroring. If the bit is set, flip the 256x256 pixel 'screen' in that direction.
^When clear, the playing field is 1024x1024 pixels (so the tilemap completely fills it). When set, the playing field is much larger, and the ‘empty space’ fill is controlled by bit 6. See the section “BACKGROUNDS” below for details.
M7A - Mode 7 Matrix A (also used with $2134/6)
M7B - Mode 7 Matrix B (also used with $2134/6)
M7C - Mode 7 Matrix C
M7D - Mode 7 Matrix D
211b ww+++- 211c ww+++- 211d ww+++- 211e ww+++- aaaaaaaa aaaaaaaa
Note that these are “write twice” registers, first the low byte is written then the high. Current theory is that writes to the register work like this:
Reg = (Current<<8) | Prev; Prev = Current;
Note that there is only one Prev shared by all these registers. This Prev is NOT shared with the BGnxOFS registers, but it IS shared with the M7xOFS registers. These set the matrix parameters for Mode 7. The values are an 8-bit fixed point, i.e. the value should be divided by 256.0 when used in calculations. See below for more explanation. The product A*(B>>8)
may be read from registers $2134/6. There is supposedly no important delay. It may not be operative during Mode 7 rendering. See the section “BACKGROUNDS” below for details.
M7X - Mode 7 Center X
M7Y - Mode 7 Center Y
211f ww+++- 2120 ww+++- ---xxxxx xxxxxxxx
Note that these are “write twice” registers, like the other M7* registers. See above for the write semantics. The value is 13 bit two’s-complement signed. The matrix transformation formula is:
[ X ] [ A B ] [ SX + M7HOFS - CX ] [ CX ] [ ] = [ ] * [ ] + [ ] [ Y ] [ C D ] [ SY + M7VOFS - CY ] [ CY ]
Note: SX/SY are screen coordinates. X/Y are coordinates in the playing field from which the pixel is taken. If $211a bit 7 is clear, the result is then restricted to 0<=X<=1023
and 0<=Y<=1023
. If $211a bits 6 and 7 are both set and X or Y is less than 0 or greater than 1023, use the low 3 bits of each to choose the pixel from character 0. The bit-accurate formula seems to be something along the lines of:
#define CLIP(a) (((a)&0x2000)?((a)|~0x3ff):((a)&0x3ff)) X[0,y] = ((A*CLIP(HOFS-CX))&~63) + ((B*y)&~63) + ((B*CLIP(VOFS-CY))&~63) + (CX<<8) Y[0,y] = ((C*CLIP(HOFS-CX))&~63) + ((D*y)&~63) + ((D*CLIP(VOFS-CY))&~63) + (CY<<8) X[x,y] = X[x-1,y] + A Y[x,y] = Y[x-1,y] + C (In all cases, X[] and Y[] are fixed point with 8 bits of fraction)
See the section “BACKGROUNDS” below for details.
W12SEL - Window Mask Settings for BG1 and BG2
W34SEL - Window Mask Settings for BG3 and BG4
WOBJSEL - Window Mask Settings for OBJ and Color Window
2123 wb+++- 2124 wb+++- 2125 wb+++- ABCDabcd d = Window 1 Inversion for BG1/BG3/OBJ c = Enable window 1 for BG1/BG3/OBJ b = Window 2 Inversion for BG1/BG3/OBJ a = Enable window 2 for BG1/BG3/OBJ D = Window 1 Inversion for BG2/BG4/Color^^ C = Enable window 1 for BG2/BG4/Color^ B = Window 2 Inversion for BG2/BG4/Color^^ A = Enable window 2 for BG2/BG4/Color^
^When the bit is set, the corresponding window will affect the corresponding background (subject to the settings of $212e/f).
^^When the bit is set, “W” should be replaced by “~W” (not-W) in the window combination formulae below. See the section “WINDOWS” below for more details.
WH0 - Window 1 Left Position
WH1 - Window 1 Right Position
WH2 - Window 2 Left Position
WH3 - Window 2 Right Position
2126 wb+++- 2127 wb+++- 2128 wb+++- 2129 wb+++- xxxxxxxx
These set the offset of the appropriate edge of the appropriate window. Note that if the left edge is greater than the right edge, the window is considered to have no range at all (and thus “W” always is false). See the section “WINDOWS” below for more details.
WBGLOG - Window mask logic for BGs
WOBJLOG - Window mask logic for OBJs and Color Window
212a wb+++- 44332211 212b wb+++- ----ccoo 44/33/22/11/oo/cc = Mask logic for BG1/BG2/BG3/BG4/OBJ/Color This specified the window combination method, using standard boolean operators: 00 = OR 01 = AND 10 = XOR 11 = XNOR
Consider two variables, W1 and W2, which are true for pixels between the appropriate left and right bounds as set in $2126-$2129 and false otherwise. Then, you have the following possibilities: (replace “W#” with “~W#”, depending on the Inversion settings of $2123-$2125) Neither window enabled => nothing masked. One window enabled => Either W1 or W2, as appropriate. Both windows enabled => W1 op W2, where “op” is as above. Where the function is true, the BG will be masked. See the section “WINDOWS” below for more details.
TM - Main Screen Designation
TS - Subscreen Designation
212c wb+++- 212d wb+++- ---o4321 1/2/3/4/o = Enable BG1/BG2/BG3/BG4/OBJ for display on the main (or sub) screen.
See the section “BACKGROUNDS” below for details.
TMW - Window Mask Designation for the Main Screen
TSW - Window Mask Designation for the Subscreen
212e wb+++- 212f wb+++- ---o4321 1/2/3/4/o = Enable window masking for BG1/BG2/BG3/BG4/OBJ on the main (or sub) screen.
See the section “BACKGROUNDS” below for details.
CGWSEL - Color Addition Select
2130 wb+++- ccmm--sd cc = Clip colors to black before math 00 => Never 01 => Outside Color Window only 10 => Inside Color Window only 11 => Always mm = Prevent color math 00 => Never 01 => Outside Color Window only 10 => Inside Color Window only 11 => Always s = Add subscreen (instead of fixed color) d = Direct color mode for 256-color BGs
See the sections “BACKGROUNDS”, “WINDOWS”, and “RENDERING THE SCREEN” below for details.
CGADSUB - Color math designation
2131 wb+++- shbo4321 s = Add/subtract select 0 => Add the colors 1 => Subtract the colors h = Half color math.^ 4/3/2/1/o/b = Enable color math on BG1/BG2/BG3/BG4/OBJ/Backdrop
^ When set, the result of the color math is divided by 2 (except when $2130 bit 1 is set and the fixed color is used, or when color is clipped). See the sections “BACKGROUNDS”, “WINDOWS”, and “RENDERING THE SCREEN” below for details.
COLDATA - Fixed Color Data
2132 wb+++- bgrccccc b/g/r = Which color plane(s) to set the intensity for. ccccc = Color intensity.
So basically, to set an orange you’d do something along the lines of: LDA #$3f STA $2132 LDA #$4f STA $2132 LDA #$80 STA $2132
See the sections “BACKGROUNDS” and “WINDOWS” below for details.
SETINI - Screen Mode/Video Select
2133 wb+++- se--poIi s = "External Sync".^ e = Mode 7 EXTBG ("Extra BG").^^ p = Enable pseudo-hires mode.^^^ o = Overscan mode.^^^^ I = OBJ Interlace.^^^^^ i = Screen interlace.^^^^^^
^Used for superimposing “sfx” graphics, whatever that means. Usually 0. Not much is known about this bit. Interestingly, the SPPU1 chip has a pin named “EXTSYNC” (or not-EXTSYNC, since it has a bar over it) which is tied to Vcc.
^^When this bit is set, you may enable BG2 on Mode 7. BG2 uses the same tile and character data as BG1, but interprets the high bit of the color data as a priority for the pixel. Various sources report additional effects for this bit, possibly related to bit 7. For example, “Enable the Data Supplied From the External Lsi.”, whatever that means. Of course, maybe that’s a typo and it’s supposed to apply to bit 7 instead.
^^^This creates a 512-pixel horizontal resolution by taking pixels from the subscreen for the even-numbered pixels (zero based) and from the main screen for the odd-numbered pixels. Color math behaves just as with Mode 5/6 hires. The interlace bit still has no effect. Mosaic operates as normal (not like Mode 5/6). The ‘subscreen’ pixel is clipped (by windows) when the main-screen pixel to the LEFT is clipped, not when the one to the RIGHT is clipped as you’d expect. What happens with pixel column 0 is unknown. Enabling this bit in Modes 5 or 6 has no effect.
^^^^When set, 239 lines will be displayed instead of the normal 224. This also means V-Blank will occur that much later, and be shorter. All that happens is that extra lines get added to the display, and it seems the TV will like to move the display up 8 pixels. Overscan: The bit only matters at the very end of the frame, if you change the setting on line 0xE0 before the normal NMI trigger point then it’s the same as if you had it on all frame. Note that this affects both the NMI trigger point and when HDMA stops for the frame. If you turn the bit off at the very beginning of scanline X (for 0xE1<=X<=0xF0), NMI will occur on line X and the last HDMA transfer will occur on line X-1. However, on my TV at least, the display will remain in the normal no-overscan position for lines E1-EC, it will move up only one pixel for line ED, and it will lose vertical sync for lines EF-F4! Turning the bit on, only line E1 gives any effect: NMI will occur on line E2, although the last HDMA will still occur on line E0. Anything else acts like you left the bit off the whole time. Note, however, that if you wait too long after the beginning of the scanline then you will get no effect. Even if there is no visible effect, the overscan setting still affects VRAM writes. In particular, executing LDA #'-' / STA $2118 / LDA r2133 / STA $2133 / LDA #'+' / STA $2118
during the E1-F0 period will write only + or only - to VRAM, depending on whether the overscan bit was set to 0 or 1.
^^^^^When set regardless of BG mode, the OBJ will be interlaced (see bit 0 below), and thus will appear half-height. Note that this only controls whether obj are drawn as normal or not; the interlace signal is only output to the TV based on bit 0 below.
^^^^^^When set in BG mode 5 (and probably 6), the effective screen height will be 448 (or 478) pixles, rather than 224 (or 239). When set in any other mode, the screen will just get a bit jumpy. However, toggling the tilemap each field would simulate the increased screen height (much like pseudo-hires simulares hires). In hardware, setting this bit makes the SNES output a normal interlace signal rather than always forcing one frame.
See the sections “BACKGROUNDS” and “SPRITES” below for details.
MPYL - Multiplication Result low byte
MPYM - Multiplication Result middle byte
MPYH - Multiplication Result high byte
2134 r l+++? 2135 r m+++? 2136 r h+++? xxxxxxxx xxxxxxxx xxxxxxxx
This is the 2’s compliment product of the 16-bit value written to $211b and the 8-bit value most recently written to $211c. There is supposedly no important delay. It may not be operative during Mode 7 rendering.
SLHV - Software Latch for H/V Counter
2137 b++++ --------
When read, the H/V counter (as read from $213c and $213d) will be latched to the current X and Y position if bit 7 of $4201 is set. The data actually read is open bus.
OAMDATAREAD* - Data for OAM read
2138 r w++?- xxxxxxxx
OAM reads are straightforward: the current byte as set in $2102/3 and incremented by reads from this register and writes to $2104 will be returned. Note that writes to the lower table are not affected so logically. See register $2104 and the section “SPRITES” below for details. Also, note that OAM address invalidation probably affects the address read by this register as well.
VMDATALREAD* - VRAM Data Read low byte
VMDATAHREAD* - VRAM Data Read high byte
2139 r l++?- 213a r h++?- xxxxxxxx xxxxxxxx
Simply, this reads data from VRAM. The address is incremented when either $2139 or $213a is read, depending on the setting of bit 7 of $2115. Actually, the reading is more complex. When either of these registers is read, the appropriate byte from a word-sized buffer is returned. A word from VRAM is loaded into this buffer just before the VRAM address is incremented. The actual data read and the amount of the increment depend on the low 4 bits of $2115. The effect of this is that a ‘dummy read’ is required after setting $2116-7 before you start getting the actual data. The interaction between these registers and $2118/9 is unknown. See the sections “BACKGROUNDS” and “SPRITES” below for details.
CGDATAREAD* - CGRAM Data read
213b r w++?- -bbbbbgg gggrrrrr
This reads from CGRAM. Accesses to CGRAM are handled just like accesses to the low table of OAM, see $2138 for details. Note that the color values are stored in BGR order. The ‘-’ bit is PPU2 Open Bus.
OPHCT - Horizontal Scanline Location
OPVCT - Vertical Scanline Location
213c r w++++ 213d r w++++ -------x xxxxxxxx
These values are latched by reading $2137 when bit 7 of $4201 is set, or by clearing-and-setting bit 7 of $4201 either by writing $4201 or by pin 6 of Controller Port 2 (the latch occurs on the 1->0 transition). Note that the value read is only 9 bits: bits 1-7 of the high byte are PPU2 Open Bus. Each register keeps seperate track of whether to return the low or high byte. The high/low selector is reset to ‘low’ when $213f is read (the selector is NOT reset when the counter is latched). H Counter values range from 0 to 339, with 22-277 being visible on the screen. V Counter values range from 0 to 261 in NTSC mode (262 is possible every other frame when interlace is active) and 0 to 311 in PAL mode (312 in interlace?), with 1-224 (or 1-239(?) if overscan is enabled) visible on the screen.
STAT77 - PPU Status Flag and Version
213e r b++++ trm-vvvv t = Time Over Flag.^ r = Range Over Flag.^^ m = "Master/slave mode select".^^^ - = PPU1 Open Bus. vvvv = 5c77 chip version number. So far, we've only encountered version 1.
^If more than 34 sprite-tiles (e.g. a 16x16 sprite has 2 sprite-tiles) were encountered on a single line, this flag will be set. The flag is reset at the end of V-Blank. See the section “SPRITES” below for details.
^^If more than 32 sprites were encountered on a single line, this flag will be set. The flag is reset at the end of V-Blank. See the section “SPRITES” below for details. Note that the above two flags are set whether or not OBJ are actually enabled at the time.
^^^Little is known about this bit. Current theory is that it indicates the status of the “MASTER” pin on the S-PPU1 chip, which in the normal SNES is always Gnd. We always seem to read back 0 here.
STAT78 - PPU Status Flag and Version
213f r b++++ fl-pvvvv f = Interlace Field.^ l = External latch flag.^^ - = PPU2 Open Bus. p = NTSC/Pal Mode.^^^ vvvv = 5C78 chip version number. So far, we've encountered at least 2 and 3. Possibly 1 as well.
^This will toggle every V-Blank.
^^When the PPU counters are latched, this flag gets set. The flag is reset on read, but only when $4201 bit 7 is set.
^^^If this is a PAL SNES, this bit will be set, otherwise it will be clear.
Note: as a side effect of reading this register, the high/low byte selector for $213c/d is reset to ‘low’.