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 }