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