-
Notifications
You must be signed in to change notification settings - Fork 2
/
fetcher.go
171 lines (151 loc) · 5.02 KB
/
fetcher.go
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package ppu
import (
"github.com/lazy-stripes/goholint/memory"
"github.com/lazy-stripes/goholint/ppu/states"
)
// Fetcher reads tile data from VRAM and pushes pixels to PPU FIFO.
type Fetcher struct {
Enabled bool
fifo *FIFO
vRAM memory.Addressable
oamRAM memory.Addressable
ticks int
state, oldState states.State
lcdc *uint8 // Reference to LCDC for sprites height bit
mapAddr uint16 // Start address of BG/Windows map row
dataAddr uint16 // Start address of Sprite/BG tile data
tileOffset uint8 // X offset in the tile map row (will wrap around)
tileLine uint8 // Y offset (in pixels) in the tile
signedID bool
tileID uint8
tileData [8]uint8
sprite Sprite // Stores X, Y and address in OAM
spriteID uint8
spriteFlags uint8
spriteOffset uint8 // X offset for sprite (if not fully on screen)
spriteLine uint8 // Y offset (in pixels) in the sprite
spriteData [8]uint8
}
// NewFetcher creates a pixel fetcher instance that can read directly from
// video and OAM RAM.
func NewFetcher(ppu *PPU) *Fetcher {
f := Fetcher{
fifo: &ppu.FIFO,
lcdc: &ppu.LCDC,
vRAM: ppu.VRAM.RAM,
oamRAM: ppu.OAM.RAM,
}
return &f
}
// Start fetching a line of pixels from the given tile in the given tilemap
// address space when Tick() is called.
func (f *Fetcher) Start(mapAddr, dataAddr uint16, tileOffset, tileLine uint8, signedID bool) {
f.mapAddr, f.dataAddr = mapAddr, dataAddr
f.tileOffset, f.tileLine = tileOffset, tileLine
f.signedID = signedID
f.state = states.ReadTileID
f.Enabled = true
f.fifo.Clear()
}
// FetchSprite pauses the current fetching state to read sprite data and mix it
// in the pixel FIFO.
func (f *Fetcher) FetchSprite(sprite Sprite, spriteOffset, spriteLine uint8) {
f.sprite = sprite
f.spriteOffset, f.spriteLine = spriteOffset, spriteLine
f.oldState = f.state
f.state = states.ReadSpriteID
}
// Tick advances the fetcher's state machine one step.
func (f *Fetcher) Tick() {
if !f.Enabled {
return
}
f.ticks++
if f.ticks < ClockFactor {
return
}
// Reset tick counter and execute next state
f.ticks = 0
switch f.state {
case states.ReadTileID:
f.tileID = f.vRAM.Read(f.mapAddr + uint16(f.tileOffset))
f.state = states.ReadTileData0
//logger.Printf("fetcher", "%04x: %02x\n", f.mapAddr+uint(f.tileOffset), f.tileID)
case states.ReadTileData0:
f.ReadTileLine(0, f.dataAddr, f.tileID, f.signedID, f.tileLine, 0, &f.tileData)
f.state = states.ReadTileData1
case states.ReadTileData1:
f.ReadTileLine(1, f.dataAddr, f.tileID, f.signedID, f.tileLine, 0, &f.tileData)
f.state = states.PushToFIFO
case states.PushToFIFO:
if f.fifo.Size() <= 8 {
for i := 0; i < 8; i++ {
f.fifo.Push(Pixel{f.tileData[i], PixelBGP, false})
}
f.tileOffset = (f.tileOffset + 1) % 32
f.state = states.ReadTileID
}
case states.ReadSpriteID:
// Read directly from OAM RAM.
f.spriteID = f.oamRAM.Read(f.sprite.Address + 2) // We already read X&Y
f.state = states.ReadSpriteFlags
case states.ReadSpriteFlags:
f.spriteFlags = f.oamRAM.Read(f.sprite.Address + 3)
f.state = states.ReadSpriteData0
case states.ReadSpriteData0:
f.ReadTileLine(0, 0x8000, f.spriteID, false, f.spriteLine, f.spriteFlags, &f.spriteData)
f.state = states.ReadSpriteData1
case states.ReadSpriteData1:
f.ReadTileLine(1, 0x8000, f.spriteID, false, f.spriteLine, f.spriteFlags, &f.spriteData)
f.state = states.MixInFIFO
case states.MixInFIFO:
if f.fifo.Size() < 8 {
break
}
// Mix sprite pixels with FIFO, taking into account offset if sprite
// is only partially displayed (i.e. entering screen from the left).
var palette uint8
if f.spriteFlags&0x10 == 0 {
palette = PixelOBP0
} else {
palette = PixelOBP1
}
bgOverObj := f.spriteFlags&SpritePriority != 0
for i := int(f.spriteOffset); i < 8; i++ {
f.fifo.Mix(i-int(f.spriteOffset), Pixel{f.spriteData[i], palette, bgOverObj})
}
f.state = f.oldState
}
}
// ReadTileLine updates internal pixel buffer with LSB or MSB tile line
// depending on current state.
func (f *Fetcher) ReadTileLine(bitPlane uint8, tileDataAddr uint16, tileID uint8, signedID bool, tileLine uint8, flags uint8, data *[8]uint8) {
var offset uint16
if signedID {
offset = uint16(int16(tileDataAddr) + int16(int8(tileID))*16)
} else {
offset = tileDataAddr + (uint16(tileID) * 16)
}
if flags&SpriteFlipY != 0 {
// If flipping, get line at (spriteSize-1-line)
height := uint8(8<<((*f.lcdc&LCDCSpriteSize)>>2) - 1)
tileLine = height - tileLine
}
addr := offset + (uint16(tileLine) * 2)
pixelData := f.vRAM.Read(addr + uint16(bitPlane))
for bitPos := 7; bitPos >= 0; bitPos-- {
var pixelIndex uint
if flags&SpriteFlipX != 0 {
pixelIndex = uint(bitPos)
} else {
pixelIndex = 7 - uint(bitPos)
}
if bitPlane == 0 {
// Least significant bit, replace previous value.
data[pixelIndex] = (pixelData >> uint(bitPos)) & 1
} else {
// Most significant bit, update previous value.
data[pixelIndex] |= ((pixelData >> uint(bitPos)) & 1) << 1
}
}
}