1 module nes.ppu;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.stdio;
6 
7 import nes.console;
8 import nes.image;
9 import nes.memory;
10 import nes.palette;
11 
12 struct SpritePixel {
13     ubyte index;
14     ubyte color;
15 }
16 
17 class PPU : PPUMemory {
18     this(Console console) {
19         super(console);
20         this.console = console;
21         this.front = new ImageRGBA(Rect(0, 0, 256, 240));
22         this.back = new ImageRGBA(Rect(0, 0, 256, 240));
23         this.reset();
24     }
25 
26     void reset() {
27         this.cycle = 340;
28         this.scanLine = 261;
29         this.frame = 0;
30 
31         this.renderingEnabled = false;
32         this.prevRenderingEnabled = false;
33         this.renderingEnabledUpdated = false;
34 
35         // NMI flags
36         this.nmiOccurred = false;
37         this.nmiOutput = false;
38         this.nmiPrevious = false;
39 
40         // PPU registers
41         this.v = 0; // current vram address (15 bit)
42         this.t = 0; // temporary vram address (15 bit)
43         this.x = 0; // fine x scroll (3 bit)
44         this.w = 0; // write toggle (1 bit)
45         this.f = 1; // even/odd frame flag (1 bit)
46 
47         this.vWriteDelay = 0;
48         this.vWriteValue = 0;
49         this.vReadIgnoreCounter = 0;
50 
51         this.writeControl(0); // $2000 PPUCTRL
52         this.writeMask(0); // $2001 PPUMASK
53         this.writeOAMAddress(0); // $2003: OAMADDR
54 
55         this.register = 0;
56 
57         // background temporary variables
58         this.nameTableByte = 0;
59         this.attributeTableByte = 0;
60         this.lowTileByte = 0;
61         this.highTileByte = 0;
62         this.tileData = 0;
63 
64         this.tileAddress = 0;
65 
66         // sprite temporary variables
67         this.spriteCount = 0;
68         this.spritePatterns = [0, 0, 0, 0, 0, 0, 0, 0];
69         this.spritePositions = [0, 0, 0, 0, 0, 0, 0, 0];
70         this.spritePriorities = [0, 0, 0, 0, 0, 0, 0, 0];
71         this.spriteIndexes = [0, 0, 0, 0, 0, 0, 0, 0];
72 
73         // $2002 PPUSTATUS
74         this.flagSpriteZeroHit = 0;
75         this.flagSpriteOverflow = 0;
76 
77         // $2007 PPUDATA
78         this.bufferedData = 0; // for buffered reads
79 
80         foreach (i; nameTableData) nameTableData[i] = 0;
81         paletteData = [0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 0x08,
82                        0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, 0x09, 0x01,
83                        0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 0x08, 0x3A, 0x00,
84                        0x02, 0x00, 0x20, 0x2C, 0x08];
85         foreach (i; oamData) oamData[i] = 0;
86         foreach (i; front.pix) front.pix[i] = 0;
87         foreach (i; back.pix) back.pix[i] = 0;
88 
89         foreach (_; 0 .. 28)
90             this.step();
91     }
92 
93     // Step executes a single PPU cycle
94     void step() {
95         this.tick();
96 
97         auto preLine = this.scanLine == 261;
98         auto visibleLine = this.scanLine < 240;
99         auto renderLine = preLine || visibleLine;
100         auto preFetchCycle = this.cycle >= 321 && this.cycle <= 336;
101         auto visibleCycle = this.cycle >= 1 && this.cycle <= 256;
102         auto fetchCycle = preFetchCycle || visibleCycle;
103 
104         // background logic
105         if (this.renderingEnabled) {
106             if (visibleLine && visibleCycle) {
107                 this.renderPixel();
108             }
109             if (renderLine && fetchCycle) {
110                 this.tileData <<= 4;
111                 switch ((this.cycle - 1) & 0x07) {
112                     case 0:
113                         this.fetchNameTableByte();
114 
115                         ushort fineY = (this.v >> 12) & 7;
116                         auto table = this.flagBackgroundTable;
117                         auto tile = this.nameTableByte;
118                         this.tileAddress = cast(ushort)(0x1000 * cast(ushort)table + cast(ushort)tile * 16 + fineY);
119                         break;
120                     case 2:
121                         this.fetchAttributeTableByte();
122                         break;
123                     case 3:
124                         this.fetchLowTileByte();
125                         break;
126                     case 5:
127                         this.fetchHighTileByte();
128                         break;
129                     case 7:
130                         this.storeTileData();
131                         break;
132                     default:
133                         break;
134                 }
135             }
136             if (preLine && this.cycle >= 280 && this.cycle <= 304) {
137                 this.copyY();
138             }
139             if (renderLine) {
140                 if (fetchCycle && this.cycle % 8 == 0) {
141                     this.incrementX();
142                 }
143                 if (this.cycle == 256) {
144                     this.incrementY();
145                 }
146                 if (this.cycle == 257) {
147                     this.copyX();
148                 }
149             }
150         }
151 
152         // sprite logic
153         if (this.renderingEnabled) {
154             if (this.cycle == 257) {
155                 if (visibleLine) {
156                     this.evaluateSprites();
157                 } else {
158                     this.spriteCount = 0;
159                 }
160             }
161         }
162 
163         // vblank logic
164 
165         if (this.renderingEnabledUpdated) {
166             this.updateRenderingEnabled();
167         }
168 
169         if (this.vWriteDelay > 0) {
170             this.vWriteDelay--;
171             if (this.vWriteDelay == 0) {
172                 this.v = this.vWriteValue;
173                 // vram read should happen here
174             }
175         }
176 
177         if (this.vReadIgnoreCounter > 0)
178             this.vReadIgnoreCounter--;
179     }
180 
181     ubyte readRegister(ushort address) {
182         switch (address) {
183             case 0x2002:
184                 return this.readStatus();
185             case 0x2004:
186                 return this.readOAMData();
187             case 0x2007:
188                 return this.readData();
189             default:
190                 break;
191         }
192 
193         return 0;
194     }
195 
196     void writeRegister(ushort address, ubyte value) {
197         this.register = value;
198 
199         switch (address) {
200             case 0x2000:
201                 this.writeControl(value);
202                 break;
203             case 0x2001:
204                 this.writeMask(value);
205                 break;
206             case 0x2003:
207                 this.writeOAMAddress(value);
208                 break;
209             case 0x2004:
210                 this.writeOAMData(value);
211                 break;
212             case 0x2005:
213                 this.writeScroll(value);
214                 break;
215             case 0x2006:
216                 this.writeAddress(value);
217                 break;
218             case 0x2007:
219                 this.writeData(value);
220                 break;
221             case 0x4014:
222                 this.writeDMA(value);
223                 break;
224             default:
225                 break;
226         }
227     }
228 
229     ubyte readPalette(ushort address) {
230         if (address >= 16 && address % 4 == 0) {
231             address -= 16;
232         }
233 
234         if (this.flagGrayscale) return this.paletteData[address] & 0x30;
235 
236         return this.paletteData[address];
237     }
238 
239     void writePalette(ushort address, ubyte value) {
240         if (address >= 16 && address % 4 == 0) {
241             address -= 16;
242         }
243 
244         this.paletteData[address] = value;
245     }
246 
247     void save(string[string] state) {
248         state["ppu.cycle"] = to!string(this.cycle);
249         state["ppu.scanLine"] = to!string(this.scanLine);
250         state["ppu.frame"] = to!string(this.frame);
251         state["ppu.paletteData"] = to!string(this.paletteData);
252         state["ppu.nameTableData"] = to!string(this.nameTableData);
253         state["ppu.oamData"] = to!string(this.oamData);
254         state["ppu.v"] = to!string(this.v);
255         state["ppu.t"] = to!string(this.t);
256         state["ppu.x"] = to!string(this.x);
257         state["ppu.w"] = to!string(this.w);
258         state["ppu.f"] = to!string(this.f);
259         state["ppu.register"] = to!string(this.register);
260         state["ppu.nmiOccurred"] = to!string(this.nmiOccurred);
261         state["ppu.nmiOutput"] = to!string(this.nmiOutput);
262         state["ppu.nmiPrevious"] = to!string(this.nmiPrevious);
263         state["ppu.nameTableByte"] = to!string(this.nameTableByte);
264         state["ppu.attributeTableByte"] = to!string(this.attributeTableByte);
265         state["ppu.lowTileByte"] = to!string(this.lowTileByte);
266         state["ppu.highTileByte"] = to!string(this.highTileByte);
267         state["ppu.tileData"] = to!string(this.tileData);
268         state["ppu.spriteCount"] = to!string(this.spriteCount);
269         state["ppu.spritePatterns"] = to!string(this.spritePatterns);
270         state["ppu.spritePositions"] = to!string(this.spritePositions);
271         state["ppu.spritePriorities"] = to!string(this.spritePriorities);
272         state["ppu.spriteIndexes"] = to!string(this.spriteIndexes);
273         state["ppu.flagNameTable"] = to!string(this.flagNameTable);
274         state["ppu.flagIncrement"] = to!string(this.flagIncrement);
275         state["ppu.flagSpriteTable"] = to!string(this.flagSpriteTable);
276         state["ppu.flagBackgroundTable"] = to!string(this.flagBackgroundTable);
277         state["ppu.flagSpriteSize"] = to!string(this.flagSpriteSize);
278         state["ppu.flagMasterSlave"] = to!string(this.flagMasterSlave);
279         state["ppu.flagGrayscale"] = to!string(this.flagGrayscale);
280         state["ppu.flagShowLeftBackground"] = to!string(this.flagShowLeftBackground);
281         state["ppu.flagShowLeftSprites"] = to!string(this.flagShowLeftSprites);
282         state["ppu.flagShowBackground"] = to!string(this.flagShowBackground);
283         state["ppu.flagShowSprites"] = to!string(this.flagShowSprites);
284         state["ppu.flagRedTint"] = to!string(this.flagRedTint);
285         state["ppu.flagGreenTint"] = to!string(this.flagGreenTint);
286         state["ppu.flagBlueTint"] = to!string(this.flagBlueTint);
287         state["ppu.flagSpriteZeroHit"] = to!string(this.flagSpriteZeroHit);
288         state["ppu.flagSpriteOverflow"] = to!string(this.flagSpriteOverflow);
289         state["ppu.oamAddress"] = to!string(this.oamAddress);
290         state["ppu.bufferedData"] = to!string(this.bufferedData);
291         state["ppu.renderingEnabled"] = to!string(this.renderingEnabled);
292         state["ppu.prevRenderingEnabled"] = to!string(this.prevRenderingEnabled);
293         state["ppu.renderingEnabledUpdated"] = to!string(this.renderingEnabledUpdated);
294 
295         state["ppu.vWriteValue"] = to!string(this.vWriteValue);
296         state["ppu.vWriteDelay"] = to!string(this.vWriteDelay);
297         state["ppu.vReadIgnoreCounter"] = to!string(this.vReadIgnoreCounter);
298         state["ppu.tileAddress"] = to!string(this.tileAddress);
299     }
300 
301     void load(string[string] state) {
302         this.cycle = to!int(state["ppu.cycle"]);
303         this.scanLine = to!int(state["ppu.scanLine"]);
304         this.frame = to!ulong(state["ppu.frame"]);
305         this.paletteData = to!(ubyte[32])(state["ppu.paletteData"]);
306         this.nameTableData = to!(ubyte[2048])(state["ppu.nameTableData"]);
307         this.oamData = to!(ubyte[256])(state["ppu.oamData"]);
308         this.v = to!ushort(state["ppu.v"]);
309         this.t = to!ushort(state["ppu.t"]);
310         this.x = to!ubyte(state["ppu.x"]);
311         this.w = to!ubyte(state["ppu.w"]);
312         this.f = to!ubyte(state["ppu.f"]);
313         this.register = to!ubyte(state["ppu.register"]);
314         this.nmiOccurred = to!bool(state["ppu.nmiOccurred"]);
315         this.nmiOutput = to!bool(state["ppu.nmiOutput"]);
316         this.nmiPrevious = to!bool(state["ppu.nmiPrevious"]);
317         this.nameTableByte = to!ubyte(state["ppu.nameTableByte"]);
318         this.attributeTableByte = to!ubyte(state["ppu.attributeTableByte"]);
319         this.lowTileByte = to!ubyte(state["ppu.lowTileByte"]);
320         this.highTileByte = to!ubyte(state["ppu.highTileByte"]);
321         this.tileData = to!ulong(state["ppu.tileData"]);
322         this.spriteCount = to!int(state["ppu.spriteCount"]);
323         this.spritePatterns = to!(uint[8])(state["ppu.spritePatterns"]);
324         this.spritePositions = to!(ubyte[8])(state["ppu.spritePositions"]);
325         this.spritePriorities = to!(ubyte[8])(state["ppu.spritePriorities"]);
326         this.spriteIndexes = to!(ubyte[8])(state["ppu.spriteIndexes"]);
327         this.flagNameTable = to!ubyte(state["ppu.flagNameTable"]);
328         this.flagIncrement = to!ubyte(state["ppu.flagIncrement"]);
329         this.flagSpriteTable = to!ubyte(state["ppu.flagSpriteTable"]);
330         this.flagBackgroundTable = to!ubyte(state["ppu.flagBackgroundTable"]);
331         this.flagSpriteSize = to!ubyte(state["ppu.flagSpriteSize"]);
332         this.flagMasterSlave = to!ubyte(state["ppu.flagMasterSlave"]);
333         this.flagGrayscale = to!ubyte(state["ppu.flagGrayscale"]);
334         this.flagShowLeftBackground = to!ubyte(state["ppu.flagShowLeftBackground"]);
335         this.flagShowLeftSprites = to!ubyte(state["ppu.flagShowLeftSprites"]);
336         this.flagShowBackground = to!ubyte(state["ppu.flagShowBackground"]);
337         this.flagShowSprites = to!ubyte(state["ppu.flagShowSprites"]);
338         this.flagRedTint = to!ubyte(state["ppu.flagRedTint"]);
339         this.flagGreenTint = to!ubyte(state["ppu.flagGreenTint"]);
340         this.flagBlueTint = to!ubyte(state["ppu.flagBlueTint"]);
341         this.flagSpriteZeroHit = to!ubyte(state["ppu.flagSpriteZeroHit"]);
342         this.flagSpriteOverflow = to!ubyte(state["ppu.flagSpriteOverflow"]);
343         this.oamAddress = to!ubyte(state["ppu.oamAddress"]);
344         this.bufferedData = to!ubyte(state["ppu.bufferedData"]);
345         this.renderingEnabled = to!bool(state["ppu.renderingEnabled"]);
346         this.prevRenderingEnabled = to!bool(state["ppu.prevRenderingEnabled"]);
347         this.renderingEnabledUpdated = to!bool(state["ppu.renderingEnabledUpdated"]);
348 
349         this.vWriteValue = to!ushort(state["ppu.vWriteValue"]);
350         this.vWriteDelay = to!ubyte(state["ppu.vWriteDelay"]);
351         this.vReadIgnoreCounter = to!ubyte(state["ppu.vReadIgnoreCounter"]);
352         this.tileAddress = to!ushort(state["ppu.tileAddress"]);
353     }
354 
355     package:
356         Console     console;
357         ubyte[2048] nameTableData;
358         ImageRGBA   front;
359         ImageRGBA   back;
360 
361         int   cycle;    // 0-340
362         int   scanLine; // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
363         ulong frame;    // frame counter
364 
365         bool renderingEnabled;
366         bool prevRenderingEnabled;
367         bool renderingEnabledUpdated;
368 
369         // storage variables
370         ubyte[32]  paletteData;
371         ubyte[256] oamData;
372 
373         // PPU registers
374         ushort v; // current vram address (15 bit)
375         ushort t; // temporary vram address (15 bit)
376         ubyte  x; // fine x scroll (3 bit)
377         ubyte  w; // write toggle (1 bit)
378         ubyte  f; // even/odd frame flag (1 bit)
379 
380         ushort vWriteValue;
381         ubyte  vWriteDelay;
382         ubyte  vReadIgnoreCounter;
383 
384         ubyte register;
385 
386         // NMI flags
387         bool  nmiOccurred;
388         bool  nmiOutput;
389         bool  nmiPrevious;
390 
391         // background temporary variables
392         ubyte nameTableByte;
393         ubyte attributeTableByte;
394         ubyte lowTileByte;
395         ubyte highTileByte;
396         ulong tileData;
397 
398         ushort tileAddress;
399 
400         // sprite temporary variables
401         int      spriteCount;
402         uint[8]  spritePatterns;
403         ubyte[8] spritePositions;
404         ubyte[8] spritePriorities;
405         ubyte[8] spriteIndexes;
406 
407         // $2000 PPUCTRL
408         ubyte flagNameTable; // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
409         ubyte flagIncrement; // 0: add 1; 1: add 32
410         ubyte flagSpriteTable; // 0: $0000; 1: $1000; ignored in 8x16 mode
411         ubyte flagBackgroundTable; // 0: $0000; 1: $1000
412         ubyte flagSpriteSize; // 0: 8x8; 1: 8x16
413         ubyte flagMasterSlave; // 0: read EXT; 1: write EXT
414 
415         // $2001 PPUMASK
416         ubyte flagShowBackground; // 0: hide; 1: show
417         ubyte flagShowSprites; // 0: hide; 1: show
418         ubyte flagGrayscale; // 0: color; 1: grayscale
419         ubyte flagShowLeftBackground; // 0: hide; 1: show
420         ubyte flagShowLeftSprites; // 0: hide; 1: show
421         ubyte flagRedTint; // 0: normal; 1: emphasized
422         ubyte flagGreenTint; // 0: normal; 1: emphasized
423         ubyte flagBlueTint; // 0: normal; 1: emphasized
424 
425         // $2002 PPUSTATUS
426         ubyte flagSpriteZeroHit;
427         ubyte flagSpriteOverflow;
428 
429         // $2003 OAMADDR
430         ubyte oamAddress;
431 
432         // $2007 PPUDATA
433         ubyte bufferedData; // for buffered reads
434 
435     private:
436         // $2000: PPUCTRL
437         void writeControl(ubyte value) {
438             auto orgFlagIncrement = this.flagIncrement;
439             auto orgFlagBackgroundTable = this.flagBackgroundTable;
440 
441             this.flagNameTable = (value >> 0) & 3;
442             this.flagIncrement = (value >> 2) & 1;
443             this.flagSpriteTable = (value >> 3) & 1;
444             this.flagBackgroundTable = (value >> 4) & 1;
445             this.flagSpriteSize = (value >> 5) & 1;
446             this.flagMasterSlave = (value >> 6) & 1;
447             auto prevNmiOutput = this.nmiOutput;
448             this.nmiOutput = ((value >> 7) & 1) == 1;
449             this.nmiChange();
450             // t: ....BA.. ........ = d: ......BA
451             this.t = (this.t & 0xF3FF) | ((cast(ushort)(value) & 0x03) << 10);
452 
453             // Too late to turn off NMI
454             if (this.scanLine == 241 && this.cycle < 3 && !this.nmiOutput) {
455                 this.console.cpu.clearNmiFlag();
456             }
457 
458             // Too late to turn on NMI
459             if (this.scanLine == 261 && this.cycle == 0 && this.nmiOutput) {
460                 this.console.cpu.clearNmiFlag();
461             }
462         }
463 
464         // $2001: PPUMASK
465         void writeMask(ubyte value) {
466             this.flagGrayscale = (value >> 0) & 1;
467             this.flagShowLeftBackground = (value >> 1) & 1;
468             this.flagShowLeftSprites = (value >> 2) & 1;
469             this.flagShowBackground = (value >> 3) & 1;
470             this.flagShowSprites = (value >> 4) & 1;
471             this.flagRedTint = (value >> 5) & 1;
472             this.flagGreenTint = (value >> 6) & 1;
473             this.flagBlueTint = (value >> 7) & 1;
474 
475             if (this.renderingEnabled != (this.flagShowBackground | this.flagShowSprites)) {
476                 this.renderingEnabledUpdated = true;
477             }
478         }
479 
480         // $2002: PPUSTATUS
481         ubyte readStatus() {
482             ubyte result = this.register & 0x1F;
483             result |= this.flagSpriteOverflow << 5;
484             result |= this.flagSpriteZeroHit << 6;
485             if (this.nmiOccurred) {
486                 result |= (1 << 7);
487             }
488 
489             if (this.scanLine == 241 && this.cycle < 3) {
490                 this.console.cpu.clearNmiFlag();
491 
492                 if (this.cycle == 0) {
493                     result = this.register & 0x1F;
494                     result |= this.flagSpriteOverflow << 5;
495                     result |= this.flagSpriteZeroHit << 6;
496                 }
497             }
498             
499             this.nmiOccurred = false;
500             this.nmiChange();
501             // w:                   = 0
502             this.w = 0;
503 
504             return result;
505         }
506 
507         // $2003: OAMADDR
508         void writeOAMAddress(ubyte value) {
509             this.oamAddress = value;
510         }
511 
512         // $2004: OAMDATA (read)
513         ubyte readOAMData() {
514             return this.oamData[this.oamAddress];
515         }
516 
517         // $2004: OAMDATA (write)
518         void writeOAMData(ubyte value) {
519             this.oamData[this.oamAddress] = value;
520             this.oamAddress++;
521         }
522 
523         // $2005: PPUSCROLL
524         void writeScroll(ubyte value) {
525             if (this.w == 0) {
526                 // t: ........ ...HGFED = d: HGFED...
527                 // x:               CBA = d: .....CBA
528                 // w:                   = 1
529                 this.t = (this.t & 0xFFE0) | (cast(ushort)value >> 3);
530                 this.x = value & 0x07;
531                 this.w = 1;
532             } else {
533                 // t: .CBA..HG FED..... = d: HGFEDCBA
534                 // w:                   = 0
535                 this.t = (this.t & 0x8FFF) | ((cast(ushort)value & 0x07) << 12);
536                 this.t = (this.t & 0xFC1F) | ((cast(ushort)value & 0xF8) << 2);
537                 this.w = 0;
538             }
539         }
540 
541         // $2006: PPUADDR
542         void writeAddress(ubyte value) {
543             if (this.w == 0) {
544                 // t: ..FEDCBA ........ = d: ..FEDCBA
545                 // t: .X...... ........ = 0
546                 // w:                   = 1
547                 this.t = (this.t & 0x80FF) | ((cast(ushort)value & 0x3F) << 8);
548                 this.w = 1;
549             } else {
550                 // t: ........ HGFEDCBA = d: HGFEDCBA
551                 // v                    = t
552                 // w:                   = 0
553                 this.t = (this.t & 0xFF00) | cast(ushort)value;
554                 this.vWriteDelay = 2;
555                 this.vWriteValue = this.t;
556                 this.w = 0;
557             }
558         }
559 
560         // $2007: PPUDATA (read)
561         ubyte readData() {
562             if (this.vReadIgnoreCounter == 0) {
563                 auto value = this.read(this.v);
564                 // emulate buffered reads
565                 if (this.v % 0x4000 < 0x3F00) {
566                     auto buffered = this.bufferedData;
567                     this.bufferedData = value;
568                     value = buffered;
569                 } else {
570                     this.bufferedData = this.read(cast(ushort)(this.v - 0x1000));
571                 }
572 
573                 // increment address
574                 if (this.scanLine >= 240 || !this.renderingEnabled) {
575                     if (this.flagIncrement == 0) {
576                         this.v += 1;
577                     } else {
578                         this.v += 32;
579                     }
580                     // should do a vram read here
581                 } else {
582                     this.incrementX();
583                     this.incrementY();
584                 }
585 
586                 return value;
587             } else {
588                 return 0;
589             }
590         }
591 
592         // $2007: PPUDATA (write)
593         void writeData(ubyte value) {
594             this.write(this.v, value);
595 
596             if (this.scanLine >= 240 || !this.renderingEnabled) {
597                 if (this.flagIncrement == 0) {
598                     this.v += 1;
599                 } else {
600                     this.v += 32;
601                 }
602             } else {
603                 this.incrementX();
604                 this.incrementY();
605             }
606         }
607 
608         // $4014: OAMDMA
609         void writeDMA(ubyte value) {
610             this.console.cpu.spriteDmaTransfer(value);
611         }
612 
613         // NTSC Timing Helper Functions
614 
615         void incrementX() {
616             // increment hori(v)
617             // if coarse X == 31
618             if ((this.v & 0x001F) == 31) {
619                 // coarse X = 0
620                 this.v &= 0xFFE0;
621                 // switch horizontal nametable
622                 this.v ^= 0x0400;
623             } else {
624                 // increment coarse X
625                 this.v++;
626             }
627         }
628 
629         void incrementY() {
630             // increment vert(v)
631             // if fine Y < 7
632             if ((this.v & 0x7000) != 0x7000) {
633                 // increment fine Y
634                 this.v += 0x1000;
635             } else {
636                 // fine Y = 0
637                 this.v &= 0x8FFF;
638 
639                 // let y = coarse Y
640                 ushort y = (this.v & 0x03E0) >> 5;
641                 if (y == 29) {
642                     // coarse Y = 0
643                     y = 0;
644                     // switch vertical nametable
645                     this.v ^= 0x0800;
646                 } else if (y == 31) {
647                     // coarse Y = 0, nametable not switched
648                     y = 0;
649                 } else {
650                     // increment coarse Y
651                     y++;
652                 }
653                 // put coarse Y back into v
654                 this.v = cast(ushort)((this.v & 0xFC1F) | (y << 5));
655             }
656         }
657 
658         void copyX() {
659             // hori(v) = hori(t)
660             // v: .....F.. ...EDCBA = t: .....F.. ...EDCBA
661             this.v = (this.v & 0xFBE0) | (this.t & 0x041F);
662         }
663 
664         void copyY() {
665             // vert(v) = vert(t)
666             // v: .IHGF.ED CBA..... = t: .IHGF.ED CBA.....
667             this.v = (this.v & 0x841F) | (this.t & 0x7BE0);
668         }
669 
670         void nmiChange() {
671             auto nmi = this.nmiOutput && this.nmiOccurred;
672             if (nmi && !this.nmiPrevious) {
673                 this.console.cpu.setNmiFlag();
674             }
675             this.nmiPrevious = nmi;
676         }
677 
678         void setVerticalBlank() {
679             swap(this.front, this.back);
680             this.nmiOccurred = true;
681             this.nmiChange();
682         }
683 
684         void clearVerticalBlank() {
685             this.nmiOccurred = false;
686             this.nmiChange();
687         }
688 
689         void fetchNameTableByte() {
690             auto v = this.v;
691             ushort address = 0x2000 | (v & 0x0FFF);
692             this.nameTableByte = this.read(address);
693         }
694 
695         void fetchAttributeTableByte() {
696             auto v = this.v;
697             ushort address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07);
698             ubyte shift = ((v >> 4) & 4) | (v & 2);
699             this.attributeTableByte = ((this.read(address) >> shift) & 3) << 2;
700         }
701 
702         void fetchLowTileByte() {
703             this.lowTileByte = this.read(this.tileAddress);
704         }
705 
706         void fetchHighTileByte() {
707             this.highTileByte = this.read(cast(ushort)(this.tileAddress + 8));
708         }
709 
710         void storeTileData() {
711             uint data;
712             foreach (_; 0 .. 8) {
713                 auto a = this.attributeTableByte;
714                 ubyte p1 = (this.lowTileByte & 0x80) >> 7;
715                 ubyte p2 = (this.highTileByte & 0x80) >> 6;
716                 this.lowTileByte <<= 1;
717                 this.highTileByte <<= 1;
718                 data <<= 4;
719                 data |= cast(uint)(a | p1 | p2);
720             }
721             this.tileData |= cast(ulong)data;
722         }
723 
724         uint fetchTileData() {
725             return cast(uint)(this.tileData >> 32);
726         }
727 
728         ubyte backgroundPixel() {
729             if (this.flagShowBackground == 0) {
730                 return 0;
731             }
732             uint data = this.fetchTileData() >> ((7 - this.x) * 4);
733             return cast(ubyte)(data & 0x0F);
734         }
735 
736         SpritePixel spritePixel() {
737             if (this.flagShowSprites == 0) {
738                 return SpritePixel(0, 0);
739             }
740             foreach (i; 0 .. this.spriteCount) {
741                 int offset = (this.cycle - 1) - cast(int)this.spritePositions[i];
742                 if (offset < 0 || offset > 7) {
743                     continue;
744                 }
745                 offset = 7 - offset;
746                 ubyte color = cast(ubyte)((this.spritePatterns[i] >> cast(ubyte)(offset * 4)) & 0x0F);
747                 if (color % 4 == 0) {
748                     continue;
749                 }
750                 return SpritePixel(cast(ubyte)i, color);
751             }
752             return SpritePixel(0, 0);
753         }
754 
755         void renderPixel() {
756             auto x = this.cycle - 1;
757             auto y = this.scanLine;
758 
759             auto background = this.backgroundPixel();
760             auto sp = this.spritePixel();
761             auto i = sp.index;
762             auto sprite = sp.color;
763             if (x < 8 && this.flagShowLeftBackground == 0) {
764                 background = 0;
765             }
766             if (x < 8 && this.flagShowLeftSprites == 0) {
767                 sprite = 0;
768             }
769             auto b = background % 4 != 0;
770             auto s = sprite % 4 != 0;
771             ubyte color;
772             if (!b && !s) {
773                 color = 0;
774             } else if (!b && s) {
775                 color = sprite | 0x10;
776             } else if (b && !s) {
777                 color = background;
778             } else {
779                 if (this.spriteIndexes[i] == 0 && x < 255) {
780                     this.flagSpriteZeroHit = 1;
781                 }
782                 if (this.spritePriorities[i] == 0) {
783                     color = sprite | 0x10;
784                 } else {
785                     color = background;
786                 }
787             }
788             auto c = Palette[this.readPalette(cast(ushort)color) % 64];
789             this.back.setRGBA(x, y, c);
790         }
791 
792         uint fetchSpritePattern(int i, int row) {
793             auto tile = this.oamData[i * 4 + 1];
794             auto attributes = this.oamData[i * 4 + 2];
795             ushort address;
796             if (this.flagSpriteSize == 0) {
797                 if ((attributes & 0x80) == 0x80) {
798                     row = 7 - row;
799                 }
800                 auto table = this.flagSpriteTable;
801                 address = cast(ushort)(0x1000 * cast(ushort)table + cast(ushort)tile * 16 + cast(ushort)row);
802             } else {
803                 if ((attributes & 0x80) == 0x80) {
804                     row = 15 - row;
805                 }
806                 auto table = tile & 1;
807                 tile &= 0xFE;
808                 if (row > 7) {
809                     tile++;
810                     row -= 8;
811                 }
812                 address = cast(ushort)(0x1000 * cast(ushort)table + cast(ushort)tile * 16 + cast(ushort)row);
813             }
814             auto a = (attributes & 3) << 2;
815             auto lowTileByte = this.read(address);
816             auto highTileByte = this.read(cast(ushort)(address + 8));
817             uint data;
818             foreach (_; 0 .. 8) {
819                 ubyte p1, p2;
820                 if ((attributes & 0x40) == 0x40) {
821                     p1 = (lowTileByte & 1) << 0;
822                     p2 = (highTileByte & 1) << 1;
823                     lowTileByte >>= 1;
824                     highTileByte >>= 1;
825                 } else {
826                     p1 = (lowTileByte & 0x80) >> 7;
827                     p2 = (highTileByte & 0x80) >> 6;
828                     lowTileByte <<= 1;
829                     highTileByte <<= 1;
830                 }
831                 data <<= 4;
832                 data |= cast(uint)(a | p1 | p2);
833             }
834             return data;
835         }
836 
837         void evaluateSprites() {
838             int h;
839             if (this.flagSpriteSize == 0) {
840                 h = 8;
841             } else {
842                 h = 16;
843             }
844             auto count = 0;
845             foreach (i; 0 .. 64) {
846                 auto y = this.oamData[i*4+0];
847                 auto a = this.oamData[i*4+2];
848                 auto x = this.oamData[i*4+3];
849                 auto row = this.scanLine - cast(int)y;
850                 if (row < 0 || row >= h) {
851                     continue;
852                 }
853                 if (count < 8) {
854                     this.spritePatterns[count] = this.fetchSpritePattern(i, row);
855                     this.spritePositions[count] = x;
856                     this.spritePriorities[count] = (a >> 5) & 1;
857                     this.spriteIndexes[count] = cast(ubyte)i;
858                 }
859                 count++;
860             }
861             if (count > 8) {
862                 count = 8;
863                 this.flagSpriteOverflow = 1;
864             }
865             this.spriteCount = count;
866         }
867 
868         void updateRenderingEnabled() {
869             this.renderingEnabledUpdated = false;
870 
871             this.prevRenderingEnabled = this.renderingEnabled;
872 
873             this.renderingEnabled = this.flagShowBackground || this.flagShowSprites;
874 
875             if (this.prevRenderingEnabled != this.renderingEnabled) {
876                 this.renderingEnabledUpdated = true;
877             }
878         }
879 
880         // tick updates Cycle, ScanLine and Frame counters
881         void tick() {
882             // we start on cycle 1 since we inc first
883 
884             if (this.cycle > 339) {
885                 // Cycle 0
886                 this.cycle = 0;
887 
888                 this.scanLine++;
889 
890                 if (this.scanLine == 240) {
891                     this.frame++;
892                     this.f ^= 1;
893                 } else if (this.scanLine == 241) {
894                     this.setVerticalBlank();
895                 } else if (this.scanLine == 261) {
896                     this.flagSpriteZeroHit = 0;
897                     this.flagSpriteOverflow = 0;
898                 } else if (this.scanLine > 261) {
899                     this.scanLine = 0;
900                 }
901             } else {
902                 // Cycle > 0
903                 this.cycle++;
904 
905                 if (this.cycle == 1 && this.scanLine == 261) {
906                     this.clearVerticalBlank();
907                 } else if (this.cycle == 339 && this.scanLine == 261) {
908                     if (this.f == 1 && (this.renderingEnabled)) {
909                         this.cycle = 340;
910                     }
911                 }
912             }
913         }
914 }