aboutsummaryrefslogtreecommitdiff
path: root/ppu/sprites.go
blob: 83d1703b7ffa589d3a982c8bbb3d32efec7e2bcf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
}