1#include "cartridge.hpp"
2#include "cpu.hpp"
3#include "gui.hpp"
4#include "ppu.hpp"
5
6namespace PPU {
7#include "palette.inc"
8
9
10Mirroring mirroring; // Mirroring mode.
11u8 ciRam[0x800]; // VRAM for nametables.
12u8 cgRam[0x20]; // VRAM for palettes.
13u8 oamMem[0x100]; // VRAM for sprite properties.
14Sprite oam[8], secOam[8]; // Sprite buffers.
15u32 pixels[256 * 240]; // Video buffer.
16
17Addr vAddr, tAddr; // Loopy V, T.
18u8 fX; // Fine X.
19u8 oamAddr; // OAM address.
20
21Ctrl ctrl; // PPUCTRL ($2000) register.
22Mask mask; // PPUMASK ($2001) register.
23Status status; // PPUSTATUS ($2002) register.
24
25// Background latches:
26u8 nt, at, bgL, bgH;
27// Background shift registers:
28u8 atShiftL, atShiftH; u16 bgShiftL, bgShiftH;
29bool atLatchL, atLatchH;
30
31// Rendering counters:
32int scanline, dot;
33bool frameOdd;
34
35inline bool rendering() { return mask.bg || mask.spr; }
36inline int spr_height() { return ctrl.sprSz ? 16 : 8; }
37
38/* Get CIRAM address according to mirroring */
39u16 nt_mirror(u16 addr)
40{
41 switch (mirroring)
42 {
43 case VERTICAL: return addr % 0x800;
44 case HORIZONTAL: return ((addr / 2) & 0x400) + (addr % 0x400);
45 default: return addr - 0x2000;
46 }
47}
48void set_mirroring(Mirroring mode) { mirroring = mode; }
49
50/* Access PPU memory */
51u8 rd(u16 addr)
52{
53 switch (addr)
54 {
55 case 0x0000 ... 0x1FFF: return Cartridge::chr_access<0>(addr); // CHR-ROM/RAM.
56 case 0x2000 ... 0x3EFF: return ciRam[nt_mirror(addr)]; // Nametables.
57 case 0x3F00 ... 0x3FFF: // Palettes:
58 if ((addr & 0x13) == 0x10) addr &= ~0x10;
59 return cgRam[addr & 0x1F] & (mask.gray ? 0x30 : 0xFF);
60 default: return 0;
61 }
62}
63void wr(u16 addr, u8 v)
64{
65 switch (addr)
66 {
67 case 0x0000 ... 0x1FFF: Cartridge::chr_access<1>(addr, v); break; // CHR-ROM/RAM.
68 case 0x2000 ... 0x3EFF: ciRam[nt_mirror(addr)] = v; break; // Nametables.
69 case 0x3F00 ... 0x3FFF: // Palettes:
70 if ((addr & 0x13) == 0x10) addr &= ~0x10;
71 cgRam[addr & 0x1F] = v; break;
72 }
73}
74
75/* Access PPU through registers. */
76template <bool write> u8 access(u16 index, u8 v)
77{
78 static u8 res; // Result of the operation.
79 static u8 buffer; // VRAM read buffer.
80 static bool latch; // Detect second reading.
81
82 /* Write into register */
83 if (write)
84 {
85 res = v;
86
87 switch (index)
88 {
89 case 0: ctrl.r = v; tAddr.nt = ctrl.nt; break; // PPUCTRL ($2000).
90 case 1: mask.r = v; break; // PPUMASK ($2001).
91 case 3: oamAddr = v; break; // OAMADDR ($2003).
92 case 4: oamMem[oamAddr++] = v; break; // OAMDATA ($2004).
93 case 5: // PPUSCROLL ($2005).
94 if (!latch) { fX = v & 7; tAddr.cX = v >> 3; } // First write.
95 else { tAddr.fY = v & 7; tAddr.cY = v >> 3; } // Second write.
96 latch = !latch; break;
97 case 6: // PPUADDR ($2006).
98 if (!latch) { tAddr.h = v & 0x3F; } // First write.
99 else { tAddr.l = v; vAddr.r = tAddr.r; } // Second write.
100 latch = !latch; break;
101 case 7: wr(vAddr.addr, v); vAddr.addr += ctrl.incr ? 32 : 1; // PPUDATA ($2007).
102 }
103 }
104 /* Read from register */
105 else
106 switch (index)
107 {
108 // PPUSTATUS ($2002):
109 case 2: res = (res & 0x1F) | status.r; status.vBlank = 0; latch = 0; break;
110 case 4: res = oamMem[oamAddr]; break; // OAMDATA ($2004).
111 case 7: // PPUDATA ($2007).
112 if (vAddr.addr <= 0x3EFF)
113 {
114 res = buffer;
115 buffer = rd(vAddr.addr);
116 }
117 else
118 res = buffer = rd(vAddr.addr);
119 vAddr.addr += ctrl.incr ? 32 : 1;
120 }
121 return res;
122}
123template u8 access<0>(u16, u8); template u8 access<1>(u16, u8);
124
125/* Calculate graphics addresses */
126inline u16 nt_addr() { return 0x2000 | (vAddr.r & 0xFFF); }
127inline u16 at_addr() { return 0x23C0 | (vAddr.nt << 10) | ((vAddr.cY / 4) << 3) | (vAddr.cX / 4); }
128inline u16 bg_addr() { return (ctrl.bgTbl * 0x1000) + (nt * 16) + vAddr.fY; }
129/* Increment the scroll by one pixel */
130inline void h_scroll() { if (!rendering()) return; if (vAddr.cX == 31) vAddr.r ^= 0x41F; else vAddr.cX++; }
131inline void v_scroll()
132{
133 if (!rendering()) return;
134 if (vAddr.fY < 7) vAddr.fY++;
135 else
136 {
137 vAddr.fY = 0;
138 if (vAddr.cY == 31) vAddr.cY = 0;
139 else if (vAddr.cY == 29) { vAddr.cY = 0; vAddr.nt ^= 0b10; }
140 else vAddr.cY++;
141 }
142}
143/* Copy scrolling data from loopy T to loopy V */
144inline void h_update() { if (!rendering()) return; vAddr.r = (vAddr.r & ~0x041F) | (tAddr.r & 0x041F); }
145inline void v_update() { if (!rendering()) return; vAddr.r = (vAddr.r & ~0x7BE0) | (tAddr.r & 0x7BE0); }
146/* Put new data into the shift registers */
147inline void reload_shift()
148{
149 bgShiftL = (bgShiftL & 0xFF00) | bgL;
150 bgShiftH = (bgShiftH & 0xFF00) | bgH;
151
152 atLatchL = (at & 1);
153 atLatchH = (at & 2);
154}
155
156/* Clear secondary OAM */
157void clear_oam()
158{
159 for (int i = 0; i < 8; i++)
160 {
161 secOam[i].id = 64;
162 secOam[i].y = 0xFF;
163 secOam[i].tile = 0xFF;
164 secOam[i].attr = 0xFF;
165 secOam[i].x = 0xFF;
166 secOam[i].dataL = 0;
167 secOam[i].dataH = 0;
168 }
169}
170
171/* Fill secondary OAM with the sprite infos for the next scanline */
172void eval_sprites()
173{
174 int n = 0;
175 for (int i = 0; i < 64; i++)
176 {
177 int line = (scanline == 261 ? -1 : scanline) - oamMem[i*4 + 0];
178 // If the sprite is in the scanline, copy its properties into secondary OAM:
179 if (line >= 0 and line < spr_height())
180 {
181 secOam[n].id = i;
182 secOam[n].y = oamMem[i*4 + 0];
183 secOam[n].tile = oamMem[i*4 + 1];
184 secOam[n].attr = oamMem[i*4 + 2];
185 secOam[n].x = oamMem[i*4 + 3];
186
187 if (++n >= 8)
188 {
189 status.sprOvf = true;
190 break;
191 }
192 }
193 }
194}
195
196/* Load the sprite info into primary OAM and fetch their tile data. */
197void load_sprites()
198{
199 u16 addr;
200 for (int i = 0; i < 8; i++)
201 {
202 oam[i] = secOam[i]; // Copy secondary OAM into primary.
203
204 // Different address modes depending on the sprite height:
205 if (spr_height() == 16)
206 addr = ((oam[i].tile & 1) * 0x1000) + ((oam[i].tile & ~1) * 16);
207 else
208 addr = ( ctrl.sprTbl * 0x1000) + ( oam[i].tile * 16);
209
210 unsigned sprY = (scanline - oam[i].y) % spr_height(); // Line inside the sprite.
211 if (oam[i].attr & 0x80) sprY ^= spr_height() - 1; // Vertical flip.
212 addr += sprY + (sprY & 8); // Select the second tile if on 8x16.
213
214 oam[i].dataL = rd(addr + 0);
215 oam[i].dataH = rd(addr + 8);
216 }
217}
218
219/* Process a pixel, draw it if it's on screen */
220void pixel()
221{
222 u8 palette = 0, objPalette = 0;
223 bool objPriority = 0;
224 int x = dot - 2;
225
226 if (scanline < 240 and x >= 0 and x < 256)
227 {
228 if (mask.bg and not (!mask.bgLeft && x < 8))
229 {
230 // Background:
231 palette = (NTH_BIT(bgShiftH, 15 - fX) << 1) |
232 NTH_BIT(bgShiftL, 15 - fX);
233 if (palette)
234 palette |= ((NTH_BIT(atShiftH, 7 - fX) << 1) |
235 NTH_BIT(atShiftL, 7 - fX)) << 2;
236 }
237 // Sprites:
238 if (mask.spr and not (!mask.sprLeft && x < 8))
239 for (int i = 7; i >= 0; i--)
240 {
241 if (oam[i].id == 64) continue; // Void entry.
242 unsigned sprX = x - oam[i].x;
243 if (sprX >= 8) continue; // Not in range.
244 if (oam[i].attr & 0x40) sprX ^= 7; // Horizontal flip.
245
246 u8 sprPalette = (NTH_BIT(oam[i].dataH, 7 - sprX) << 1) |
247 NTH_BIT(oam[i].dataL, 7 - sprX);
248 if (sprPalette == 0) continue; // Transparent pixel.
249
250 if (oam[i].id == 0 && palette && x != 255) status.sprHit = true;
251 sprPalette |= (oam[i].attr & 3) << 2;
252 objPalette = sprPalette + 16;
253 objPriority = oam[i].attr & 0x20;
254 }
255 // Evaluate priority:
256 if (objPalette && (palette == 0 || objPriority == 0)) palette = objPalette;
257
258 pixels[scanline*256 + x] = nesRgb[rd(0x3F00 + (rendering() ? palette : 0))];
259 }
260 // Perform background shifts:
261 bgShiftL <<= 1; bgShiftH <<= 1;
262 atShiftL = (atShiftL << 1) | atLatchL;
263 atShiftH = (atShiftH << 1) | atLatchH;
264}
265
266/* Execute a cycle of a scanline */
267template<Scanline s> void scanline_cycle()
268{
269 static u16 addr;
270
271 if (s == NMI and dot == 1) { status.vBlank = true; if (ctrl.nmi) CPU::set_nmi(); }
272 else if (s == POST and dot == 0) GUI::new_frame(pixels);
273 else if (s == VISIBLE or s == PRE)
274 {
275 // Sprites:
276 switch (dot)
277 {
278 case 1: clear_oam(); if (s == PRE) { status.sprOvf = status.sprHit = false; } break;
279 case 257: eval_sprites(); break;
280 case 321: load_sprites(); break;
281 }
282 // Background:
283 switch (dot)
284 {
285 case 2 ... 255: case 322 ... 337:
286 pixel();
287 switch (dot % 8)
288 {
289 // Nametable:
290 case 1: addr = nt_addr(); reload_shift(); break;
291 case 2: nt = rd(addr); break;
292 // Attribute:
293 case 3: addr = at_addr(); break;
294 case 4: at = rd(addr); if (vAddr.cY & 2) at >>= 4;
295 if (vAddr.cX & 2) at >>= 2; break;
296 // Background (low bits):
297 case 5: addr = bg_addr(); break;
298 case 6: bgL = rd(addr); break;
299 // Background (high bits):
300 case 7: addr += 8; break;
301 case 0: bgH = rd(addr); h_scroll(); break;
302 } break;
303 case 256: pixel(); bgH = rd(addr); v_scroll(); break; // Vertical bump.
304 case 257: pixel(); reload_shift(); h_update(); break; // Update horizontal position.
305 case 280 ... 304: if (s == PRE) v_update(); break; // Update vertical position.
306
307 // No shift reloading:
308 case 1: addr = nt_addr(); if (s == PRE) status.vBlank = false; break;
309 case 321: case 339: addr = nt_addr(); break;
310 // Nametable fetch instead of attribute:
311 case 338: nt = rd(addr); break;
312 case 340: nt = rd(addr); if (s == PRE && rendering() && frameOdd) dot++;
313 }
314 // Signal scanline to mapper:
315 if (dot == 260 && rendering()) Cartridge::signal_scanline();
316 }
317}
318
319/* Execute a PPU cycle. */
320void step()
321{
322 switch (scanline)
323 {
324 case 0 ... 239: scanline_cycle<VISIBLE>(); break;
325 case 240: scanline_cycle<POST>(); break;
326 case 241: scanline_cycle<NMI>(); break;
327 case 261: scanline_cycle<PRE>(); break;
328 }
329 // Update dot and scanline counters:
330 if (++dot > 340)
331 {
332 dot %= 341;
333 if (++scanline > 261)
334 {
335 scanline = 0;
336 frameOdd ^= 1;
337 }
338 }
339}
340
341void reset()
342{
343 frameOdd = false;
344 scanline = dot = 0;
345 ctrl.r = mask.r = status.r = 0;
346
347 memset(pixels, 0x00, sizeof(pixels));
348 memset(ciRam, 0xFF, sizeof(ciRam));
349 memset(oamMem, 0x00, sizeof(oamMem));
350}
351
352
353}
354