diff options
Diffstat (limited to 'js.2/ppu.js')
-rw-r--r-- | js.2/ppu.js | 1846 |
1 files changed, 1846 insertions, 0 deletions
diff --git a/js.2/ppu.js b/js.2/ppu.js new file mode 100644 index 0000000..f49d250 --- /dev/null +++ b/js.2/ppu.js @@ -0,0 +1,1846 @@ +NES.PPU = function(nes) { + this.nes = nes; + + // Keep Chrome happy + this.vramMem = null; + this.spriteMem = null; + this.vramAddress = null; + this.vramTmpAddress = null; + this.vramBufferedReadValue = null; + this.firstWrite = null; + this.sramAddress = null; + this.mapperIrqCounter = null; + this.currentMirroring = null; + this.requestEndFrame = null; + this.nmiOk = null; + this.dummyCycleToggle = null; + this.validTileData = null; + this.nmiCounter = null; + this.scanlineAlreadyRendered = null; + this.f_nmiOnVblank = null; + this.f_spriteSize = null; + this.f_bgPatternTable = null; + this.f_spPatternTable = null; + this.f_addrInc = null; + this.f_nTblAddress = null; + this.f_color = null; + this.f_spVisibility = null; + this.f_bgVisibility = null; + this.f_spClipping = null; + this.f_bgClipping = null; + this.f_dispType = null; + this.cntFV = null; + this.cntV = null; + this.cntH = null; + this.cntVT = null; + this.cntHT = null; + this.regFV = null; + this.regV = null; + this.regH = null; + this.regVT = null; + this.regHT = null; + this.regFH = null; + this.regS = null; + this.curNt = null; + this.attrib = null; + this.buffer = null; + this.prevBuffer = null; + this.bgbuffer = null; + this.pixrendered = null; + this.spr0dummybuffer = null; + this.dummyPixPriTable = null; + this.validTileData = null; + this.scantile = null; + this.scanline = null; + this.lastRenderedScanline = null; + this.curX = null; + this.sprX = null; + this.sprY = null; + this.sprTile = null; + this.sprCol = null; + this.vertFlip = null; + this.horiFlip = null; + this.bgPriority = null; + this.spr0HitX = null; + this.spr0HitY = null; + this.hitSpr0 = null; + this.sprPalette = null; + this.imgPalette = null; + this.ptTile = null; + this.ntable1 = null; + this.currentMirroring = null; + this.nameTable = null; + this.vramMirrorTable = null; + this.palTable = null; + + + // Rendering Options: + this.showSpr0Hit = false; + this.clipToTvSize = true; + + this.canvas = document.getElementById('screen'); + this.canvasContext = this.canvas.getContext('2d'); + this.canvasImageData = this.canvasContext.getImageData(0, 0, 256, 240); + this.canvasContext.fillStyle = 'black'; + this.canvasContext.fillRect(0, 0, 256, 240); /* set alpha to opaque */ + // Set alpha + for (var i = 3; i < this.canvasImageData.data.length-3; i+=4) { + this.canvasImageData.data[i] = 0xFF; + } + + this.reset(); +} + +NES.PPU.prototype = { + // Status flags: + STATUS_VRAMWRITE: 4, + STATUS_SLSPRITECOUNT: 5, + STATUS_SPRITE0HIT: 6, + STATUS_VBLANK: 7, + + reset: function() { + // Memory + this.vramMem = new Array(0x8000); + this.spriteMem = new Array(0x100); + for (var i=0; i<this.vramMem.length; i++) { + this.vramMem[i] = 0; + } + for (var i=0; i<this.spriteMem.length; i++) { + this.spriteMem[i] = 0; + } + + // VRAM I/O: + this.vramAddress = null; + this.vramTmpAddress = null; + this.vramBufferedReadValue = 0; + this.firstWrite = true; // VRAM/Scroll Hi/Lo latch + + // SPR-RAM I/O: + this.sramAddress = 0; // 8-bit only. + + this.mapperIrqCounter = 0; + this.currentMirroring = -1; + this.requestEndFrame = false; + this.nmiOk = false; + this.dummyCycleToggle = false; + this.validTileData = false; + this.nmiCounter = 0; + this.scanlineAlreadyRendered = null; + + // Control Flags Register 1: + this.f_nmiOnVblank = 0; // NMI on VBlank. 0=disable, 1=enable + this.f_spriteSize = 0; // Sprite size. 0=8x8, 1=8x16 + this.f_bgPatternTable = 0; // Background Pattern Table address. 0=0x0000,1=0x1000 + this.f_spPatternTable = 0; // Sprite Pattern Table address. 0=0x0000,1=0x1000 + this.f_addrInc = 0; // PPU Address Increment. 0=1,1=32 + this.f_nTblAddress = 0; // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00 + + // Control Flags Register 2: + this.f_color = 0; // Background color. 0=black, 1=blue, 2=green, 4=red + this.f_spVisibility = 0; // Sprite visibility. 0=not displayed,1=displayed + this.f_bgVisibility = 0; // Background visibility. 0=Not Displayed,1=displayed + this.f_spClipping = 0; // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping + this.f_bgClipping = 0; // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping + this.f_dispType = 0; // Display type. 0=color, 1=monochrome + + // Counters: + this.cntFV = 0; + this.cntV = 0; + this.cntH = 0; + this.cntVT = 0; + this.cntHT = 0; + + // Registers: + this.regFV = 0; + this.regV = 0; + this.regH = 0; + this.regVT = 0; + this.regHT = 0; + this.regFH = 0; + this.regS = 0; + + // These are temporary variables used in rendering and sound procedures. + // Their states outside of those procedures can be ignored. + // TODO: the use of this is a bit weird, investigate + this.curNt = null; + + // Variables used when rendering: + this.attrib = new Array(32); + this.buffer = new Array(256*240); + this.prevBuffer = new Array(256*240); + this.bgbuffer = new Array(256*240); + this.pixrendered = new Array(256*240); + this.spr0dummybuffer = new Array(256*240); + this.dummyPixPriTable = new Array(256*240); + + this.validTileData = null; + + this.scantile = new Array(32); + + // Initialize misc vars: + this.scanline = 0; + this.lastRenderedScanline = -1; + this.curX = 0; + + // Sprite data: + this.sprX = new Array(64); // X coordinate + this.sprY = new Array(64); // Y coordinate + this.sprTile = new Array(64); // Tile Index (into pattern table) + this.sprCol = new Array(64); // Upper two bits of color + this.vertFlip = new Array(64); // Vertical Flip + this.horiFlip = new Array(64); // Horizontal Flip + this.bgPriority = new Array(64); // Background priority + this.spr0HitX = 0; // Sprite #0 hit X coordinate + this.spr0HitY = 0; // Sprite #0 hit Y coordinate + this.hitSpr0 = false; + + // Palette data: + this.sprPalette = new Array(16); + this.imgPalette = new Array(16); + + // Create pattern table tile buffers: + this.ptTile = new Array(512); + for(var i=0;i<512;i++){ + this.ptTile[i] = new NES.PPU.Tile(); + } + + // Create nametable buffers: + // Name table data: + this.ntable1 = new Array(4); + this.currentMirroring = -1; + this.nameTable = new Array(4); + for(var i=0;i<4;i++){ + this.nameTable[i] = new NES.PPU.NameTable(32, 32, "Nt"+i); + } + + // Initialize mirroring lookup table: + this.vramMirrorTable = new Array(0x8000); + for(var i=0;i<0x8000;i++){ + this.vramMirrorTable[i] = i; + } + + this.palTable = new NES.PPU.PaletteTable(); + this.palTable.loadNTSCPalette(); + //this.palTable.loadDefaultPalette(); + + this.updateControlReg1(0); + this.updateControlReg2(0); + }, + + // Sets Nametable mirroring. + setMirroring: function(mirroring){ + + if(mirroring == this.currentMirroring){ + return; + } + + this.currentMirroring = mirroring; + this.triggerRendering(); + + // Remove mirroring: + if(this.vramMirrorTable==null){ + this.vramMirrorTable = new Array(0x8000); + } + for(var i=0;i<0x8000;i++){ + this.vramMirrorTable[i] = i; + } + + // Palette mirroring: + this.defineMirrorRegion(0x3f20,0x3f00,0x20); + this.defineMirrorRegion(0x3f40,0x3f00,0x20); + this.defineMirrorRegion(0x3f80,0x3f00,0x20); + this.defineMirrorRegion(0x3fc0,0x3f00,0x20); + + // Additional mirroring: + this.defineMirrorRegion(0x3000,0x2000,0xf00); + this.defineMirrorRegion(0x4000,0x0000,0x4000); + + if(mirroring == this.nes.rom.HORIZONTAL_MIRRORING){ + // Horizontal mirroring. + + this.ntable1[0] = 0; + this.ntable1[1] = 0; + this.ntable1[2] = 1; + this.ntable1[3] = 1; + + this.defineMirrorRegion(0x2400,0x2000,0x400); + this.defineMirrorRegion(0x2c00,0x2800,0x400); + + }else if(mirroring == this.nes.rom.VERTICAL_MIRRORING){ + // Vertical mirroring. + + this.ntable1[0] = 0; + this.ntable1[1] = 1; + this.ntable1[2] = 0; + this.ntable1[3] = 1; + + this.defineMirrorRegion(0x2800,0x2000,0x400); + this.defineMirrorRegion(0x2c00,0x2400,0x400); + + }else if(mirroring == this.nes.rom.SINGLESCREEN_MIRRORING){ + + // Single Screen mirroring + + this.ntable1[0] = 0; + this.ntable1[1] = 0; + this.ntable1[2] = 0; + this.ntable1[3] = 0; + + this.defineMirrorRegion(0x2400,0x2000,0x400); + this.defineMirrorRegion(0x2800,0x2000,0x400); + this.defineMirrorRegion(0x2c00,0x2000,0x400); + + }else if(mirroring == this.nes.rom.SINGLESCREEN_MIRRORING2){ + + + this.ntable1[0] = 1; + this.ntable1[1] = 1; + this.ntable1[2] = 1; + this.ntable1[3] = 1; + + this.defineMirrorRegion(0x2400,0x2400,0x400); + this.defineMirrorRegion(0x2800,0x2400,0x400); + this.defineMirrorRegion(0x2c00,0x2400,0x400); + + }else{ + + // Assume Four-screen mirroring. + + this.ntable1[0] = 0; + this.ntable1[1] = 1; + this.ntable1[2] = 2; + this.ntable1[3] = 3; + + } + + }, + + + // Define a mirrored area in the address lookup table. + // Assumes the regions don't overlap. + // The 'to' region is the region that is physically in memory. + defineMirrorRegion: function(fromStart, toStart, size){ + for(var i=0;i<size;i++){ + this.vramMirrorTable[fromStart+i] = toStart+i; + } + }, + + startVBlank: function(){ + + // Do NMI: + this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI); + + // Make sure everything is rendered: + if(this.lastRenderedScanline < 239){ + this.renderFramePartially( + this.lastRenderedScanline+1,240-this.lastRenderedScanline + ); + } + + // End frame: + this.endFrame(); + + // Reset scanline counter: + this.lastRenderedScanline = -1; + }, + + endScanline: function(){ + switch (this.scanline) { + case 19: + // Dummy scanline. + // May be variable length: + if(this.dummyCycleToggle){ + + // Remove dead cycle at end of scanline, + // for next scanline: + this.curX = 1; + this.dummyCycleToggle = !this.dummyCycleToggle; + + } + break; + + case 20: + // Clear VBlank flag: + this.setStatusFlag(this.STATUS_VBLANK,false); + + // Clear Sprite #0 hit flag: + this.setStatusFlag(this.STATUS_SPRITE0HIT,false); + this.hitSpr0 = false; + this.spr0HitX = -1; + this.spr0HitY = -1; + + if(this.f_bgVisibility == 1 || this.f_spVisibility==1){ + + // Update counters: + this.cntFV = this.regFV; + this.cntV = this.regV; + this.cntH = this.regH; + this.cntVT = this.regVT; + this.cntHT = this.regHT; + + if(this.f_bgVisibility==1){ + // Render dummy scanline: + this.renderBgScanline(false,0); + } + + } + + if(this.f_bgVisibility==1 && this.f_spVisibility==1){ + + // Check sprite 0 hit for first scanline: + this.checkSprite0(0); + + } + + if(this.f_bgVisibility==1 || this.f_spVisibility==1){ + // Clock mapper IRQ Counter: + this.nes.mmap.clockIrqCounter(); + } + break; + + case 261: + // Dead scanline, no rendering. + // Set VINT: + this.setStatusFlag(this.STATUS_VBLANK,true); + this.requestEndFrame = true; + this.nmiCounter = 9; + + // Wrap around: + this.scanline = -1; // will be incremented to 0 + + break; + + default: + if(this.scanline>=21 && this.scanline<=260){ + + // Render normally: + if(this.f_bgVisibility == 1){ + + if(!this.scanlineAlreadyRendered){ + // update scroll: + this.cntHT = this.regHT; + this.cntH = this.regH; + this.renderBgScanline(true,this.scanline+1-21); + } + this.scanlineAlreadyRendered=false; + + // Check for sprite 0 (next scanline): + if(!this.hitSpr0 && this.f_spVisibility==1){ + if(this.sprX[0]>=-7 && this.sprX[0]<256 && this.sprY[0]+1<=(this.scanline-20) && (this.sprY[0]+1+(this.f_spriteSize==0?8:16))>=(this.scanline-20)){ + + if(this.checkSprite0( + this.scanline-20)){ + //console.log("found spr0. curscan="+this.scanline+" hitscan="+this.spr0HitY); + this.hitSpr0 = true; + } + } + } + + } + + if(this.f_bgVisibility==1 || this.f_spVisibility==1){ + // Clock mapper IRQ Counter: + this.nes.mmap.clockIrqCounter(); + } + } + } + + this.scanline++; + this.regsToAddress(); + this.cntsToAddress(); + + }, + + startFrame: function(){ + // Set background color: + var bgColor=0; + + if(this.f_dispType == 0){ + + // Color display. + // f_color determines color emphasis. + // Use first entry of image palette as BG color. + bgColor = this.imgPalette[0]; + + }else{ + + // Monochrome display. + // f_color determines the bg color. + switch(this.f_color){ + case 0:{ + // Black + bgColor = 0x00000; + break; + } + case 1:{ + // Green + bgColor = 0x00FF00; + } + case 2:{ + // Blue + bgColor = 0xFF0000; + } + case 3:{ + // Invalid. Use black. + bgColor = 0x000000; + } + case 4:{ + // Red + bgColor = 0x0000FF; + } + default:{ + // Invalid. Use black. + bgColor = 0x0; + } + } + + } + + var buffer = this.buffer; + for(var i=0;i<256*240;i++) { + buffer[i] = bgColor; + } + var pixrendered = this.pixrendered; + for(var i=0;i<pixrendered.length;i++) { + pixrendered[i]=65; + } + + }, + + endFrame: function(){ + + var buffer = this.buffer; + + // Draw spr#0 hit coordinates: + if(this.showSpr0Hit){ + // Spr 0 position: + if(this.sprX[0]>=0 && this.sprX[0]<256 && this.sprY[0]>=0 && this.sprY[0]<240){ + for(var i=0;i<256;i++){ + buffer[(this.sprY[0]<<8)+i] = 0xFF5555; + } + for(var i=0;i<240;i++){ + buffer[(i<<8)+this.sprX[0]] = 0xFF5555; + } + } + // Hit position: + if(this.spr0HitX>=0 && this.spr0HitX<256 && this.spr0HitY>=0 && this.spr0HitY<240){ + for(var i=0;i<256;i++){ + buffer[(this.spr0HitY<<8)+i] = 0x55FF55; + } + for(var i=0;i<240;i++){ + buffer[(i<<8)+this.spr0HitX] = 0x55FF55; + } + } + } + + // This is a bit lazy.. + // if either the sprites or the background should be clipped, + // both are clipped after rendering is finished. + if(this.clipToTvSize || this.f_bgClipping==0 || this.f_spClipping==0){ + // Clip left 8-pixels column: + for(var y=0;y<240;y++){ + for(var x=0;x<8;x++){ + buffer[(y<<8)+x] = 0; + } + } + } + + if(this.clipToTvSize){ + // Clip right 8-pixels column too: + for(var y=0;y<240;y++){ + for(var x=0;x<8;x++){ + buffer[(y<<8)+255-x] = 0; + } + } + } + + // Clip top and bottom 8 pixels: + if(this.clipToTvSize){ + for(var y=0;y<8;y++){ + for(var x=0;x<256;x++){ + buffer[(y<<8)+x] = 0; + buffer[((239-y)<<8)+x] = 0; + } + } + } + + if (this.nes.opts.showDisplay) { + var imageData = this.canvasImageData.data; + var prevBuffer = this.prevBuffer; + + for (var i=0;i<256*240;i++) { + var pixel = buffer[i]; + + if (pixel != prevBuffer[i]) { + var j = i*4; + imageData[j] = pixel&0xFF; + imageData[j+1] = (pixel>>8)&0xFF; + imageData[j+2] = (pixel>>16)&0xFF; + prevBuffer[i] = pixel; + } + } + + this.canvasContext.putImageData(this.canvasImageData, 0, 0); + } + }, + + updateControlReg1: function(value){ + + this.triggerRendering(); + + this.f_nmiOnVblank = (value>>7)&1; + this.f_spriteSize = (value>>5)&1; + this.f_bgPatternTable = (value>>4)&1; + this.f_spPatternTable = (value>>3)&1; + this.f_addrInc = (value>>2)&1; + this.f_nTblAddress = value&3; + + this.regV = (value>>1)&1; + this.regH = value&1; + this.regS = (value>>4)&1; + + }, + + updateControlReg2: function(value){ + + this.triggerRendering(); + + this.f_color = (value>>5)&7; + this.f_spVisibility = (value>>4)&1; + this.f_bgVisibility = (value>>3)&1; + this.f_spClipping = (value>>2)&1; + this.f_bgClipping = (value>>1)&1; + this.f_dispType = value&1; + + if(this.f_dispType == 0){ + this.palTable.setEmphasis(this.f_color); + } + this.updatePalettes(); + }, + + setStatusFlag: function(flag, value){ + var n = 1<<flag; + this.nes.cpu.mem[0x2002] = + ((this.nes.cpu.mem[0x2002] & (255-n)) | (value?n:0)); + }, + + // CPU Register $2002: + // Read the Status Register. + readStatusRegister: function(){ + + var tmp = this.nes.cpu.mem[0x2002]; + + // Reset scroll & VRAM Address toggle: + this.firstWrite = true; + + // Clear VBlank flag: + this.setStatusFlag(this.STATUS_VBLANK,false); + + // Fetch status data: + return tmp; + + }, + + // CPU Register $2003: + // Write the SPR-RAM address that is used for sramWrite (Register 0x2004 in CPU memory map) + writeSRAMAddress: function(address){ + this.sramAddress = address; + }, + + // CPU Register $2004 (R): + // Read from SPR-RAM (Sprite RAM). + // The address should be set first. + sramLoad: function(){ + /*short tmp = sprMem.load(sramAddress); + sramAddress++; // Increment address + sramAddress%=0x100; + return tmp;*/ + return this.spriteMem[this.sramAddress] + }, + + // CPU Register $2004 (W): + // Write to SPR-RAM (Sprite RAM). + // The address should be set first. + sramWrite: function(value){ + this.spriteMem[this.sramAddress] = value; + this.spriteRamWriteUpdate(this.sramAddress,value); + this.sramAddress++; // Increment address + this.sramAddress %= 0x100; + }, + + // CPU Register $2005: + // Write to scroll registers. + // The first write is the vertical offset, the second is the + // horizontal offset: + scrollWrite: function(value){ + this.triggerRendering(); + + if(this.firstWrite){ + // First write, horizontal scroll: + this.regHT = (value>>3)&31; + this.regFH = value&7; + + }else{ + + // Second write, vertical scroll: + this.regFV = value&7; + this.regVT = (value>>3)&31; + + } + this.firstWrite = !this.firstWrite; + + }, + + // CPU Register $2006: + // Sets the adress used when reading/writing from/to VRAM. + // The first write sets the high byte, the second the low byte. + writeVRAMAddress: function(address){ + + if(this.firstWrite){ + + this.regFV = (address>>4)&3; + this.regV = (address>>3)&1; + this.regH = (address>>2)&1; + this.regVT = (this.regVT&7) | ((address&3)<<3); + + }else{ + this.triggerRendering(); + + this.regVT = (this.regVT&24) | ((address>>5)&7); + this.regHT = address&31; + + this.cntFV = this.regFV; + this.cntV = this.regV; + this.cntH = this.regH; + this.cntVT = this.regVT; + this.cntHT = this.regHT; + + this.checkSprite0(this.scanline-20); + + } + + this.firstWrite = !this.firstWrite; + + // Invoke mapper latch: + this.cntsToAddress(); + if(this.vramAddress < 0x2000){ + this.nes.mmap.latchAccess(this.vramAddress); + } + }, + + // CPU Register $2007(R): + // Read from PPU memory. The address should be set first. + vramLoad: function(){ + + this.cntsToAddress(); + this.regsToAddress(); + + // If address is in range 0x0000-0x3EFF, return buffered values: + if(this.vramAddress <= 0x3EFF){ + + var tmp = this.vramBufferedReadValue; + + // Update buffered value: + if(this.vramAddress < 0x2000){ + this.vramBufferedReadValue = this.vramMem[this.vramAddress]; + }else{ + this.vramBufferedReadValue = this.mirroredLoad(this.vramAddress); + } + + // Mapper latch access: + if(this.vramAddress < 0x2000){ + this.nes.mmap.latchAccess(this.vramAddress); + } + + // Increment by either 1 or 32, depending on d2 of Control Register 1: + this.vramAddress += (this.f_addrInc==1?32:1); + + this.cntsFromAddress(); + this.regsFromAddress(); + return tmp; // Return the previous buffered value. + + } + + // No buffering in this mem range. Read normally. + var tmp = this.mirroredLoad(this.vramAddress); + + // Increment by either 1 or 32, depending on d2 of Control Register 1: + this.vramAddress += (this.f_addrInc==1?32:1); + + this.cntsFromAddress(); + this.regsFromAddress(); + + return tmp; + + }, + + // CPU Register $2007(W): + // Write to PPU memory. The address should be set first. + vramWrite: function(value){ + + this.triggerRendering(); + this.cntsToAddress(); + this.regsToAddress(); + + if(this.vramAddress >= 0x2000){ + // Mirroring is used. + this.mirroredWrite(this.vramAddress,value); + }else{ + + // Write normally. + this.writeMem(this.vramAddress,value); + + // Invoke mapper latch: + this.nes.mmap.latchAccess(this.vramAddress); + + } + + // Increment by either 1 or 32, depending on d2 of Control Register 1: + this.vramAddress += (this.f_addrInc==1?32:1); + this.regsFromAddress(); + this.cntsFromAddress(); + + }, + + // CPU Register $4014: + // Write 256 bytes of main memory + // into Sprite RAM. + sramDMA: function(value){ + var baseAddress = value * 0x100; + var data; + for(var i=this.sramAddress; i < 256; i++){ + data = this.nes.cpu.mem[baseAddress+i]; + this.spriteMem[i] = data; + this.spriteRamWriteUpdate(i, data); + } + + this.nes.cpu.haltCycles(513); + + }, + + // Updates the scroll registers from a new VRAM address. + regsFromAddress: function(){ + + var address = (this.vramTmpAddress>>8)&0xFF; + this.regFV = (address>>4)&7; + this.regV = (address>>3)&1; + this.regH = (address>>2)&1; + this.regVT = (this.regVT&7) | ((address&3)<<3); + + address = this.vramTmpAddress&0xFF; + this.regVT = (this.regVT&24) | ((address>>5)&7); + this.regHT = address&31; + }, + + // Updates the scroll registers from a new VRAM address. + cntsFromAddress: function(){ + + var address = (this.vramAddress>>8)&0xFF; + this.cntFV = (address>>4)&3; + this.cntV = (address>>3)&1; + this.cntH = (address>>2)&1; + this.cntVT = (this.cntVT&7) | ((address&3)<<3); + + address = this.vramAddress&0xFF; + this.cntVT = (this.cntVT&24) | ((address>>5)&7); + this.cntHT = address&31; + + }, + + regsToAddress: function(){ + var b1 = (this.regFV&7)<<4; + b1 |= (this.regV&1)<<3; + b1 |= (this.regH&1)<<2; + b1 |= (this.regVT>>3)&3; + + var b2 = (this.regVT&7)<<5; + b2 |= this.regHT&31; + + this.vramTmpAddress = ((b1<<8) | b2)&0x7FFF; + }, + + cntsToAddress: function(){ + var b1 = (this.cntFV&7)<<4; + b1 |= (this.cntV&1)<<3; + b1 |= (this.cntH&1)<<2; + b1 |= (this.cntVT>>3)&3; + + var b2 = (this.cntVT&7)<<5; + b2 |= this.cntHT&31; + + this.vramAddress = ((b1<<8) | b2)&0x7FFF; + }, + + incTileCounter: function(count){ + for(var i=count; i!=0; i--){ + this.cntHT++; + if(this.cntHT==32){ + this.cntHT=0; + this.cntVT++; + if(this.cntVT>=30){ + this.cntH++; + if(this.cntH==2){ + this.cntH=0; + this.cntV++; + if(this.cntV==2){ + this.cntV=0; + this.cntFV++; + this.cntFV&=0x7; + } + } + } + } + } + }, + + // Reads from memory, taking into account + // mirroring/mapping of address ranges. + mirroredLoad: function(address) { + return this.vramMem[this.vramMirrorTable[address]]; + }, + + // Writes to memory, taking into account + // mirroring/mapping of address ranges. + mirroredWrite: function(address, value){ + if(address>=0x3f00 && address<0x3f20){ + // Palette write mirroring. + if(address==0x3F00 || address==0x3F10){ + this.writeMem(0x3F00,value); + this.writeMem(0x3F10,value); + + }else if(address==0x3F04 || address==0x3F14){ + + this.writeMem(0x3F04,value); + this.writeMem(0x3F14,value); + + }else if(address==0x3F08 || address==0x3F18){ + + this.writeMem(0x3F08,value); + this.writeMem(0x3F18,value); + + }else if(address==0x3F0C || address==0x3F1C){ + + this.writeMem(0x3F0C,value); + this.writeMem(0x3F1C,value); + + }else{ + this.writeMem(address,value); + } + + }else{ + + // Use lookup table for mirrored address: + if(address<this.vramMirrorTable.length){ + this.writeMem(this.vramMirrorTable[address],value); + }else{ + // FIXME + alert("Invalid VRAM address: "+address.toString(16)); + } + + } + }, + + triggerRendering: function(){ + if(this.scanline >= 21 && this.scanline <= 260){ + // Render sprites, and combine: + this.renderFramePartially( + this.lastRenderedScanline+1, + this.scanline-21-this.lastRenderedScanline + ); + + // Set last rendered scanline: + this.lastRenderedScanline = this.scanline-21; + } + }, + + renderFramePartially: function(startScan, scanCount){ + if(this.f_spVisibility == 1){ + this.renderSpritesPartially(startScan,scanCount,true); + } + + if(this.f_bgVisibility == 1){ + var si = startScan<<8; + var ei = (startScan+scanCount)<<8; + if(ei>0xF000) ei=0xF000; + var buffer = this.buffer; + var bgbuffer = this.bgbuffer; + var pixrendered = this.pixrendered; + for(var destIndex=si;destIndex<ei;destIndex++){ + if(pixrendered[destIndex]>0xFF){ + buffer[destIndex] = bgbuffer[destIndex]; + } + } + } + + if(this.f_spVisibility == 1){ + this.renderSpritesPartially(startScan,scanCount,false); + } + + this.validTileData = false; + }, + + renderBgScanline: function(bgbuffer, scan){ + + var baseTile = (this.regS==0?0:256); + var destIndex = (scan<<8)-this.regFH; + + this.curNt = this.ntable1[this.cntV+this.cntV+this.cntH]; + + this.cntHT = this.regHT; + this.cntH = this.regH; + this.curNt = this.ntable1[this.cntV+this.cntV+this.cntH]; + + if(scan<240 && (scan-this.cntFV)>=0){ + + var tscanoffset = this.cntFV<<3; + var scantile = this.scantile; + var attrib = this.attrib; + var ptTile = this.ptTile; + var nameTable = this.nameTable; + var imgPalette = this.imgPalette; + var pixrendered = this.pixrendered; + var targetBuffer = bgbuffer ? this.bgbuffer : this.buffer; + + var t, tpix, att, col; + + for(var tile=0;tile<32;tile++){ + + if(scan>=0){ + + // Fetch tile & attrib data: + if(this.validTileData){ + // Get data from array: + t = scantile[tile]; + tpix = t.pix; + att = attrib[tile]; + }else{ + // Fetch data: + t = ptTile[baseTile+nameTable[this.curNt].getTileIndex(this.cntHT,this.cntVT)]; + tpix = t.pix; + att = nameTable[this.curNt].getAttrib(this.cntHT,this.cntVT); + scantile[tile] = t; + attrib[tile] = att; + } + + // Render tile scanline: + var sx = 0; + var x = (tile<<3)-this.regFH; + + if(x>-8){ + if(x<0){ + destIndex-=x; + sx = -x; + } + if(t.opaque[this.cntFV]){ + for(;sx<8;sx++){ + var pix = imgPalette[tpix[tscanoffset+sx]+att]; + targetBuffer[destIndex] = pix; + pixrendered[destIndex] |= 256; + destIndex++; + } + }else{ + for(;sx<8;sx++){ + col = tpix[tscanoffset+sx]; + if(col != 0){ + var pix = imgPalette[col+att]; + targetBuffer[destIndex] = pix; + pixrendered[destIndex] |= 256; + } + destIndex++; + } + } + } + + } + + // Increase Horizontal Tile Counter: + if(++this.cntHT==32){ + this.cntHT=0; + this.cntH++; + this.cntH%=2; + this.curNt = this.ntable1[(this.cntV<<1)+this.cntH]; + } + + + } + + // Tile data for one row should now have been fetched, + // so the data in the array is valid. + this.validTileData = true; + + } + + // update vertical scroll: + this.cntFV++; + if(this.cntFV==8){ + this.cntFV = 0; + this.cntVT++; + if(this.cntVT==30){ + this.cntVT = 0; + this.cntV++; + this.cntV%=2; + this.curNt = this.ntable1[(this.cntV<<1)+this.cntH]; + }else if(this.cntVT==32){ + this.cntVT = 0; + } + + // Invalidate fetched data: + this.validTileData = false; + + } + }, + + renderSpritesPartially: function(startscan, scancount, bgPri){ + if(this.f_spVisibility==1){ + + var sprT1,sprT2; + + for(var i=0;i<64;i++){ + if(this.bgPriority[i]==bgPri && this.sprX[i]>=0 && this.sprX[i]<256 && this.sprY[i]+8>=startscan && this.sprY[i]<startscan+scancount){ + // Show sprite. + if(this.f_spriteSize == 0){ + // 8x8 sprites + + this.srcy1 = 0; + this.srcy2 = 8; + + if(this.sprY[i]<startscan){ + this.srcy1 = startscan - this.sprY[i]-1; + } + + if(this.sprY[i]+8 > startscan+scancount){ + this.srcy2 = startscan+scancount-this.sprY[i]+1; + } + + if(this.f_spPatternTable==0){ + this.ptTile[this.sprTile[i]].render(this.buffer, + 0, this.srcy1, 8, this.srcy2, this.sprX[i], + this.sprY[i]+1, this.sprCol[i], this.sprPalette, + this.horiFlip[i], this.vertFlip[i], i, + this.pixrendered + ); + }else{ + this.ptTile[this.sprTile[i]+256].render(this.buffer, 0, this.srcy1, 8, this.srcy2, this.sprX[i], this.sprY[i]+1, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered); + } + }else{ + // 8x16 sprites + var top = this.sprTile[i]; + if((top&1)!=0){ + top = this.sprTile[i]-1+256; + } + + var srcy1 = 0; + var srcy2 = 8; + + if(this.sprY[i]<startscan){ + srcy1 = startscan - this.sprY[i]-1; + } + + if(this.sprY[i]+8 > startscan+scancount){ + srcy2 = startscan+scancount-this.sprY[i]; + } + + this.ptTile[top+(this.vertFlip[i]?1:0)].render( + this.buffer, + 0, + srcy1, + 8, + srcy2, + this.sprX[i], + this.sprY[i]+1, + this.sprCol[i], + this.sprPalette, + this.horiFlip[i], + this.vertFlip[i], + i, + this.pixrendered + ); + + var srcy1 = 0; + var srcy2 = 8; + + if(this.sprY[i]+8<startscan){ + srcy1 = startscan - (this.sprY[i]+8+1); + } + + if(this.sprY[i]+16 > startscan+scancount){ + srcy2 = startscan+scancount-(this.sprY[i]+8); + } + + this.ptTile[top+(this.vertFlip[i]?0:1)].render( + this.buffer, + 0, + srcy1, + 8, + srcy2, + this.sprX[i], + this.sprY[i]+1+8, + this.sprCol[i], + this.sprPalette, + this.horiFlip[i], + this.vertFlip[i], + i, + this.pixrendered + ); + + } + } + } + } + }, + + checkSprite0: function(scan){ + + this.spr0HitX = -1; + this.spr0HitY = -1; + + var toffset; + var tIndexAdd = (this.f_spPatternTable==0?0:256); + var x,y; + var bufferIndex; + var col; + var bgPri; + var t; + + x = this.sprX[0]; + y = this.sprY[0]+1; + + if(this.f_spriteSize==0){ + // 8x8 sprites. + + // Check range: + if(y<=scan && y+8>scan && x>=-7 && x<256){ + + // Sprite is in range. + // Draw scanline: + t = this.ptTile[this.sprTile[0]+tIndexAdd]; + col = this.sprCol[0]; + bgPri = this.bgPriority[0]; + + if(this.vertFlip[0]){ + toffset = 7-(scan-y); + }else{ + toffset = scan-y; + } + toffset*=8; + + bufferIndex = scan*256+x; + if(this.horiFlip[0]){ + for(var i=7;i>=0;i--){ + if(x>=0 && x<256){ + if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){ + if(t.pix[toffset+i] != 0){ + this.spr0HitX = bufferIndex%256; + this.spr0HitY = scan; + return true; + } + } + } + x++; + bufferIndex++; + } + + }else{ + for(var i=0;i<8;i++){ + if(x>=0 && x<256){ + if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){ + if(t.pix[toffset+i] != 0){ + this.spr0HitX = bufferIndex%256; + this.spr0HitY = scan; + return true; + } + } + } + x++; + bufferIndex++; + } + } + } + }else{ + // 8x16 sprites: + + // Check range: + if(y<=scan && y+16>scan && x>=-7 && x<256){ + + // Sprite is in range. + // Draw scanline: + + if(this.vertFlip[0]){ + toffset = 15-(scan-y); + }else{ + toffset = scan-y; + } + + if(toffset<8){ + // first half of sprite. + t = this.ptTile[this.sprTile[0]+(this.vertFlip[0]?1:0)+((this.sprTile[0]&1)!=0?255:0)]; + }else{ + // second half of sprite. + t = this.ptTile[this.sprTile[0]+(this.vertFlip[0]?0:1)+((this.sprTile[0]&1)!=0?255:0)]; + if(this.vertFlip[0]){ + toffset = 15-toffset; + }else{ + toffset -= 8; + } + } + toffset*=8; + col = this.sprCol[0]; + bgPri = this.bgPriority[0]; + + bufferIndex = scan*256+x; + if(this.horiFlip[0]){ + for(var i=7;i>=0;i--){ + if(x>=0 && x<256){ + if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){ + if(t.pix[toffset+i] != 0){ + this.spr0HitX = bufferIndex%256; + this.spr0HitY = scan; + return true; + } + } + } + x++; + bufferIndex++; + } + + }else{ + + for(var i=0;i<8;i++){ + if(x>=0 && x<256){ + if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){ + if(t.pix[toffset+i] != 0){ + this.spr0HitX = bufferIndex%256; + this.spr0HitY = scan; + return true; + } + } + } + x++; + bufferIndex++; + } + + } + + } + + } + + return false; + }, + + // This will write to PPU memory, and + // update internally buffered data + // appropriately. + writeMem: function(address, value){ + + this.vramMem[address] = value; + + // Update internally buffered data: + if(address < 0x2000){ + + this.vramMem[address] = value; + this.patternWrite(address,value); + + }else if(address >=0x2000 && address <0x23c0){ + + this.nameTableWrite(this.ntable1[0],address-0x2000,value); + + }else if(address >=0x23c0 && address <0x2400){ + + this.attribTableWrite(this.ntable1[0],address-0x23c0,value); + + }else if(address >=0x2400 && address <0x27c0){ + + this.nameTableWrite(this.ntable1[1],address-0x2400,value); + + }else if(address >=0x27c0 && address <0x2800){ + + this.attribTableWrite(this.ntable1[1],address-0x27c0,value); + + }else if(address >=0x2800 && address <0x2bc0){ + + this.nameTableWrite(this.ntable1[2],address-0x2800,value); + + }else if(address >=0x2bc0 && address <0x2c00){ + + this.attribTableWrite(this.ntable1[2],address-0x2bc0,value); + + }else if(address >=0x2c00 && address <0x2fc0){ + + this.nameTableWrite(this.ntable1[3],address-0x2c00,value); + + }else if(address >=0x2fc0 && address <0x3000){ + + this.attribTableWrite(this.ntable1[3],address-0x2fc0,value); + + }else if(address >=0x3f00 && address <0x3f20){ + + this.updatePalettes(); + + } + }, + + // Reads data from $3f00 to $f20 + // into the two buffered palettes. + updatePalettes: function(){ + + for(var i=0;i<16;i++){ + if (this.f_dispType == 0) { + this.imgPalette[i] = this.palTable.getEntry( + this.vramMem[0x3f00+i] & 63 + ); + } + else { + this.imgPalette[i] = this.palTable.getEntry( + this.vramMem[0x3f00+i] & 32 + ); + } + } + for(var i=0;i<16;i++){ + if (this.f_dispType == 0) { + this.sprPalette[i] = this.palTable.getEntry( + this.vramMem[0x3f10+i] & 63 + ); + } + else { + this.sprPalette[i] = this.palTable.getEntry( + this.vramMem[0x3f10+i] & 32 + ); + } + } + }, + + // Updates the internal pattern + // table buffers with this new byte. + // In vNES, there is a version of this with 4 arguments which isn't used. + patternWrite: function(address, value){ + var tileIndex = parseInt(address/16); + var leftOver = address%16; + if (leftOver<8) { + this.ptTile[tileIndex].setScanline( + leftOver, + value, + this.vramMem[address+8] + ); + } + else { + this.ptTile[tileIndex].setScanline( + leftOver-8, + this.vramMem[address-8], + value + ); + } + }, + + // Updates the internal name table buffers + // with this new byte. + nameTableWrite: function(index, address, value){ + this.nameTable[index].tile[address] = value; + + // Update Sprite #0 hit: + //updateSpr0Hit(); + this.checkSprite0(this.scanline-20); + }, + + // Updates the internal pattern + // table buffers with this new attribute + // table byte. + attribTableWrite: function(index, address, value){ + this.nameTable[index].writeAttrib(address,value); + }, + + // Updates the internally buffered sprite + // data with this new byte of info. + spriteRamWriteUpdate: function(address, value){ + var tIndex = parseInt(address/4); + + if(tIndex == 0){ + //updateSpr0Hit(); + this.checkSprite0(this.scanline-20); + } + + if(address%4 == 0){ + // Y coordinate + this.sprY[tIndex] = value; + }else if(address%4 == 1){ + // Tile index + this.sprTile[tIndex] = value; + }else if(address%4 == 2){ + // Attributes + this.vertFlip[tIndex] = ((value&0x80)!=0); + this.horiFlip[tIndex] = ((value&0x40)!=0); + this.bgPriority[tIndex] = ((value&0x20)!=0); + this.sprCol[tIndex] = (value&3)<<2; + + }else if(address%4 == 3){ + // X coordinate + this.sprX[tIndex] = value; + } + }, + + doNMI: function(){ + // Set VBlank flag: + this.setStatusFlag(this.STATUS_VBLANK,true); + //nes.getCpu().doNonMaskableInterrupt(); + this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI); + } +} + +NES.PPU.NameTable = function(width, height, name) { + this.width = width; + this.height = height; + this.name = name; + + this.tile = new Array(width*height); + this.attrib = new Array(width*height); +} + +NES.PPU.NameTable.prototype = { + getTileIndex: function(x, y){ + return this.tile[y*this.width+x]; + }, + + getAttrib: function(x, y){ + return this.attrib[y*this.width+x]; + }, + + writeAttrib: function(index, value){ + var basex = (index % 8) * 4; + var basey = parseInt(index / 8) * 4; + var add; + var tx, ty; + var attindex; + + for(var sqy=0;sqy<2;sqy++){ + for(var sqx=0;sqx<2;sqx++){ + add = (value>>(2*(sqy*2+sqx)))&3; + for(var y=0;y<2;y++){ + for(var x=0;x<2;x++){ + tx = basex+sqx*2+x; + ty = basey+sqy*2+y; + attindex = ty*this.width+tx; + this.attrib[ty*this.width+tx] = (add<<2)&12; + } + } + } + } + } +} + + +NES.PPU.PaletteTable = function() { + this.curTable = new Array(64); + this.emphTable = new Array(8); + this.currentEmph = -1; +} + +NES.PPU.PaletteTable.prototype = { + reset: function() { + this.setEmphasis(0); + }, + + loadNTSCPalette: function() { + this.curTable = [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000]; + this.makeTables(); + this.setEmphasis(0); + }, + + loadPALPalette: function() { + this.curTable = [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000]; + this.makeTables(); + this.setEmphasis(0); + }, + + makeTables: function(){ + var r,g,b,col; + + // Calculate a table for each possible emphasis setting: + for(var emph=0;emph<8;emph++){ + + // Determine color component factors: + var rFactor=1.0, gFactor=1.0, bFactor=1.0; + if((emph&1)!=0){ + rFactor = 0.75; + bFactor = 0.75; + } + if((emph&2)!=0){ + rFactor = 0.75; + gFactor = 0.75; + } + if((emph&4)!=0){ + gFactor = 0.75; + bFactor = 0.75; + } + + this.emphTable[emph] = new Array(64); + + // Calculate table: + for(var i=0;i<64;i++){ + col = this.curTable[i]; + r = parseInt(this.getRed(col) * rFactor); + g = parseInt(this.getGreen(col) * gFactor); + b = parseInt(this.getBlue(col) * bFactor); + this.emphTable[emph][i] = this.getRgb(r,g,b); + } + } + }, + + setEmphasis: function(emph){ + if(emph != this.currentEmph){ + this.currentEmph = emph; + for(var i=0;i<64;i++){ + this.curTable[i] = this.emphTable[emph][i]; + } + } + }, + + getEntry: function(yiq){ + return this.curTable[yiq]; + }, + + getRed: function(rgb){ + return (rgb>>16)&0xFF; + }, + + getGreen: function(rgb){ + return (rgb>>8)&0xFF; + }, + + getBlue: function(rgb){ + return rgb&0xFF; + }, + + getRgb: function(r, g, b){ + return ((r<<16)|(g<<8)|(b)); + }, + + loadDefaultPalette: function(){ + this.curTable[ 0] = this.getRgb(117,117,117); + this.curTable[ 1] = this.getRgb( 39, 27,143); + this.curTable[ 2] = this.getRgb( 0, 0,171); + this.curTable[ 3] = this.getRgb( 71, 0,159); + this.curTable[ 4] = this.getRgb(143, 0,119); + this.curTable[ 5] = this.getRgb(171, 0, 19); + this.curTable[ 6] = this.getRgb(167, 0, 0); + this.curTable[ 7] = this.getRgb(127, 11, 0); + this.curTable[ 8] = this.getRgb( 67, 47, 0); + this.curTable[ 9] = this.getRgb( 0, 71, 0); + this.curTable[10] = this.getRgb( 0, 81, 0); + this.curTable[11] = this.getRgb( 0, 63, 23); + this.curTable[12] = this.getRgb( 27, 63, 95); + this.curTable[13] = this.getRgb( 0, 0, 0); + this.curTable[14] = this.getRgb( 0, 0, 0); + this.curTable[15] = this.getRgb( 0, 0, 0); + this.curTable[16] = this.getRgb(188,188,188); + this.curTable[17] = this.getRgb( 0,115,239); + this.curTable[18] = this.getRgb( 35, 59,239); + this.curTable[19] = this.getRgb(131, 0,243); + this.curTable[20] = this.getRgb(191, 0,191); + this.curTable[21] = this.getRgb(231, 0, 91); + this.curTable[22] = this.getRgb(219, 43, 0); + this.curTable[23] = this.getRgb(203, 79, 15); + this.curTable[24] = this.getRgb(139,115, 0); + this.curTable[25] = this.getRgb( 0,151, 0); + this.curTable[26] = this.getRgb( 0,171, 0); + this.curTable[27] = this.getRgb( 0,147, 59); + this.curTable[28] = this.getRgb( 0,131,139); + this.curTable[29] = this.getRgb( 0, 0, 0); + this.curTable[30] = this.getRgb( 0, 0, 0); + this.curTable[31] = this.getRgb( 0, 0, 0); + this.curTable[32] = this.getRgb(255,255,255); + this.curTable[33] = this.getRgb( 63,191,255); + this.curTable[34] = this.getRgb( 95,151,255); + this.curTable[35] = this.getRgb(167,139,253); + this.curTable[36] = this.getRgb(247,123,255); + this.curTable[37] = this.getRgb(255,119,183); + this.curTable[38] = this.getRgb(255,119, 99); + this.curTable[39] = this.getRgb(255,155, 59); + this.curTable[40] = this.getRgb(243,191, 63); + this.curTable[41] = this.getRgb(131,211, 19); + this.curTable[42] = this.getRgb( 79,223, 75); + this.curTable[43] = this.getRgb( 88,248,152); + this.curTable[44] = this.getRgb( 0,235,219); + this.curTable[45] = this.getRgb( 0, 0, 0); + this.curTable[46] = this.getRgb( 0, 0, 0); + this.curTable[47] = this.getRgb( 0, 0, 0); + this.curTable[48] = this.getRgb(255,255,255); + this.curTable[49] = this.getRgb(171,231,255); + this.curTable[50] = this.getRgb(199,215,255); + this.curTable[51] = this.getRgb(215,203,255); + this.curTable[52] = this.getRgb(255,199,255); + this.curTable[53] = this.getRgb(255,199,219); + this.curTable[54] = this.getRgb(255,191,179); + this.curTable[55] = this.getRgb(255,219,171); + this.curTable[56] = this.getRgb(255,231,163); + this.curTable[57] = this.getRgb(227,255,163); + this.curTable[58] = this.getRgb(171,243,191); + this.curTable[59] = this.getRgb(179,255,207); + this.curTable[60] = this.getRgb(159,255,243); + this.curTable[61] = this.getRgb( 0, 0, 0); + this.curTable[62] = this.getRgb( 0, 0, 0); + this.curTable[63] = this.getRgb( 0, 0, 0); + + this.makeTables(); + this.setEmphasis(0); + } +} + +NES.PPU.Tile = function() { + // Tile data: + this.pix = new Array(64); + + this.fbIndex = null; + this.tIndex = null; + this.x = null; + this.y = null; + this.w = null; + this.h = null; + this.incX = null; + this.incY = null; + this.palIndex = null; + this.tpri = null; + this.c = null; + this.initialized = false; + this.opaque = new Array(8); +} + +NES.PPU.Tile.prototype = { + setBuffer: function(scanline){ + for(this.y=0;this.y<8;this.y++){ + this.setScanline(this.y,scanline[this.y],scanline[this.y+8]); + } + }, + + setScanline: function(sline, b1, b2){ + this.initialized = true; + this.tIndex = sline<<3; + for(this.x=0;this.x<8;this.x++){ + this.pix[this.tIndex+this.x] = ((b1>>(7-this.x))&1) + + (((b2>>(7-this.x))&1)<<1); + if(this.pix[this.tIndex+this.x]==0) this.opaque[sline]=false; + } + }, + + render: function(buffer, srcx1, srcy1, srcx2, srcy2, dx, dy, palAdd, palette, flipHorizontal, flipVertical, pri, priTable) { + + if(dx<-7 || dx>=256 || dy<-7 || dy>=240){ + return; + } + + this.w=srcx2-srcx1; + this.h=srcy2-srcy1; + + if(dx<0){ + srcx1-=dx; + } + if(dx+srcx2>=256){ + srcx2=256-dx; + } + + if(dy<0){ + srcy1-=dy; + } + if(dy+srcy2>=240){ + srcy2=240-dy; + } + + if(!flipHorizontal && !flipVertical){ + + this.fbIndex = (dy<<8)+dx; + this.tIndex = 0; + for(this.y=0;this.y<8;this.y++){ + for(this.x=0;this.x<8;this.x++){ + if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){ + this.palIndex = this.pix[this.tIndex]; + this.tpri = priTable[this.fbIndex]; + if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){ + //console.log("Rendering upright tile to buffer"); + buffer[this.fbIndex] = palette[this.palIndex+palAdd]; + this.tpri = (this.tpri&0xF00)|pri; + priTable[this.fbIndex] =this.tpri; + } + } + this.fbIndex++; + this.tIndex++; + } + this.fbIndex-=8; + this.fbIndex+=256; + } + + }else if(flipHorizontal && !flipVertical){ + + this.fbIndex = (dy<<8)+dx; + this.tIndex = 7; + for(this.y=0;this.y<8;this.y++){ + for(this.x=0;this.x<8;this.x++){ + if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){ + this.palIndex = this.pix[this.tIndex]; + this.tpri = priTable[this.fbIndex]; + if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){ + buffer[this.fbIndex] = palette[this.palIndex+palAdd]; + this.tpri = (this.tpri&0xF00)|pri; + priTable[this.fbIndex] =this.tpri; + } + } + this.fbIndex++; + this.tIndex--; + } + this.fbIndex-=8; + this.fbIndex+=256; + this.tIndex+=16; + } + + }else if(flipVertical && !flipHorizontal){ + + this.fbIndex = (dy<<8)+dx; + this.tIndex = 56; + for(this.y=0;this.y<8;this.y++){ + for(this.x=0;this.x<8;this.x++){ + if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){ + this.palIndex = this.pix[this.tIndex]; + this.tpri = priTable[this.fbIndex]; + if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){ + buffer[this.fbIndex] = palette[this.palIndex+palAdd]; + this.tpri = (this.tpri&0xF00)|pri; + priTable[this.fbIndex] =this.tpri; + } + } + this.fbIndex++; + this.tIndex++; + } + this.fbIndex-=8; + this.fbIndex+=256; + this.tIndex-=16; + } + + }else{ + + this.fbIndex = (dy<<8)+dx; + this.tIndex = 63; + for(this.y=0;this.y<8;this.y++){ + for(this.x=0;this.x<8;this.x++){ + if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){ + this.palIndex = this.pix[this.tIndex]; + this.tpri = priTable[this.fbIndex]; + if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){ + buffer[this.fbIndex] = palette[this.palIndex+palAdd]; + this.tpri = (this.tpri&0xF00)|pri; + priTable[this.fbIndex] =this.tpri; + } + } + this.fbIndex++; + this.tIndex--; + } + this.fbIndex-=8; + this.fbIndex+=256; + } + + } + + }, + + isTransparent: function(x, y){ + return (this.pix[(y<<3)+x]==0); + } +} + |