/
ppu_rendering.go
141 lines (120 loc) · 3.53 KB
/
ppu_rendering.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
package nes
import (
"image"
"image/color"
)
// Step is in charge of rendering the output of the PPU
func (ppu *PPU) Step() {
// PREPARE FOR RENDERING
if ppu.scanline >= -1 && ppu.scanline < 240 {
if ppu.scanline == 0 && ppu.cycle == 0 {
ppu.cycle = 1
}
// RESET VARIABLES TO START THE RENDERING
if ppu.scanline == -1 && ppu.cycle == 1 {
ppu.statusRegister.VerticalBlank = 0
ppu.statusRegister.SpriteOverflow = 0
ppu.statusRegister.SpriteZeroHit = 0
ppu.spriteRenderInfo.resetShifters()
}
// BackgroundRenderingCycle gets&updates the variables to render the next background tile
ppu.backgroundRenderingCycle()
// Sprite rendering cycle
ppu.foregroundRenderingCycle()
}
if ppu.scanline >= 241 && ppu.scanline < 261 {
if ppu.scanline == 241 && ppu.cycle == 1 {
ppu.statusRegister.VerticalBlank = 1
if ppu.controlRegister.EnableNMI != 0 {
ppu.nmiSignaled = true
}
}
}
// Render background pixel & palette
pixel, palette := ppu.renderPixel()
// Draw it!
color := ppu.colorFromPalette(palette, pixel)
ppu.renderedTexture.SetRGBA(int(ppu.cycle-1), int(ppu.scanline), *color)
ppu.cycle++
if ppu.cycle >= 341 {
ppu.cycle = 0
ppu.scanline++
if ppu.scanline >= 261 {
ppu.scanline = -1
ppu.frameComplete = true
}
}
}
func (ppu *PPU) renderPixel() (uint8, uint8) {
bgPixel, bgPalette := ppu.renderBackgroundPixel()
fgPixel, fgPalette, fgPriority := ppu.renderForegroundPixel()
// Combine output
pixel := uint8(0)
palette := uint8(0)
if bgPixel == 0 && fgPixel == 0 {
// 0 - nothing
} else if bgPixel == 0 && fgPixel > 0 { // Foreground
pixel = fgPixel
palette = fgPalette
} else if bgPixel > 0 && fgPixel == 0 { // Background
pixel = bgPixel
palette = bgPalette
} else if bgPixel > 0 && fgPixel > 0 {
// Priority decides
if fgPriority {
pixel = fgPixel
palette = fgPalette
} else {
pixel = bgPixel
palette = bgPalette
}
if ppu.spriteRenderInfo.spriteZeroHitPossible && ppu.spriteRenderInfo.spriteZeroRendered {
if ppu.isRendering() {
if !(ppu.maskRegister.RenderBackgroundLeft != 0 || ppu.maskRegister.RenderSpritesLeft != 0) {
if ppu.cycle >= 9 && ppu.cycle < 258 {
ppu.statusRegister.SpriteZeroHit = 1
}
} else {
if ppu.cycle >= 1 && ppu.cycle < 258 {
ppu.statusRegister.SpriteZeroHit = 1
}
}
}
}
}
return pixel, palette
}
func (ppu *PPU) patternTable(idx uint8, paletteID uint8) *image.RGBA {
for yTile := uint16(0); yTile < 16; yTile++ {
for xTile := uint16(0); xTile < 16; xTile++ {
offset := (yTile * 256) + (xTile * 16)
for row := uint16(0); row < 8; row++ {
tileLSB := ppu.PPURead(uint16(idx)*0x1000+offset+row+0, false)
tileMSB := ppu.PPURead(uint16(idx)*0x1000+offset+row+8, false)
for col := uint16(0); col < 8; col++ {
pixel := (tileLSB & 0x01) | (tileMSB&0x01)<<1
tileLSB >>= 1
tileMSB >>= 1
x := (xTile * 8) + (7 - col)
y := (yTile * 8) + row
color := ppu.colorFromPalette(paletteID, pixel)
ppu.spritePatternTableTextures[idx].SetRGBA(
int(x),
int(y),
*color,
)
}
}
}
}
return ppu.spritePatternTableTextures[idx]
}
func (ppu *PPU) colorFromPalette(paletteID uint8, pixel uint8) *color.RGBA {
address := uint16(0x3F00) + uint16(paletteID)<<2 + uint16(pixel)
idx := ppu.PPURead(address, false)
return &ppu.colorPalette[idx&0x3F]
}
// isRendering returns true if background and sprites should be rendered
func (ppu *PPU) isRendering() bool {
return ppu.maskRegister.RenderBackground != 0 || ppu.maskRegister.RenderSprites != 0
}