content: /*
   * HalfNES by Andrew Hoffman
   * Licensed under the GNU GPL Version 3. See LICENSE file
   */
  package com.grapeshot.halfnes;
  
  import static com.grapeshot.halfnes.PrefsSingleton.get;
  import com.grapeshot.halfnes.mappers.Mapper;
  import com.grapeshot.halfnes.ui.DebugUI;
  import com.grapeshot.halfnes.ui.GUIInterface;
  import static com.grapeshot.halfnes.utils.reverseByte;
  import java.awt.image.BufferedImage;
  import static java.awt.image.BufferedImage.TYPE_INT_BGR;
  import java.util.Arrays;
  import static java.util.Arrays.fill;
  import static java.util.Arrays.fill;
  
  public class PPU {
  
      public Mapper mapper;
      private int oamaddr, oamstart, readbuffer = 0;
      private int loopyV = 0x0;//ppu memory pointer
      private int loopyT = 0x0;//temp pointer
      private int loopyX = 0;//fine x scroll
      public int scanline = 0;
      public int cycles = 0;
      private int framecount = 0;
      private int div = 2;
      private final int[] OAM = new int[256], secOAM = new int[32],
              spriteshiftregH = new int[8],
              spriteshiftregL = new int[8], spriteXlatch = new int[8],
              spritepals = new int[8], bitmap = new int[240 * 256];
      private int found, bgShiftRegH, bgShiftRegL, bgAttrShiftRegH, bgAttrShiftRegL;
      private final boolean[] spritebgflags = new boolean[8];
      private boolean even = true, bgpattern = true, sprpattern, spritesize, nmicontrol,
              grayscale, bgClip, spriteClip, bgOn, spritesOn,
              vblankflag, sprite0hit, spriteoverflow;
      private int emph;
      public final int[] pal;
      private DebugUI debuggui;
      private int vraminc = 1;
      private final static boolean PPUDEBUG = get().getBoolean("ntView", false);
      private BufferedImage nametableView;
      private final int[] bgcolors = new int[256];
      private int openbus = 0; //the last value written to the PPU
      private int nextattr;
      private int linelowbits;
      private int linehighbits;
      private int penultimateattr;
      private int numscanlines;
      private int vblankline;
      private final int[] cpudivider = {3, 3, 3, 3, 3};
  
      public PPU(final Mapper mapper) {
          this.pal = new int[]{0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D,
              0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, 0x09, 0x01, 0x34,
              0x03, 0x00, 0x04, 0x00, 0x14, 0x08, 0x3A, 0x00, 0x02, 0x00, 0x20,
              0x2C, 0x08};
          /*
       power-up pallette checked by Blargg's power_up_palette test. Different
       revs of NES PPU might give different initial results but there's a test
       expecting this set of values and nesemu1, BizHawk, RockNES, MyNes use it
           */
          this.mapper = mapper;
          fill(OAM, 0xff);
          if (PPUDEBUG) {
              nametableView = new BufferedImage(512, 480, TYPE_INT_BGR);
              debuggui = new DebugUI(512, 480);
              debuggui.run();
          }
          setParameters();
      }
  
      final void setParameters() {
          //set stuff to NTSC or PAL or Dendy values
          switch (mapper.getTVType()) {
              case NTSC:
              default:
                  numscanlines = 262;
                  vblankline = 241;
                  cpudivider[0] = 3;
                  break;
              case PAL:
                  numscanlines = 312;
                  vblankline = 241;
                  cpudivider[0] = 4;
                  break;
              case DENDY:
                  numscanlines = 312;
                  vblankline = 291;
                  cpudivider[0] = 3;
                  break;
          }
      }
  
      public void runFrame() {
          for (int line = 0; line < numscanlines; ++line) {
              clockLine(line);
          }
      }
  
      /**
       * Performs a read from a PPU register, as well as causes any side effects
       * of reading that specific register.
       *
       * @param regnum register to read (address with 0x2000 already subtracted)
       * @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;
      }
  
      /**
       * Performs a write to a PPU register
       *
       * @param regnum register number from 0 to 7, memory addresses are decoded
       * to these elsewhere
       * @param data the value to write to the register (0x00 to 0xff valid)
       */
      public final void write(final int regnum, final int data) {
  //        if (regnum != 4 /*&& regnum != 7*/) {
  //            System.err.println("PPU write - wrote " + utils.hex(data) + " to reg "
  //                    + utils.hex(regnum + 0x2000)
  //                    + " frame " + framecount + " scanline " + scanline);
  //        }
          //debugdraw();
          openbus = data;
          switch (regnum) {
              case 0: //PPUCONTROL (2000)
                  //set 2 bits of vram address (nametable select)
                  //bits 0 and 1 affect loopyT to change nametable start by 0x400
                  loopyT &= ~0xc00;
                  loopyT |= (data & 3) << 10;
                  /*
                   SMB1 writes here at the end of its main loop and if this write
                   lands on one exact PPU clock, the address bits are set to 0.
                   This only happens on one CPU/PPU alignment of real hardware 
                   though so it only shows up ~33% of the time.
                   */
                  vraminc = (((data & (utils.BIT2)) != 0) ? 32 : 1);
                  sprpattern = ((data & (utils.BIT3)) != 0);
                  bgpattern = ((data & (utils.BIT4)) != 0);
                  spritesize = ((data & (utils.BIT5)) != 0);
                  /*bit 6 is kind of a halt and catch fire situation since it outputs
                   ppu color data on the EXT pins that are tied to ground if set
                   and that'll make the PPU get very hot from sourcing the current. 
                   Only really useful for the NESRGB interposer board, kind of
                   useless for emulators. I will ignore it.
                   */
                  nmicontrol = ((data & (utils.BIT7)) != 0);
  
                  break;
              case 1: //PPUMASK (2001)
                  grayscale = ((data & (utils.BIT0)) != 0);
                  bgClip = !((data & (utils.BIT1)) != 0); //clip left 8 pixels when its on
                  spriteClip = !((data & (utils.BIT2)) != 0);
                  bgOn = ((data & (utils.BIT3)) != 0);
                  spritesOn = ((data & (utils.BIT4)) != 0);
                  emph = (data & 0xe0) << 1;
                  if (numscanlines == 312) {
                      //if PAL switch position of red and green emphasis bits (6 and 5)
                      //red is bit 6 -> bit 7
                      //green is bit 7 -> bit 6
                      int red = (emph >> 6) & 1;
                      int green = (emph >> 7) & 1;
                      emph &= 0xf3f;
                      emph |= (red << 7) | (green << 6);
                  }
                  break;
              case 3:
                  // PPUOAMADDR (2003)
                  // most games just write zero and use the dma
                  oamaddr = data & 0xff;
                  break;
              case 4:
                  // PPUOAMDATA(2004)
                  if ((oamaddr & 3) == 2) {
                      OAM[oamaddr++] = (data & 0xE3);
                  } else {
                      OAM[oamaddr++] = data;
                  }
                  oamaddr &= 0xff;
                  // games don't usually write this directly anyway, it's unreliable
                  break;
  
              // PPUSCROLL(2005)
              case 5:
                  if (even) {
                      // update horizontal scroll
                      loopyT &= ~0x1f;
                      loopyX = data & 7;
                      loopyT |= data >> 3;
  
                      even = false;
                  } else {
                      // update vertical scroll
                      loopyT &= ~0x7000;
                      loopyT |= ((data & 7) << 12);
                      loopyT &= ~0x3e0;
                      loopyT |= (data & 0xf8) << 2;
                      even = true;
  
                  }
                  break;
  
              case 6:
                  // PPUADDR (2006)
                  if (even) {
                      // high byte
                      loopyT &= 0xc0ff;
                      loopyT |= ((data & 0x3f) << 8);
                      loopyT &= 0x3fff;
                      even = false;
                  } else {
                      loopyT &= 0xfff00;
                      loopyT |= data;
                      loopyV = loopyT;
                      even = true;
                  }
                  break;
              case 7:
                  // PPUDATA             
                  mapper.ppuWrite((loopyV & 0x3fff), data);
                  if (!renderingOn() || (scanline > 240 && scanline < (numscanlines - 1))) {
                      loopyV += vraminc;
                  } else if ((loopyV & 0x7000) == 0x7000) {
                      int YScroll = loopyV & 0x3E0;
                      loopyV &= 0xFFF;
                      switch (YScroll) {
                          case 0x3A0:
                              loopyV ^= 0xBA0;
                              break;
                          case 0x3E0:
                              loopyV ^= 0x3E0;
                              break;
                          default:
                              loopyV += 0x20;
                              break;
                      }
                  } else {
                      // while rendering, it seems to drop by 1 line, regardless of increment mode
                      loopyV += 0x1000;
                  }
                  break;
              default:
                  break;
          }
      }
  
      /**
       * PPU is on if either background or sprites are enabled
       *
       * @return true
       */
      public boolean renderingOn() {
          return bgOn || spritesOn;
      }
  
      /**
       * MMC3 scan line counter isn't clocked if background and sprites are using
       * the same half of the pattern table
       *
       * @return true if PPU is rendering and BG and sprites are using different
       * pattern tables
       */
      public final boolean mmc3CounterClocking() {
          return (bgpattern != sprpattern) && renderingOn();
      }
  
      /**
       * Runs the PPU emulation for one NES scan line.
       */
      public final void clockLine(int scanline) {
          //skip a PPU clock on line 0 of odd frames when rendering is on
          //and we are in NTSC mode (pal has no skip)
          int skip = (numscanlines == 262
                  && scanline == 0
                  && renderingOn()
                  && !((framecount & (utils.BIT1)) != 0)) ? 1 : 0;
          for (cycles = skip; cycles < 341; ++cycles) {
              clock();
          }
      }
  
      private int tileAddr = 0;
      private int cpudividerctr = 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 scanline 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 && cycles >= 1 && cycles <= 256) {
              int bufferoffset = (scanline << 8) + (cycles - 1);
              //bg drawing
              if (bgOn) { //if background is on, draw a dot of that first
                  final boolean isBG = drawBGPixel(bufferoffset);
                  //sprite drawing
                  drawSprites(scanline, 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, 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;
              }
          }
      }
  
      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();
          }
      }
  
      private void incLoopyVVert() {
          //increment loopy_v to next row of tiles
          if ((loopyV & 0x7000) == 0x7000) {
              //reset the fine scroll bits and increment tile address to next row
              loopyV &= ~0x7000;
              int y = (loopyV & 0x03E0) >> 5;
              if (y == 29) {
                  //if row is 29 zero fine scroll and bump to next nametable
                  y = 0;
                  loopyV ^= 0x0800;
              } else {
                  //increment (wrap to 5 bits) but if row is already over 29
                  //we don't bump loopyV to next nt.
                  y = (y + 1) & 31;
              }
              loopyV = (loopyV & ~0x03E0) | (y << 5);
          } else {
              //increment the fine scroll
              loopyV += 0x1000;
          }
      }
  
      private void incLoopyVHoriz() {
          //increment horizontal part of loopyv
          if ((loopyV & 0x001F) == 31) // if coarse X == 31
          {
              loopyV &= ~0x001F; // coarse X = 0
              loopyV ^= 0x0400;// switch horizontal nametable
          } else {
              loopyV += 1;// increment coarse X
          }
      }
  
      private void fetchNTByte() {
          //fetch nt byte
          tileAddr = mapper.ppuRead(
                  ((loopyV & 0xc00) | 0x2000) + (loopyV & 0x3ff)) * 16
                  + (bgpattern ? 0x1000 : 0);
      }
  
      private boolean drawBGPixel(int bufferoffset) {
          //background drawing
          //loopyX picks bits
          final boolean isBG;
          if (bgClip && (bufferoffset & 0xff) < 8) {
              //left hand of screen clipping
              //(needs to be marked as BG and not cause a sprite hit)
              bitmap[bufferoffset] = pal[0];
              isBG = true;
          } else {
              final int bgPix = (((bgShiftRegH >> -loopyX + 16) & 1) << 1)
                      + ((bgShiftRegL >> -loopyX + 16) & 1);
              final int bgPal = (((bgAttrShiftRegH >> -loopyX + 8) & 1) << 1)
                      + ((bgAttrShiftRegL >> -loopyX + 8) & 1);
              isBG = (bgPix == 0);
              bitmap[bufferoffset] = isBG ? pal[0] : pal[(bgPal << 2) + bgPix];
          }
          bgShiftClock();
          return isBG;
      }
  
      private void bgShiftClock() {
          bgShiftRegH <<= 1;
          bgShiftRegL <<= 1;
          bgAttrShiftRegH <<= 1;
          bgAttrShiftRegL <<= 1;
      }
  
      boolean dotcrawl = true;
      private boolean sprite0here = false;
  
      /**
       * evaluates PPU sprites for the NEXT scanline
       */
      private void evalSprites() {
          sprite0here = false;
          int ypos, offset;
          found = 0;
          Arrays.fill(secOAM, 0xff);
          //primary evaluation
          //need to emulate behavior when OAM address is set to nonzero here
          for (int spritestart = oamstart; spritestart < 255; spritestart += 4) {
              //for each sprite, first we cull the non-visible ones
              ypos = OAM[spritestart];
              offset = scanline - ypos;
              if (ypos > scanline || offset > (spritesize ? 15 : 7)) {
                  //sprite is out of range vertically
                  continue;
              }
              //if we're here it's a valid renderable sprite
              if (spritestart == 0) {
                  sprite0here = true;
              }
              //actually which sprite is flagged for sprite 0 depends on the starting
              //oam address which is, on the real thing, not necessarily zero.
              if (found >= 8) {
                  //if more than 8 sprites, set overflow bit and STOP looking
                  //todo: add "no sprite limit" option back
                  spriteoverflow = true;
                  break; //also the real PPU does strange stuff on sprite overflow
                  //todo: emulate register trashing that happens when overflow
              } else {
                  //set up ye sprite for rendering
                  secOAM[found * 4] = OAM[spritestart];
  //                secOAM[found * 4 + 1] = OAM[spritestart + 1];
  //                secOAM[found * 4 + 2] = OAM[spritestart + 2];
  //                secOAM[found * 4 + 3] = OAM[spritestart + 3];
                  final int oamextra = OAM[spritestart + 2];
  
                  //bg flag
                  spritebgflags[found] = ((oamextra & (utils.BIT5)) != 0);
                  //x value
                  spriteXlatch[found] = OAM[spritestart + 3];
                  spritepals[found] = ((oamextra & 3) + 4) * 4;
                  if (((oamextra & (utils.BIT7)) != 0)) {
                      //if sprite is flipped vertically, reverse the offset
                      offset = (spritesize ? 15 : 7) - offset;
                  }
                  //now correction for the fact that 8x16 tiles are 2 separate tiles
                  if (offset > 7) {
                      offset += 8;
                  }
                  //get tile address (8x16 sprites can use both pattern tbl pages but only the even tiles)
                  final int tilenum = OAM[spritestart + 1];
                  spriteFetch(spritesize, tilenum, offset, oamextra);
                  ++found;
              }
          }
          for (int i = found; i < 8; ++i) {
              //fill unused sprite registers with zeros
              spriteshiftregL[found] = 0;
              spriteshiftregH[found] = 0;
              //also, we need to do 8 reads no matter how many sprites we found
              //dummy reads are to sprite 0xff
              spriteFetch(spritesize, 0xff, 0, 0);
          }
      }
  
      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);
          }
      }
  
      /**
       * draws appropriate pixel of the sprites selected by sprite evaluation
       */
      private void drawSprites(int line, int x, boolean bgflag) {
          final int startdraw = !spriteClip ? 0 : 8;//sprite left 8 pixels clip
          int sprpxl = 0;
          int index = 7;
          //check all the used sprite slots to see if any sprite covers this pixel
          for (int y = found - 1; y >= 0; --y) {
              int off = x - spriteXlatch[y];
              if (off >= 0 && off <= 8) {
                  if ((spriteshiftregH[y] & 1) + (spriteshiftregL[y] & 1) != 0) {
                      index = y;
                      sprpxl = 2 * (spriteshiftregH[y] & 1) + (spriteshiftregL[y] & 1);
                  }
                  spriteshiftregH[y] >>= 1;
                  spriteshiftregL[y] >>= 1;
              }
          }
          if (sprpxl == 0 || x < startdraw || !spritesOn) {
              //no opaque sprite pixel here
              return;
          }
  
          if (sprite0here && (index == 0) && !bgflag
                  && x < 255) {
              //sprite 0 hit!
              sprite0hit = true;
          }
          //now, FINALLY, drawing.
          if (!spritebgflags[index] || bgflag) {
              bitmap[(line << 8) + x] = pal[spritepals[index] + sprpxl];
          }
      }
  
      /**
       * 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();
      }
  
      /**
       * 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.
       *
       * @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;
      }
  
      /**
       * Sends off a frame of NES video to be rendered by the GUI. also includes
       * dot crawl flag and BG color to be displayed around edges which are needed
       * for the NTSC renderer.
       *
       * @param gui the GUI window to render to
       */
      public final void renderFrame(GUIInterface gui) {
          if (PPUDEBUG) {
              debugDraw();
          }
          if (gui != null) {
              gui.setFrame(bitmap, bgcolors, dotcrawl);
          }
  
      }
  }
  