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 }