background and sprites priorities

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
background and sprites priorities
by on (#84464)
Earlier today I was looking up how the priority bits work, and I noticed how complicated it is.


BG3 P=1 Mode 1 with bit 3 of $2105 set
sprites P=3
BG1 P=1
sprites P=2 Modes 2,3,4,5,6,7
BG2 P=1
sprites P=2 Modes 0,1
sprites P=1 Modes 2,3,4,5,6,7
BG1 P=0
sprites P=0 Modes 2,3,4,5,6,7
BG2 P=0
sprites P=1 Modes 0,1
BG3 P=1
BG4 P=1
sprites P=0 Modes 0,1
BG3 P=0
BG4 P=0

Has anybody know why it's like this, and what is going on in the sPPU?

by on (#84465)
Well, it's that complicated because they wanted it to be.

As for what goes on inside, here's my guess at it:

PPU1 handles sprites and addressing. PPU2 snoops the data bus to pick up BG data.
PPU2 handles backgrounds, and all the actual screen composition.

PPU1 figures out the sprite priority among sprites, and feeds the 4-bit color, 3-bit palette select, and 2 bit priority for the current pixel to PPU2 via the CHR, PRIO, and COLOR lines as listed on the schematics at neviksti's site.

From there, the ordering isn't that crazy. Two main cases to worry about, 3/4 screen and 1/2 screen.

3/4 screen:
Background color
BG3/4 Pri 0
OBJ 0
BG3/4 Pri 1
OBJ 1
BG1/2 Pri 0
OBJ 2
BG1/2 Pri 1
OBJ3
(BG3 Pri 1 override case)

The $2105.d3 override would be for placing a 16-color HUD in BG3, and having it appear over sprites. Handy for fighting games or whatnot.

1/2 screen:
Background color
BG2 Pri 0
OBJ 0
BG1 Pri 0
OBJ 1
BG2 Pri 1
OBJ 2
BG1 Pri 1
OBJ 3

Internally, PPU2 has 2 priority decoders, one for the main screen, one for the sub screen. The inputs to each of these are the set of BGs and the OBJ, after filtering down based on which ones are enabled for that screen, and based on the window logic.

There's a number of ways the priority logic could be implemented, but a couple of observations simplify things:

Aside from the d3 exception, BG1/2 are always over BG3/4.
Priority 1 BGs are in front of Priority 0 BGs.

First we magic up some signals, where BGx is 1 if that BG is not transparent at this pixel, and m1ex is 1 if we're in mode 1, and bit3 of $2105 is set. Then we come up with a 2 bit select, which will also serve as the priority.

For 2-screen mode, sel[1] = (BG2 & BG2P) | (BG1 & BG1P). sel[0] = (BG1 &BG1P) | (BG1 & ~(BG2 & BG2P)). Feed these in as the selector lines for a 4:1 multiplexer, with the inputs set to (BG2, BG1, BG2, BG1). Feed the output of this selector to the next stage, along with the transparency flag for the selected BG.

For 4-screen mode, we have two of those, one for BG1/2, one for BG3/4. Call the signals for this fsel, ft, bsel, and bt, for the select and the transparency (1=solid, 0=transparent). f is for BG1/2, b is for BG3/4. sel[1] = ft; sel[0] = (ft & fsel[1]) | (~ft & bsel[1]); color output is from the f mux if ft, b mux if not. send the color output, sel, and ft | bt as the transparency flag forward.

Screen color logic would be something like:
Code:
if (BG3 & BG3P & m1ex)
  color <= BG3
else if (OBJ & (OBJP >= sel))
  color <= OBJ
else if (BGT)
  color <= BG
else
  color <= background


There's probably some sneaky way of building a priority encoder that would reduce this stuff down a bit, but the above should be reasonably efficient in hardware.

by on (#84476)
I think I figured out how this works. Each priority layer has a bit determining if the pixel is solid or transparent.

For example, if BG1 p=0 and BG2 p=0 are both solid in the same spot it looks like this:

000000101000000

First you OR each bit with the bit to the left of it.

000000111111111

Then you XOR each bit with the bit to the left of it.

000000100000000

Then you take the bits that belong to the same layer and OR them together:

x0x0x0x0x0xx0xx
sprites = 0

xx0xxx1xxxxxxxx
BG1 = 1

xxxx0xxx0xxxxxx
BG2 = 0

0xxxxxxxxx0xx0x
BG3 = 0

xxxxxxxxxxx0xx0
BG4 = 0

BG1 gets rendered