/** * Fetches 8x8 NES tile stored at the given offset. This is an artifact of the first renderer I * wrote which drew 8 scanlines at a time. * * @param offset * @return an 8x8 array with colors stored as RGB packed in int */ private int[] debugGetTile(final int offset) { // read one whole tile from nametable and convert from bitplane to packed // only used for debugging int[] dat = new int[64]; for (int i = 0; i < 8; ++i) { // per line of tile ( 1 byte) for (int j = 0; j < 8; ++j) { // per pixel(1 bit) dat[8 * i + j] = ((((mapper.ppuRead(i + offset) & (utils.BIT7 - j)) != 0)) ? 0x555555 : 0) + ((((mapper.ppuRead(i + offset + 8) & (utils.BIT7 - j)) != 0)) ? 0xaaaaaa : 0); } } return dat; }
private void spriteFetch( final boolean spritesize, final int tilenum, int offset, final int oamextra) { int tilefetched; if (spritesize) { tilefetched = ((tilenum & 1) * 0x1000) + (tilenum & 0xfe) * 16; } else { tilefetched = tilenum * 16 + ((sprpattern) ? 0x1000 : 0); } tilefetched += offset; // now load up the shift registers for said sprite final boolean hflip = ((oamextra & (utils.BIT6)) != 0); if (!hflip) { spriteshiftregL[found] = reverseByte(mapper.ppuRead(tilefetched)); spriteshiftregH[found] = reverseByte(mapper.ppuRead(tilefetched + 8)); } else { spriteshiftregL[found] = mapper.ppuRead(tilefetched); spriteshiftregH[found] = mapper.ppuRead(tilefetched + 8); } }
private void bgFetch() { // fetch tiles for background // on real PPU this logic is repurposed for sprite fetches as well // System.err.println(hex(loopyV)); bgAttrShiftRegH |= ((nextattr >> 1) & 1); bgAttrShiftRegL |= (nextattr & 1); // background fetches switch ((cycles - 1) & 7) { case 1: fetchNTByte(); break; case 3: // fetch attribute (FIX MATH) penultimateattr = getAttribute(((loopyV & 0xc00) + 0x23c0), (loopyV) & 0x1f, (((loopyV) & 0x3e0) >> 5)); break; case 5: // fetch low bg byte linelowbits = mapper.ppuRead((tileAddr) + ((loopyV & 0x7000) >> 12)); break; case 7: // fetch high bg byte linehighbits = mapper.ppuRead((tileAddr) + 8 + ((loopyV & 0x7000) >> 12)); bgShiftRegL |= linelowbits; bgShiftRegH |= linehighbits; nextattr = penultimateattr; if (cycles != 256) { incLoopyVHoriz(); } else { incLoopyVVert(); } break; default: break; } if (cycles >= 321 && cycles <= 336) { bgShiftClock(); } }
/** * Read the appropriate color attribute byte for the current tile. this is fetched 2x as often as * it really needs to be, the MMC5 takes advantage of that for ExGrafix mode. * * @param ntstart //start of the current attribute table * @param tileX //x position of tile (0-31) * @param tileY //y position of tile (0-29) * @return attribute table value (0-3) */ private int getAttribute(final int ntstart, final int tileX, final int tileY) { final int base = mapper.ppuRead(ntstart + (tileX >> 2) + 8 * (tileY >> 2)); if (((tileY & (utils.BIT1)) != 0)) { if (((tileX & (utils.BIT1)) != 0)) { return (base >> 6) & 3; } else { return (base >> 4) & 3; } } else { if (((tileX & (utils.BIT1)) != 0)) { return (base >> 2) & 3; } else { return base & 3; } } }
/** draw all 4 nametables/tileset/pallette to debug window. (for the nametable viewer) */ private void debugDraw() { for (int i = 0; i < 32; ++i) { for (int j = 0; j < 30; ++j) { nametableView.setRGB( i * 8, j * 8, 8, 8, debugGetTile(mapper.ppuRead(0x2000 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)), 0, 8); } } for (int i = 0; i < 32; ++i) { for (int j = 0; j < 30; ++j) { nametableView.setRGB( i * 8 + 255, j * 8, 8, 8, debugGetTile(mapper.ppuRead(0x2400 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)), 0, 8); } } for (int i = 0; i < 32; ++i) { for (int j = 0; j < 30; ++j) { nametableView.setRGB( i * 8, j * 8 + 239, 8, 8, debugGetTile(mapper.ppuRead(0x2800 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)), 0, 8); } } for (int i = 0; i < 32; ++i) { for (int j = 0; j < 30; ++j) { nametableView.setRGB( i * 8 + 255, j * 8 + 239, 8, 8, debugGetTile(mapper.ppuRead(0x2C00 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)), 0, 8); } } // draw the tileset // for (int i = 0; i < 16; ++i) { // for (int j = 0; j < 32; ++j) { // nametableView.setRGB(i * 8, j * 8, 8, 8, // debugGetTile((i + 16 * j) * 16), 0, 8); // } // } // draw the palettes on the bottom. // for (int i = 0; i < 32; ++i) { // for (int j = 0; j < 16; ++j) { // for (int k = 0; k < 16; ++k) { // nametableView.setRGB(j + i * 16, k + 256, nescolor[0][pal[i]]); // } // } // } debuggui.setFrame(nametableView); // debugbuff.clear(); }
private void fetchNTByte() { // fetch nt byte tileAddr = mapper.ppuRead(((loopyV & 0xc00) | 0x2000) + (loopyV & 0x3ff)) * 16 + (bgpattern ? 0x1000 : 0); }
/** runs the emulation for one PPU clock cycle. */ public final void clock() { // cycle based ppu stuff will go here if (cycles == 1) { if (scanline == 0) { dotcrawl = renderingOn(); } if (scanline < 240) { bgcolors[scanline] = pal[0]; } } if (scanline < 240 || scanline == (numscanlines - 1)) { // on all rendering lines if (renderingOn() && ((cycles >= 1 && cycles <= 256) || (cycles >= 321 && cycles <= 336))) { // fetch background tiles, load shift registers bgFetch(); } else if (cycles == 257 && renderingOn()) { // x scroll reset // horizontal bits of loopyV = loopyT loopyV &= ~0x41f; loopyV |= loopyT & 0x41f; } else if (cycles > 257 && cycles <= 341) { // clear the oam address from pxls 257-341 continuously oamaddr = 0; } if ((cycles == 340) && renderingOn()) { // read the same nametable byte twice // this signals the MMC5 to increment the scanline counter fetchNTByte(); fetchNTByte(); } if (cycles == 65 && renderingOn()) { oamstart = oamaddr; } if (cycles == 260 && renderingOn()) { // evaluate sprites for NEXT scanline (as long as either background or sprites are enabled) // this does in fact happen on scanine 261 but it doesn't do anything useful // it's cycle 260 because that's when the first important sprite byte is read // actually sprite overflow should be set by sprite eval somewhat before // so this needs to be split into 2 parts, the eval and the data fetches evalSprites(); } if (scanline == (numscanlines - 1)) { if (cycles == 0) { // turn off vblank, sprite 0, sprite overflow flags vblankflag = false; sprite0hit = false; spriteoverflow = false; } else if (cycles >= 280 && cycles <= 304 && renderingOn()) { // loopyV = (all of)loopyT for each of these cycles loopyV = loopyT; } } } else if (scanline == vblankline && cycles == 1) { // handle vblank on / off vblankflag = true; } if (!renderingOn() || (scanline > 240 && scanline < (numscanlines - 1))) { // HACK ALERT // handle the case of MMC3 mapper watching A12 toggle // even when read or write aren't asserted on the bus // needed to pass Blargg's mmc3 tests mapper.checkA12(loopyV & 0x3fff); } if (scanline < 240) { if (cycles >= 1 && cycles <= 256) { int bufferoffset = (scanline << 8) + (cycles - 1); // bg drawing if (bgOn) { // if background is on, draw a line of that final boolean isBG = drawBGPixel(bufferoffset); // sprite drawing drawSprites(scanline << 8, cycles - 1, isBG); } else if (spritesOn) { // just the sprites then int bgcolor = ((loopyV > 0x3f00 && loopyV < 0x3fff) ? mapper.ppuRead(loopyV) : pal[0]); bitmap[bufferoffset] = bgcolor; drawSprites(scanline << 8, cycles - 1, true); } else { // rendering is off, so draw either the background color OR // if the PPU address points to the palette, draw that color instead. int bgcolor = ((loopyV > 0x3f00 && loopyV < 0x3fff) ? mapper.ppuRead(loopyV) : pal[0]); bitmap[bufferoffset] = bgcolor; } // deal with the grayscale flag if (grayscale) { bitmap[bufferoffset] &= 0x30; } // handle color emphasis bitmap[bufferoffset] = (bitmap[bufferoffset] & 0x3f) | emph; } } // handle nmi if (vblankflag && nmicontrol) { // pull NMI line on when conditions are right mapper.cpu.setNMI(true); } else { mapper.cpu.setNMI(false); } // clock CPU, once every 3 ppu cycles div = (div + 1) % cpudivider[cpudividerctr]; if (div == 0) { mapper.cpu.runcycle(scanline, cycles); mapper.cpucycle(1); cpudividerctr = (cpudividerctr + 1) % cpudivider.length; } if (cycles == 257) { mapper.notifyscanline(scanline); } else if (cycles == 340) { scanline = (scanline + 1) % numscanlines; if (scanline == 0) { ++framecount; } } }
/** * Performs a read from a PPU register, as well as causes any side effects of reading that * specific register. * * @param regnum * @return the data in the PPU register, or open bus (the last value written to a PPU register) if * the register is read only */ public final int read(final int regnum) { switch (regnum) { case 2: even = true; if (scanline == 241) { if (cycles == 1) { // suppress NMI flag if it was just turned on this same cycle vblankflag = false; } // OK, uncommenting this makes blargg's NMI suppression test // work but breaks Antarctic Adventure. // I'm going to need a cycle accurate CPU to fix that... // if (cycles < 4) { // //show vblank flag but cancel pending NMI before the CPU // //can actually do anything with it // //TODO: use proper interface for this // mapper.cpu.nmiNext = false; // } } openbus = (vblankflag ? 0x80 : 0) | (sprite0hit ? 0x40 : 0) | (spriteoverflow ? 0x20 : 0) | (openbus & 0x1f); vblankflag = false; break; case 4: // reading this is NOT reliable but some games do it anyways openbus = OAM[oamaddr]; // System.err.println("codemasters?"); if (renderingOn() && (scanline <= 240)) { if (cycles < 64) { return 0xFF; } else if (cycles <= 256) { return 0x00; } // Micro Machines relies on this: else if (cycles < 320) { return 0xFF; } // and this: else { return secOAM[0]; // is this the right value @ the time? } } break; case 7: // PPUDATA // correct behavior. read is delayed by one // -unless- is a read from sprite pallettes final int temp; if ((loopyV & 0x3fff) < 0x3f00) { temp = readbuffer; readbuffer = mapper.ppuRead(loopyV & 0x3fff); } else { readbuffer = mapper.ppuRead((loopyV & 0x3fff) - 0x1000); temp = mapper.ppuRead(loopyV); } if (!renderingOn() || (scanline > 240 && scanline < (numscanlines - 1))) { loopyV += vraminc; } else { // if 2007 is read during rendering PPU increments both horiz // and vert counters erroneously. incLoopyVHoriz(); incLoopyVVert(); } openbus = temp; break; // and don't increment on read default: return openbus; // last value written to ppu } return openbus; }