-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathppu.lua
284 lines (252 loc) · 6.79 KB
/
ppu.lua
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
function ppu_init(bitops)
-- Lookup table to map 8b -> 16b with every second bit 0, for merging tile bit planes
local tbl_expand = {}
for i = 0, 255 do
local expand = 0
local shift = i
-- Go from MSB to LSB
for bit = 1, 8 do
expand = expand * 4
if shift >= 0x80 then
expand = expand + 1
shift = shift - 0x80
end
shift = shift * 2
end
tbl_expand[1+i] = expand
end
assert(tbl_expand[1+0x00] == 0x0000)
assert(tbl_expand[1+0x0F] == 0x0055)
assert(tbl_expand[1+0xF0] == 0x5500)
assert(tbl_expand[1+0xFF] == 0x5555)
-- PPU registers
local reg_lcdc, reg_scx, reg_scy, reg_bgp = 0, 0, 0, 0
local reg_obp0, reg_obp1 = 0, 0
local reg_stat, reg_lyc = 0, 0
local reg_wx, reg_wy = 0, 0
-- PPU state
local ly, mode = 0, 1
local ret = {}
-- Draw tile with given palette (nil = transparent)
-- at given offset (can be out of bounds)
function ret.draw_tile(vram, tile, fb, x, y, c0, c1, c2, c3, mirror_h, mirror_v)
local tile_addr = 1 + tile * 0x10
local y_start, y_end, y_step
if mirror_v then
y_start, y_end, y_step = y + 1, y, -1
else
y_start, y_end, y_step = y, y + 7, 1
end
local x_start, x_end, x_step
if mirror_h then
x_start, x_end, x_step = x + 7, x, -1
else
x_start, x_end, x_step = x, x + 7, 1
end
local tbl_expand_l = tbl_expand
for ty = y_start, y_end, y_step do
local pxdata = tbl_expand_l[1+vram[tile_addr]] + 2 * tbl_expand_l[1+vram[tile_addr+1]]
for tx = x_start, x_end, x_step do
local c
if pxdata >= 0xC000 then
c = c3
pxdata = pxdata - 0xC000
elseif pxdata >= 0x8000 then
c = c2
pxdata = pxdata - 0x8000
elseif pxdata >= 0x4000 then
c = c1
pxdata = pxdata - 0x4000
else
c = c0
end
if tx >= 0 and tx < 160 and ty >= 0 and ty <= 144 and c then
fb[1 + ty * 160 + tx] = c
end
pxdata = pxdata * 4
end
tile_addr = tile_addr + 2
end
end
local function split_palette(reg)
local c0 = reg % 4
reg = (reg - c0) / 4
local c1 = reg % 4
reg = (reg - c1) / 4
local c2 = reg % 4
reg = (reg - c2) / 4
local c3 = reg % 4
return c0, c1, c2, c3
end
function ret.draw_tilemap(vram, oam, fb)
local bgp_0, bgp_1, bgp_2, bgp_3 = split_palette(reg_bgp)
-- Draw the background
local vram_offset = 0x1801
if bitops.tbl_and[0x0801 + reg_lcdc] ~= 0 then
vram_offset = 0x1C01
end
local signed_addr_mode = bitops.tbl_and[0x1001 + reg_lcdc] == 0
local tile_x_start, tile_y = math.floor(reg_scx / 8), math.floor(reg_scy / 8)
local x, y = -(reg_scx % 8), -(reg_scy % 8)
for y = y, 144, 8 do
local tile_x = tile_x_start
for x = x, 168, 8 do
local tile = vram[vram_offset + tile_x + tile_y * 32]
if signed_addr_mode and tile < 128 then
tile = tile + 256
end
ret.draw_tile(vram, tile, fb, x, y, bgp_0, bgp_1, bgp_2, bgp_3)
if tile_x == 31 then
tile_x = 0 -- x wraparound
else
tile_x = tile_x + 1
end
end
if tile_y == 31 then
tile_y = 0 -- y wraparound
else
tile_y = tile_y + 1
end
end
-- Draw the window
if bitops.tbl_and[0x2001 + reg_lcdc] ~= 0 then
vram_offset = 0x1801
if bitops.tbl_and[0x4001 + reg_lcdc] ~= 0 then
vram_offset = 0x1C01
end
local tile_y = 0
x, y = reg_wx - 7, reg_wy
for y = y, 144, 8 do
local tile_x = 0
for x = x, 168, 8 do
local tile = vram[vram_offset + tile_x + tile_y * 32]
if signed_addr_mode and tile < 128 then
tile = tile + 256
end
ret.draw_tile(vram, tile, fb, x, y, bgp_0, bgp_1, bgp_2, bgp_3)
if tile_x == 31 then
tile_x = 0 -- x wraparound
else
tile_x = tile_x + 1
end
end
if tile_y == 31 then
tile_y = 0 -- y wraparound
else
tile_y = tile_y + 1
end
end
end
-- Draw objects
local mode8x16 = bitops.tbl_and[0x0401 + reg_lcdc] ~= 0
local obp0_0, obp0_1, obp0_2, obp0_3 = split_palette(reg_obp0)
local obp1_0, obp1_1, obp1_2, obp1_3 = split_palette(reg_obp1)
for oam_offset = 1, 0xA0, 4 do
local y, x = oam[oam_offset], oam[oam_offset+1]
if y > 0 and y < 160 and x > 0 and x < 168 then
local tile, flags = oam[oam_offset+2], oam[oam_offset+3]
local mirror_h, mirror_v = bitops.tbl_and[0x2001 + flags] ~= 0, bitops.tbl_and[0x4001 + flags] ~= 0
local use_obp0 = bitops.tbl_and[0x1001 + flags] == 0
if use_obp0 then
ret.draw_tile(vram, tile, fb, x - 8, y - 16, nil, obp0_1, obp0_2, obp0_3, mirror_h, mirror_v)
else
ret.draw_tile(vram, tile, fb, x - 8, y - 16, nil, obp1_1, obp1_2, obp1_3, mirror_h, mirror_v)
end
if mode8x16 then
if use_obp0 then
ret.draw_tile(vram, tile + 1, fb, x - 8, y - 8, nil, obp0_1, obp0_2, obp0_3, mirror_h, mirror_v)
else
ret.draw_tile(vram, tile + 1, fb, x - 8, y - 8, nil, obp1_1, obp1_2, obp1_3, mirror_h, mirror_v)
end
end
end
end
end
function ret.read_byte(address)
--print(string.format("PPU read %04x", address))
if address == 0xFF40 then
return reg_lcdc
elseif address == 0xFF41 then
return reg_stat + mode
elseif address == 0xFF42 then
return reg_scy
elseif address == 0xFF43 then
return reg_scx
elseif address == 0xFF44 then
return ly
elseif address == 0xFF45 then
return reg_lyc
elseif address == 0xFF47 then
return reg_bgp
elseif address == 0xFF48 then
return reg_obp0
elseif address == 0xFF49 then
return reg_obp1
elseif address == 0xFF4A then
return reg_wy
elseif address == 0xFF4B then
return reg_wx
else
warn(string.format("UNIMPL: PPU read %04x", address))
return 0
end
end
function ret.write_byte(address, value)
--print(string.format("PPU write %04x %02x", address, value))
if address == 0xFF40 then
reg_lcdc = value
elseif address == 0xFF41 then
reg_stat = bitops.tbl_and[0x7801 + value]
if reg_stat ~= 0 then
warn(string.format("UNIMPL: STAT value %02x", reg_stat))
end
elseif address == 0xFF42 then
reg_scy = value
elseif address == 0xFF43 then
reg_scx = value
elseif address == 0xFF45 then
reg_lyc = value
elseif address == 0xFF47 then
reg_bgp = value
elseif address == 0xFF48 then
reg_obp0 = value
elseif address == 0xFF49 then
reg_obp1 = value
elseif address == 0xFF4A then
reg_wy = value
elseif address == 0xFF4B then
reg_wx = value
else
warn(string.format("UNIMPL: PPU write %04x %02x", address, value))
end
end
function ret.next_line(mem, fb)
if reg_lcdc < 0x80 then
-- PPU off
ly = 0
return
end
if ly == 144 then
-- Begin of vblank
mem.raise_irq(1)
ret.draw_tilemap(mem.vram, mem.oam, fb)
end
if ly >= 144 then
mode = 1 -- vblank
else
mode = 0 -- hblank. mode 2 and 3 not exposed.
end
if ly == 153 then
ly = 0
else
ly = ly + 1
end
if ly == reg_lyc then
mode = mode + 4 -- LYC == LY bit in STAT
if reg_stat >= 0x40 then
mem.raise_irq(2)
end
end
end
return ret
end