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 }