This repository has been archived by the owner on Nov 16, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ppu.py
193 lines (166 loc) · 6.93 KB
/
ppu.py
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# from memory import Memory
from register import Register
from display import palette_data
import c_render
import numpy as np
class PPU(object):
def __init__(self, file):
self.pseudo = 0
self._vram_addr_write_twice = 0
self.vmirroring = file.header.vmirroring
self.pixels = np.array([[0 for _ in range(240)] for _ in range(256)])
# self.pattern_tables = Memory(0x2000)
self.pattern_tables = list(file.chr_rom)
self.name_tables = [0 for _ in range(0x800)]
self.palette = [0 for _ in range(0x20)]
self.oam = [0 for _ in range(0x256)]
self.ppu_ctrl = Register(0) # $2000
self.ppu_mask = Register(0) # $2001
self.ppu_status = Register(0) # $2002
self.oam_addr = 0 # $2003
self.oam_data = 0 # $2004
self.ppu_scroll = 0 # $2005
self.ppu_addr = 0 # $2006
self.ppu_data = 0 # $2007
self.oam_dma = 0 # 4014
def print(self):
print(" P_CTRL:" + hex(self.ppu_ctrl.value),
" P_MASK:" + hex(self.ppu_mask.value),
" P_STATUS:" + hex(self.ppu_status.value),
" OAM_ADDR:" + hex(self.oam_addr),
" P_SCROLL:" + hex(self.ppu_scroll),
" P_ADDR:" + hex(self.ppu_addr), end=''
)
def read_register(self, addr):
if addr == 0x2002:
ret = self.ppu_status.value
self.ppu_status.bit7 = 0
return ret
elif addr == 0x2004:
ret = self.oam[self.oam_addr]
self.oam_addr += 1
return ret
elif addr == 0x2007:
return self.read_vram()
else:
assert 0, "Error address! Can't read PPU register"
def write_register(self, addr, data):
if addr == 0x2000:
self.ppu_ctrl.value = data
elif addr == 0x2001:
self.ppu_mask.value = data
elif addr == 0x2003:
self.oam_addr = data
elif addr == 0x2004:
self.oam[self.oam_addr] = data
self.oam_addr += 1
elif addr == 0x2005:
if self._vram_addr_write_twice & 1:
self.ppu_scroll = self.ppu_scroll & 0xff00 | data
else:
self.ppu_scroll = self.ppu_scroll & 0xff | data << 8
self._vram_addr_write_twice += 1
elif addr == 0x2006:
if self._vram_addr_write_twice & 1:
self.ppu_addr = self.ppu_addr & 0xff00 | data
else:
self.ppu_addr = self.ppu_addr & 0xff | data << 8
self._vram_addr_write_twice += 1
elif addr == 0x2007:
self.write_vram(data)
else:
assert 0, "Error address! Can't write PPU register $2002"
def read_vram(self):
addr = self.ppu_addr
self.ppu_addr += (32 if self.ppu_ctrl.bit2 else 1)
if 0 <= addr < 0x3f00:
ret = self.pseudo
if 0 <= addr < 0x2000:
self.pseudo = self.pattern_tables[addr]
else:
if self.vmirroring:
self.pseudo = self.name_tables[addr & 0x7ff]
else:
self.pseudo = self.name_tables[addr & 0x3ff | (addr & 0x800) >> 1]
return ret
elif 0x3f00 <= addr < 0x4000:
index = (addr - 0x3f00) % 0x20
return self.palette[index]
else:
assert 0, "Error PPU addr"
def write_vram(self, data):
addr = self.ppu_addr
self.ppu_addr += (32 if self.ppu_ctrl.bit2 else 1)
if 0 <= addr < 0x2000:
self.pattern_tables[addr] = data
elif 0x2000 <= addr < 0x3000:
if self.vmirroring:
self.name_tables[addr & 0x7ff] = data
else:
self.name_tables[addr & 0x3ff | (addr & 0x800) >> 1] = data
elif 0x3f00 <= addr < 0x4000:
index = (addr - 0x3f00) % 0x20
if index % 4:
self.palette[index] = data
elif index == 0 or index == 0x10:
self.palette[::4] = [data for _ in range(8)]
# print(self.palette.memory)
else:
assert 0, "Error PPU addr"
def render_sprites(self):
pattern_base = 0x1000 if self.ppu_ctrl.bit3 else 0
is_8x16_mode = self.ppu_ctrl.bit5
for i in range(63, -1, -1):
data = self.oam[i * 4: i * 4 + 4]
x, y, attr, pattern_index = data[3], data[0] + 1, data[2], data[1]
if y > 0xEF:
continue
if is_8x16_mode:
pattern1 = pattern_index // 2 * 0x20 + (0x1000 if pattern_index % 2 else 0)
pattern2 = pattern1 + 16
else:
pattern1 = pattern_base | pattern_index * 16
pattern2 = pattern_base | pattern1 + 8
high = (attr & 3) << 2
for yy in range(16 if is_8x16_mode else 8):
if y + yy > 239:
continue
for xx in range(8):
p0 = self.pattern_tables[pattern1 + yy]
p1 = self.pattern_tables[pattern2 + yy]
shift = (~xx) & 0x7
mask = 1 << shift
low = ((p0 & mask) >> shift) | ((p1 & mask) >> shift << 1)
if low == 0:
continue
if x + xx > 255:
continue
self.pixels[x + xx, y + yy] = palette_data[self.palette[0x10 + high | low]]
def render_background(self):
name_table_index = 0 # TODO
pattern_base = 0x1000 if self.ppu_ctrl.bit4 else 0
for y in range(240):
for x in range(256):
tile_id = (x >> 3) + (y >> 3) * 32
pattern_tables_id = self.name_tables[tile_id + name_table_index * 0x400]
pattern1 = pattern_tables_id * 16 | pattern_base
pattern2 = pattern1 + 8 | pattern_base
offset = y & 0x7
p0 = self.pattern_tables[pattern1 + offset]
p1 = self.pattern_tables[pattern2 + offset]
shift = (~x) & 0x7
mask = 1 << shift
low = ((p0 & mask) >> shift) | ((p1 & mask) >> shift << 1)
aid = (x >> 5) + (y >> 5) * 8
attr = self.name_tables[name_table_index * 0x400 + aid + (32 * 30)]
aoffset = ((x & 0x10) >> 3) | ((y & 0x10) >> 2)
high = (attr & (3 << aoffset)) >> aoffset << 2
index = high | low
self.pixels[x, y] = palette_data[self.palette[index]]
# name_table_index = 0 # TODO
# pattern_base = 0x1000 if self.ppu_ctrl.bit4 else 0
def render_background_1(self):
c_render.render_background_cython(self.pattern_tables, self.name_tables, self.palette, self.ppu_ctrl.bit4,
self.pixels)
# name_table_index = 0 # TODO
# pattern_base = 0x1000 if self.ppu_ctrl.bit4 else 0