diff options
Diffstat (limited to 'ppu/sprites.go')
| -rw-r--r-- | ppu/sprites.go | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/ppu/sprites.go b/ppu/sprites.go new file mode 100644 index 0000000..83d1703 --- /dev/null +++ b/ppu/sprites.go @@ -0,0 +1,97 @@ +package ppu + +import "math/bits" + +const ( + spriteAttrPalette = 0x03 + spriteAttrBehind = 0x20 // draw behind opaque background pixels + spriteAttrFlipH = 0x40 + spriteAttrFlipV = 0x80 +) + +type sprite struct { + patternLo, patternHi uint8 + attr uint8 + x uint8 + isSprite0 bool +} + +// ill add comments later im tired +// https://www.nesdev.org/wiki/PPU_sprite_evaluation +func (p *PPU) evaluateSprites() { + p.spriteCount = 0 + // nothing is drawn above line 1, so the pre-render line and the last line leave it empty + if !p.renderingEnabled() || p.Scanline >= ScreenHeight-1 { + return + } + + height := 8 + if p.Ctrl&ctrlSpriteSize16 != 0 { + height = 16 + } + + for i := 0; i < 64; i++ { + row := p.Scanline - int(p.OAM[i*4]) + if row < 0 || row >= height { + continue + } + if p.spriteCount == 8 { + p.spriteOverflow = true + break + } + p.spriteLine[p.spriteCount] = p.fetchSprite(i, row, height) + p.spriteCount++ + } +} + +func (p *PPU) fetchSprite(i, row, height int) sprite { + tile := p.OAM[i*4+1] + attr := p.OAM[i*4+2] + if attr&spriteAttrFlipV != 0 { + row = height - 1 - row + } + + var addr uint16 + if height == 16 { + table := uint16(tile&1) << 12 + tile &^= 1 + if row >= 8 { + tile++ + row -= 8 + } + addr = table | uint16(tile)<<4 | uint16(row) + } else { + var base uint16 + if p.Ctrl&ctrlSpritePatternHigh != 0 { + base = 0x1000 + } + addr = base | uint16(tile)<<4 | uint16(row) + } + + lo := p.readVRAM(addr) + hi := p.readVRAM(addr + 8) + if attr&spriteAttrFlipH != 0 { + lo, hi = bits.Reverse8(lo), bits.Reverse8(hi) + } + return sprite{patternLo: lo, patternHi: hi, attr: attr, x: p.OAM[i*4+3], isSprite0: i == 0} +} + +func (p *PPU) spritePixel(x int) (pixel, palette uint8, behind, isSprite0 bool) { + if p.Mask&maskShowSprites == 0 || (x < 8 && p.Mask&maskShowSpritesLeft == 0) { + return 0, 0, false, false + } + for i := 0; i < p.spriteCount; i++ { + s := &p.spriteLine[i] + offset := x - int(s.x) + if offset < 0 || offset > 7 { + continue + } + shift := 7 - offset + pixel = (s.patternHi>>shift&1)<<1 | (s.patternLo >> shift & 1) + if pixel == 0 { + continue + } + return pixel, s.attr & spriteAttrPalette, s.attr&spriteAttrBehind != 0, s.isSprite0 + } + return 0, 0, false, false +} |