/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 1998 BERO * Copyright (C) 2003 Xodnizel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "types.h" #include "x6502.h" #include "fceu.h" #include "ppu.h" #include "nsf.h" #include "sound.h" #include "file.h" #include "utils/endian.h" #include "utils/memory.h" #include "cart.h" #include "palette.h" #include "state.h" #include "video.h" #include "input.h" #include "driver.h" #define VBlankON (PPU[0]&0x80) //Generate VBlank NMI #define Sprite16 (PPU[0]&0x20) //Sprites 8x16/8x8 #define BGAdrHI (PPU[0]&0x10) //BG pattern adr $0000/$1000 #define SpAdrHI (PPU[0]&0x08) //Sprite pattern adr $0000/$1000 #define INC32 (PPU[0]&0x04) //auto increment 1/32 #define SpriteON (PPU[1]&0x10) //Show Sprite #define ScreenON (PPU[1]&0x08) //Show screen #define PPUON (PPU[1]&0x18) //PPU should operate #define GRAYSCALE (PPU[1]&0x01) //Grayscale (AND palette entries with 0x30) #define SpriteLeft8 (PPU[1]&0x04) #define BGLeft8 (PPU[1]&0x02) #define PPU_status (PPU[2]) #define Pal (PALRAM) static void FetchSpriteData(void); static void RefreshLine(int lastpixel); static void RefreshSprites(void); static void CopySprites(uint8 *target); static void Fixit1(void); static uint32 ppulut1[256]; static uint32 ppulut2[256]; static uint32 ppulut3[128]; int test = 0; template struct BITREVLUT { T* lut; BITREVLUT() { int bits = BITS; int n = 1<>1; int j = 2; lut[0] = 0; lut[1] = a; while(--bits) { m <<= 1; a >>= 1; for(int i=0;i bitrevlut; struct PPUSTATUS { int32 sl; int32 cycle, end_cycle; }; struct SPRITE_READ { int32 num; int32 count; int32 fetch; int32 found; int32 found_pos[8]; int32 ret; int32 last; int32 mode; void reset() { num = count = fetch = found = ret = last = mode = 0; found_pos[0] = found_pos[1] = found_pos[2] = found_pos[3] = 0; found_pos[4] = found_pos[5] = found_pos[6] = found_pos[7] = 0; } void start_scanline() { num = 1; found = 0; fetch = 1; count = 0; last = 64; mode = 0; found_pos[0] = found_pos[1] = found_pos[2] = found_pos[3] = 0; found_pos[4] = found_pos[5] = found_pos[6] = found_pos[7] = 0; } }; //doesn't need to be savestated as it is just a reflection of the current position in the ppu loop PPUPHASE ppuphase; //this needs to be savestated since a game may be trying to read from this across vblanks SPRITE_READ spr_read; //definitely needs to be savestated uint8 idleSynch = 1; //uses the internal counters concept at http://nesdev.icequake.net/PPU%20addressing.txt struct PPUREGS { //normal clocked regs. as the game can interfere with these at any time, they need to be savestated uint32 fv;//3 uint32 v;//1 uint32 h;//1 uint32 vt;//5 uint32 ht;//5 //temp unlatched regs (need savestating, can be written to at any time) uint32 _fv, _v, _h, _vt, _ht; //other regs that need savestating uint32 fh;//3 (horz scroll) uint32 s;//1 ($2000 bit 4: "Background pattern table address (0: $0000; 1: $1000)") //other regs that don't need saving uint32 par;//8 (sort of a hack, just stored in here, but not managed by this system) //cached state data. these are always reset at the beginning of a frame and don't need saving //but just to be safe, we're gonna save it PPUSTATUS status; void reset() { fv = v = h = vt = ht = 0; fh = par = s = 0; _fv = _v = _h = _vt = _ht = 0; status.cycle = 0; status.end_cycle = 341; status.sl = 241; } void install_latches() { fv = _fv; v = _v; h = _h; vt = _vt; ht = _ht; } void install_h_latches() { ht = _ht; h = _h; } void clear_latches() { _fv = _v = _h = _vt = _ht = 0; fh = 0; } void increment_hsc() { //The first one, the horizontal scroll counter, consists of 6 bits, and is //made up by daisy-chaining the HT counter to the H counter. The HT counter is //then clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles). ht++; h += (ht>>5); ht &= 31; h &= 1; } void increment_vs() { fv++; vt += (fv>>3); v += (vt==30)?1:0; fv &= 7; if(vt==30) vt=0; v &= 1; } uint32 get_ntread() { return 0x2000 | (v<<0xB) | (h<<0xA) | (vt<<5) | ht; } uint32 get_2007access() { return ((fv&3)<<0xC) | (v<<0xB) | (h<<0xA) | (vt<<5) | ht; } //The PPU has an internal 4-position, 2-bit shifter, which it uses for //obtaining the 2-bit palette select data during an attribute table byte //fetch. To represent how this data is shifted in the diagram, letters a..c //are used in the diagram to represent the right-shift position amount to //apply to the data read from the attribute data (a is always 0). This is why //you only see bits 0 and 1 used off the read attribute data in the diagram. uint32 get_atread() { return 0x2000 | (v<<0xB) | (h<<0xA) | 0x3C0 | ((vt&0x1C)<<1) | ((ht&0x1C)>>2); } //address line 3 relates to the pattern table fetch occuring (the PPU always makes them in pairs). uint32 get_ptread() { return (s<<0xC) | (par<<0x4) | fv; } void increment2007(bool by32) { //If the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the //scroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that //the carry out of each counter controls the next counter's clock rate. The //result is that all 5 counters function as a single 15-bit one. Any access to //2007 clocks the HT counter here. // //If the VRAM address increment bit is set (inc. amt. = 32), the only //difference is that the HT counter is no longer being clocked, and the VT //counter is now being clocked by access to 2007. if(by32) { vt++; } else { ht++; vt+=(ht>>5)&1; } h+=(vt>>5); v+=(h>>1); fv+=(v>>1); ht &= 31; vt &= 31; h &= 1; v &= 1; fv &= 7; } } ppur; static void makeppulut(void) { int x; int y; int cc,xo,pixel; for(x=0;x<256;x++) { ppulut1[x] = 0; for(y=0;y<8;y++) { ppulut1[x] |= ((x>>(7-y))&1)<<(y*4); } ppulut2[x] = ppulut1[x] << 1; } for(cc=0;cc<16;cc++) { for(xo=0;xo<8;xo++) { ppulut3[ xo | ( cc << 3 ) ] = 0; for(pixel=0;pixel<8;pixel++) { int shiftr; shiftr = ( pixel + xo ) / 8; shiftr *= 2; ppulut3[ xo | (cc<<3) ] |= ( ( cc >> shiftr ) & 3 ) << ( 2 + pixel * 4 ); } // printf("%08x\n",ppulut3[xo|(cc<<3)]); } } } static int ppudead=1; static int kook=0; int fceuindbg=0; //mbg 6/23/08 //make the no-bg fill color configurable //0xFF shall indicate to use palette[0] uint8 gNoBGFillColor = 0xFF; int MMC5Hack=0; uint32 MMC5HackVROMMask=0; uint8 *MMC5HackExNTARAMPtr=0; uint8 *MMC5HackVROMPTR=0; uint8 MMC5HackCHRMode=0; uint8 MMC5HackSPMode=0; uint8 MMC50x5130=0; uint8 MMC5HackSPScroll=0; uint8 MMC5HackSPPage=0; uint8 VRAMBuffer=0,PPUGenLatch=0; uint8 *vnapage[4]; uint8 PPUNTARAM=0; uint8 PPUCHRRAM=0; //Color deemphasis emulation. Joy... static uint8 deemp=0; static int deempcnt[8]; void (*GameHBIRQHook)(void), (*GameHBIRQHook2)(void); void (*PPU_hook)(uint32 A); uint8 vtoggle=0; uint8 XOffset=0; uint32 TempAddr=0,RefreshAddr=0; static int maxsprites=8; //scanline is equal to the current visible scanline we're on. int scanline; static uint32 scanlines_per_frame; uint8 PPU[4]; uint8 PPUSPL; uint8 NTARAM[0x800],PALRAM[0x20],SPRAM[0x100],SPRBUF[0x100]; uint8 UPALRAM[0x03]; //for 0x4/0x8/0xC addresses in palette, the ones in //0x20 are 0 to not break fceu rendering. #define MMC5SPRVRAMADR(V) &MMC5SPRVPage[(V)>>10][(V)] #define VRAMADR(V) &VPage[(V)>>10][(V)] //mbg 8/6/08 - fix a bug relating to //"When in 8x8 sprite mode, only one set is used for both BG and sprites." //in mmc5 docs uint8 * MMC5BGVRAMADR(uint32 V) { if(!Sprite16) { extern uint8 mmc5ABMode; /* A=0, B=1 */ if(mmc5ABMode==0) return MMC5SPRVRAMADR(V); else return &MMC5BGVPage[(V)>>10][(V)]; } else return &MMC5BGVPage[(V)>>10][(V)]; } //this duplicates logic which is embedded in the ppu rendering code //which figures out where to get CHR data from depending on various hack modes //mostly involving mmc5. //this might be incomplete. uint8* FCEUPPU_GetCHR(uint32 vadr, uint32 refreshaddr) { if(MMC5Hack) { if(MMC5HackCHRMode==1) { uint8 *C = MMC5HackVROMPTR; C += (((MMC5HackExNTARAMPtr[refreshaddr & 0x3ff]) & 0x3f & MMC5HackVROMMask) << 12) + (vadr & 0xfff); C += (MMC50x5130&0x3)<<18; //11-jun-2009 for kuja_killer return C; } else { return MMC5BGVRAMADR(vadr); } } else return VRAMADR(vadr); } //likewise for ATTR int FCEUPPU_GetAttr(int ntnum, int xt, int yt) { int attraddr = 0x3C0+((yt>>2)<<3)+(xt>>2); int temp = (((yt&2)<<1)+(xt&2)); int refreshaddr = xt+yt*32; if(MMC5Hack && MMC5HackCHRMode==1) return (MMC5HackExNTARAMPtr[refreshaddr & 0x3ff] & 0xC0)>>6; else return (vnapage[ntnum][attraddr] & (3<> temp; } //new ppu----- inline void FFCEUX_PPUWrite_Default(uint32 A, uint8 V) { uint32 tmp = A; if(PPU_hook) PPU_hook(A); if(tmp<0x2000) { if(PPUCHRRAM&(1<<(tmp>>10))) VPage[tmp>>10][tmp]=V; } else if (tmp<0x3F00) { if(PPUNTARAM&(1<<((tmp&0xF00)>>10))) vnapage[((tmp&0xF00)>>10)][tmp&0x3FF]=V; } else { if (!(tmp & 3)) { if (!(tmp & 0xC)) PALRAM[0x00] = PALRAM[0x04] = PALRAM[0x08] = PALRAM[0x0C] = V & 0x3F; else UPALRAM[((tmp & 0xC) >> 2) - 1] = V & 0x3F; } else PALRAM[tmp & 0x1F] = V & 0x3F; } } uint8 FASTCALL FFCEUX_PPURead_Default(uint32 A) { uint32 tmp = A; if(PPU_hook) PPU_hook(A); if(tmp<0x2000) { return VPage[tmp>>10][tmp]; } else if (tmp < 0x3F00) { return vnapage[(tmp>>10)&0x3][tmp&0x3FF]; } else { uint8 ret; if (!(tmp & 3)) { if (!(tmp & 0xC)) ret = PALRAM[0x00]; else ret = UPALRAM[((tmp & 0xC) >> 2) - 1]; } else ret = PALRAM[tmp & 0x1F]; if (GRAYSCALE) ret &= 0x30; return ret; } } uint8 (FASTCALL *FFCEUX_PPURead)(uint32 A) = 0; void (*FFCEUX_PPUWrite)(uint32 A, uint8 V) = 0; #define CALL_PPUREAD(A) (FFCEUX_PPURead(A)) #define CALL_PPUWRITE(A,V) (FFCEUX_PPUWrite?FFCEUX_PPUWrite(A,V):FFCEUX_PPUWrite_Default(A,V)) //whether to use the new ppu (new PPU doesn't handle MMC5 extra nametables at all int newppu = 0; //--------------- static DECLFR(A2002) { if(newppu) { //once we thought we clear latches here, but that caused midframe glitches. //i think we should only reset the state machine for 2005/2006 //ppur.clear_latches(); } uint8 ret; FCEUPPU_LineUpdate(); ret = PPU_status; ret|=PPUGenLatch&0x1F; #ifdef FCEUDEF_DEBUGGER if(!fceuindbg) #endif { vtoggle=0; PPU_status&=0x7F; PPUGenLatch=ret; } return ret; } static DECLFR(A2004) { if (newppu) { if ((ppur.status.sl < 241) && PPUON) { /* from cycles 0 to 63, the * 32 byte OAM buffer gets init * to 0xFF */ if (ppur.status.cycle < 64) return spr_read.ret = 0xFF; else { for (int i = spr_read.last; i != ppur.status.cycle; ++i) { if (i < 256) { switch (spr_read.mode) { case 0: if (spr_read.count < 2) spr_read.ret = (PPU[3] & 0xF8) + (spr_read.count << 2); else spr_read.ret = spr_read.count << 2; spr_read.found_pos[spr_read.found] = spr_read.ret; spr_read.ret = SPRAM[spr_read.ret]; if (i & 1) //odd cycle { //see if in range if ( !((ppur.status.sl - 1 - spr_read.ret) & ~(Sprite16 ? 0xF : 0x7)) ) { ++spr_read.found; spr_read.fetch = 1; spr_read.mode = 1; } else { if (++spr_read.count == 64) { spr_read.mode = 4; spr_read.count = 0; } else if (spr_read.found == 8) { spr_read.fetch = 0; spr_read.mode = 2; } } } break; case 1: //sprite is in range fetch next 3 bytes if (i & 1) { ++spr_read.fetch; if (spr_read.fetch == 4) { spr_read.fetch = 1; if (++spr_read.count == 64) { spr_read.count = 0; spr_read.mode = 4; } else if (spr_read.found == 8) { spr_read.fetch = 0; spr_read.mode = 2; } else spr_read.mode = 0; } } if (spr_read.count < 2) spr_read.ret = (PPU[3] & 0xF8) + (spr_read.count << 2); else spr_read.ret = spr_read.count << 2; spr_read.ret = SPRAM[spr_read.ret | spr_read.fetch]; break; case 2: //8th sprite fetched spr_read.ret = SPRAM[(spr_read.count << 2) | spr_read.fetch]; if (i & 1) { if ( !((ppur.status.sl - 1 - SPRAM[((spr_read.count << 2) | spr_read.fetch)]) & ~((Sprite16) ? 0xF : 0x7)) ) { spr_read.fetch = 1; spr_read.mode = 3; } else { if (++spr_read.count == 64) { spr_read.count = 0; spr_read.mode = 4; } spr_read.fetch = (spr_read.fetch + 1) & 3; } } spr_read.ret = spr_read.count; break; case 3: //9th sprite overflow detected spr_read.ret = SPRAM[spr_read.count | spr_read.fetch]; if (i & 1) { if (++spr_read.fetch == 4) { spr_read.count = (spr_read.count + 1) & 63; spr_read.mode = 4; } } break; case 4: //read OAM[n][0] until hblank if (i & 1) spr_read.count = (spr_read.count + 1) & 63; spr_read.fetch = 0; spr_read.ret = SPRAM[spr_read.count << 2]; break; } } else if (i < 320) { spr_read.ret = (i & 0x38) >> 3; if (spr_read.found < (spr_read.ret + 1)) { if (spr_read.num) { spr_read.ret = SPRAM[252]; spr_read.num = 0; } else spr_read.ret = 0xFF; } else if ((i & 7) < 4) { spr_read.ret = SPRAM[spr_read.found_pos[spr_read.ret] | spr_read.fetch++]; if (spr_read.fetch == 4) spr_read.fetch = 0; } else spr_read.ret = SPRAM[spr_read.found_pos [spr_read.ret | 3]]; } else { if (!spr_read.found) spr_read.ret = SPRAM[252]; else spr_read.ret = SPRAM[spr_read.found_pos[0]]; break; } } spr_read.last = ppur.status.cycle; return spr_read.ret; } } else return SPRAM[PPU[3]]; } else { FCEUPPU_LineUpdate(); return PPUGenLatch; } } static DECLFR(A200x) /* Not correct for $2004 reads. */ { FCEUPPU_LineUpdate(); return PPUGenLatch; } /* static DECLFR(A2004) { uint8 ret; FCEUPPU_LineUpdate(); ret = SPRAM[PPU[3]]; if(PPUSPL>=8) { if(PPU[3]>=8) ret = SPRAM[PPU[3]]; } else { //printf("$%02x:$%02x\n",PPUSPL,V); ret = SPRAM[PPUSPL]; } PPU[3]++; PPUSPL++; PPUGenLatch = ret; printf("%d, %02x\n",scanline,ret); return(ret); } */ static DECLFR(A2007) { uint8 ret; uint32 tmp=RefreshAddr&0x3FFF; if(newppu) { ret = VRAMBuffer; RefreshAddr = ppur.get_2007access() & 0x3FFF; if ((RefreshAddr & 0x3F00) == 0x3F00) { //if it is in the palette range bypass the //delayed read, and what gets filled in the temp //buffer is the address - 0x1000, also //if grayscale is set then the return is AND with 0x30 //to get a gray color reading if (!(tmp & 3)) { if (!(tmp & 0xC)) ret = PALRAM[0x00]; else ret = UPALRAM[((tmp & 0xC) >> 2) - 1]; } else ret = PALRAM[tmp & 0x1F]; if (GRAYSCALE) ret &= 0x30; VRAMBuffer = CALL_PPUREAD(RefreshAddr - 0x1000); } else VRAMBuffer = CALL_PPUREAD(RefreshAddr); ppur.increment2007(INC32!=0); RefreshAddr = ppur.get_2007access(); return ret; } else { FCEUPPU_LineUpdate(); ret=VRAMBuffer; #ifdef FCEUDEF_DEBUGGER if(!fceuindbg) #endif { if(PPU_hook) PPU_hook(tmp); PPUGenLatch=VRAMBuffer; if(tmp<0x2000) { VRAMBuffer=VPage[tmp>>10][tmp]; } else if (tmp < 0x3F00) { VRAMBuffer=vnapage[(tmp>>10)&0x3][tmp&0x3FF]; } } #ifdef FCEUDEF_DEBUGGER if(!fceuindbg) #endif { if(INC32) RefreshAddr+=32; else RefreshAddr++; if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); } return ret; } } static DECLFW(B2000) { // FCEU_printf("%04x:%02x, (%d) %02x, %02x\n",A,V,scanline,PPU[0],PPU_status); FCEUPPU_LineUpdate(); PPUGenLatch=V; if(!(PPU[0]&0x80) && (V&0x80) && (PPU_status&0x80)) { // FCEU_printf("Trigger NMI, %d, %d\n",timestamp,ppudead); TriggerNMI2(); } PPU[0]=V; TempAddr&=0xF3FF; TempAddr|=(V&3)<<10; ppur._h = V&1; ppur._v = (V>>1)&1; ppur.s = (V>>4)&1; } static DECLFW(B2001) { //printf("%04x:$%02x, %d\n",A,V,scanline); FCEUPPU_LineUpdate(); PPUGenLatch=V; PPU[1]=V; if(V&0xE0) deemp=V>>5; } // static DECLFW(B2002) { PPUGenLatch=V; } static DECLFW(B2003) { //printf("$%04x:$%02x, %d, %d\n",A,V,timestamp,scanline); PPUGenLatch=V; PPU[3]=V; PPUSPL=V&0x7; } static DECLFW(B2004) { //printf("Wr: %04x:$%02x\n",A,V); PPUGenLatch=V; if (newppu) { //the attribute upper bits are not connected //so AND them out on write, since reading them //should return 0 in those bits. if ((PPU[3] & 3) == 2) V &= 0xE3; SPRAM[PPU[3]] = V; PPU[3] = (PPU[3] + 1) & 0xFF; } else { if(PPUSPL>=8) { if(PPU[3]>=8) SPRAM[PPU[3]]=V; } else { //printf("$%02x:$%02x\n",PPUSPL,V); SPRAM[PPUSPL]=V; } PPU[3]++; PPUSPL++; } } static DECLFW(B2005) { uint32 tmp=TempAddr; FCEUPPU_LineUpdate(); PPUGenLatch=V; if(!vtoggle) { tmp&=0xFFE0; tmp|=V>>3; XOffset=V&7; ppur._ht = V>>3; ppur.fh = V&7; } else { tmp&=0x8C1F; tmp|=((V&~0x7)<<2); tmp|=(V&7)<<12; ppur._vt = V>>3; ppur._fv = V&7; } TempAddr=tmp; vtoggle^=1; } static DECLFW(B2006) { if(!newppu) FCEUPPU_LineUpdate(); PPUGenLatch=V; if(!vtoggle) { TempAddr&=0x00FF; TempAddr|=(V&0x3f)<<8; ppur._vt &= 0x07; ppur._vt |= (V&0x3)<<3; ppur._h = (V>>2)&1; ppur._v = (V>>3)&1; ppur._fv = (V>>4)&3; } else { TempAddr&=0xFF00; TempAddr|=V; RefreshAddr=TempAddr; if(PPU_hook) PPU_hook(RefreshAddr); //printf("%d, %04x\n",scanline,RefreshAddr); ppur._vt &= 0x18; ppur._vt |= (V>>5); ppur._ht = V&31; ppur.install_latches(); } vtoggle^=1; } static DECLFW(B2007) { uint32 tmp=RefreshAddr&0x3FFF; if(newppu) { RefreshAddr = ppur.get_2007access() & 0x3FFF; CALL_PPUWRITE(RefreshAddr,V); //printf("%04x ",RefreshAddr); ppur.increment2007(INC32!=0); RefreshAddr = ppur.get_2007access(); } else { //printf("%04x ",tmp); PPUGenLatch=V; if(tmp>=0x3F00) { // hmmm.... if(!(tmp&0xf)) PALRAM[0x00]=PALRAM[0x04]=PALRAM[0x08]=PALRAM[0x0C]=V&0x3F; else if(tmp&3) PALRAM[(tmp&0x1f)]=V&0x3f; } else if(tmp<0x2000) { if(PPUCHRRAM&(1<<(tmp>>10))) VPage[tmp>>10][tmp]=V; } else { if(PPUNTARAM&(1<<((tmp&0xF00)>>10))) vnapage[((tmp&0xF00)>>10)][tmp&0x3FF]=V; } // FCEU_printf("ppu (%04x) %04x:%04x %d, %d\n",X.PC,RefreshAddr,PPUGenLatch,scanline,timestamp); if(INC32) RefreshAddr+=32; else RefreshAddr++; if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); } } static DECLFW(B4014) { uint32 t=V<<8; int x; for(x=0;x<256;x++) X6502_DMW(0x2004,X6502_DMR(t+x)); } #define PAL(c) ((c)+cc) #define GETLASTPIXEL (PAL?((timestamp*48-linestartts)/15) : ((timestamp*48-linestartts)>>4) ) static uint8 *Pline,*Plinef; static int firsttile; int linestartts; //no longer static so the debugger can see it static int tofix=0; static void ResetRL(uint8 *target) { memset(target,0xFF,256); InputScanlineHook(0,0,0,0); Plinef=target; Pline=target; firsttile=0; linestartts=timestamp*48+X.count; tofix=0; FCEUPPU_LineUpdate(); tofix=1; } static uint8 sprlinebuf[256+8]; void FCEUPPU_LineUpdate(void) { #ifdef FCEUDEF_DEBUGGER if(!fceuindbg) #endif if(Pline) { int l=GETLASTPIXEL; RefreshLine(l); } } static bool rendersprites=true, renderbg=true; void FCEUI_SetRenderPlanes(bool sprites, bool bg) { rendersprites = sprites; renderbg = bg; } void FCEUI_GetRenderPlanes(bool& sprites, bool& bg) { sprites = rendersprites; bg = renderbg; } //mbg 6/21/08 - tileview is being ripped out since i dont know how long its been since it worked //static int tileview=1; //void FCEUI_ToggleTileView(void) //{ // tileview^=1; //} //mbg 6/21/08 - tileview is being ripped out since i dont know how long its been since it worked //static void TileView(void) //{ // uint8 *P=XBuf+16*256; // int bgh; // int y; // int X1; // for(bgh=0;bgh<2;bgh++) // for(y=0;y<16*8;y++) // for(P=XBuf+bgh*128+(16+y)*256,X1=16;X1;X1--,P+=8) // { // uint8 *C; // register uint8 cc; // uint32 vadr; // // vadr=((((16-X1)|((y>>3)<<4))<<4)|(y&7))+bgh*0x1000; // //C= ROM+vadr+turt*8192; // C = VRAMADR(vadr); // //if((vadr+turt*8192)>=524288) // //printf("%d ",vadr+turt*8192); // cc=0; // //#include "pputile.inc" // } //} static void CheckSpriteHit(int p); static void EndRL(void) { RefreshLine(272); if(tofix) Fixit1(); CheckSpriteHit(272); Pline=0; } static int32 sphitx; static uint8 sphitdata; static void CheckSpriteHit(int p) { int l=p-16; int x; if(sphitx==0x100) return; for(x=sphitx;x<(sphitx+8) && x>(x-sphitx))) && !(Plinef[x]&64)) { PPU_status|=0x40; //printf("Ha: %d, %d, Hita: %d, %d, %d, %d, %d\n",p,p&~7,scanline,GETLASTPIXEL-16,&Plinef[x],Pline,Pline-Plinef); //printf("%d\n",GETLASTPIXEL-16); //if(Plinef[x] == 0xFF) //printf("PL: %d, %02x\n",scanline, Plinef[x]); sphitx=0x100; break; } } } //spork the world. Any sprites on this line? Then this will be set to 1. //Needed for zapper emulation and *gasp* sprite emulation. static int spork=0; // lasttile is really "second to last tile." static void RefreshLine(int lastpixel) { static uint32 pshift[2]; static uint32 atlatch; uint32 smorkus=RefreshAddr; #define RefreshAddr smorkus uint32 vofs; int X1; register uint8 *P=Pline; int lasttile=lastpixel>>3; int numtiles; static int norecurse=0; /* Yeah, recursion would be bad. PPU_hook() functions can call mirroring/chr bank switching functions, which call FCEUPPU_LineUpdate, which call this function. */ if(norecurse) return; if(sphitx != 0x100 && !(PPU_status&0x40)) { if((sphitx < (lastpixel-16)) && !(sphitx < ((lasttile - 2)*8))) { //printf("OK: %d\n",scanline); lasttile++; } } if(lasttile>34) lasttile=34; numtiles=lasttile-firsttile; if(numtiles<=0) return; P=Pline; vofs=0; vofs=((PPU[0]&0x10)<<8) | ((RefreshAddr>>12)&7); if(!ScreenON && !SpriteON) { uint32 tem; tem=Pal[0]|(Pal[0]<<8)|(Pal[0]<<16)|(Pal[0]<<24); tem|=0x40404040; FCEU_dwmemset(Pline,tem,numtiles*8); P+=numtiles*8; Pline=P; firsttile=lasttile; #define TOFIXNUM (272-0x4) if(lastpixel>=TOFIXNUM && tofix) { Fixit1(); tofix=0; } if((lastpixel-16)>=0) { InputScanlineHook(Plinef,spork?sprlinebuf:0,linestartts,lasttile*8-16); } return; } //Priority bits, needed for sprite emulation. Pal[0]|=64; Pal[4]|=64; Pal[8]|=64; Pal[0xC]|=64; //This high-level graphics MMC5 emulation code was written for MMC5 carts in "CL" mode. //It's probably not totally correct for carts in "SL" mode. #define PPUT_MMC5 if(MMC5Hack && geniestage!=1) { if(MMC5HackCHRMode==0 && (MMC5HackSPMode&0x80)) { int tochange=MMC5HackSPMode&0x1F; tochange-=firsttile; for(X1=firsttile;X10 && !(MMC5HackSPMode&0x40))) { #define PPUT_MMC5SP #include "pputile.inc" #undef PPUT_MMC5SP } else { #include "pputile.inc" } tochange--; } } else if(MMC5HackCHRMode==1 && (MMC5HackSPMode&0x80)) { int tochange=MMC5HackSPMode&0x1F; tochange-=firsttile; #define PPUT_MMC5SP #define PPUT_MMC5CHR1 for(X1=firsttile;X10) FCEU_dwmemset(Plinef+tstart*8,tem,tcount*8); } if(lastpixel>=TOFIXNUM && tofix) { //puts("Fixed"); Fixit1(); tofix=0; } //CheckSpriteHit(lasttile*8); //lasttile*8); //lastpixel); //This only works right because of a hack earlier in this function. CheckSpriteHit(lastpixel); if((lastpixel-16)>=0) { InputScanlineHook(Plinef,spork?sprlinebuf:0,linestartts,lasttile*8-16); } Pline=P; firsttile=lasttile; } static INLINE void Fixit2(void) { if(ScreenON || SpriteON) { uint32 rad=RefreshAddr; rad&=0xFBE0; rad|=TempAddr&0x041f; RefreshAddr=rad; //PPU_hook(RefreshAddr); //PPU_hook(RefreshAddr,-1); } } static void Fixit1(void) { if(ScreenON || SpriteON) { uint32 rad=RefreshAddr; if((rad&0x7000)==0x7000) { rad^=0x7000; if((rad&0x3E0)==0x3A0) { rad^=0x3A0; rad^=0x800; } else { if((rad&0x3E0)==0x3e0) rad^=0x3e0; else rad+=0x20; } } else rad+=0x1000; RefreshAddr=rad; //PPU_hook(RefreshAddr); //,-1); } } void MMC5_hb(int); //Ugh ugh ugh. static void DoLine(void) { int x; uint8 *target=XBuf+(scanline<<8); if(MMC5Hack && (ScreenON || SpriteON)) MMC5_hb(scanline); X6502_Run(256); EndRL(); if(!renderbg) // User asked to not display background data. { uint32 tem; uint8 col; if(gNoBGFillColor == 0xFF) col = Pal[0]; else col = gNoBGFillColor; tem=col|(col<<8)|(col<<16)|(col<<24); tem|=0x40404040; FCEU_dwmemset(target,tem,256); } if(SpriteON) CopySprites(target); if(ScreenON || SpriteON) // Yes, very el-cheapo. { if(PPU[1]&0x01) { for(x=63;x>=0;x--) *(uint32 *)&target[x<<2]=(*(uint32*)&target[x<<2])&0x30303030; } } if((PPU[1]>>5)==0x7) { for(x=63;x>=0;x--) *(uint32 *)&target[x<<2]=((*(uint32*)&target[x<<2])&0x3f3f3f3f)|0xc0c0c0c0; } else if(PPU[1]&0xE0) for(x=63;x>=0;x--) *(uint32 *)&target[x<<2]=(*(uint32*)&target[x<<2])|0x40404040; else for(x=63;x>=0;x--) *(uint32 *)&target[x<<2]=((*(uint32*)&target[x<<2])&0x3f3f3f3f)|0x80808080; sphitx=0x100; if(ScreenON || SpriteON) FetchSpriteData(); if(GameHBIRQHook && (ScreenON || SpriteON) && ((PPU[0]&0x38)!=0x18)) { X6502_Run(6); Fixit2(); X6502_Run(4); GameHBIRQHook(); X6502_Run(85-16-10); } else { X6502_Run(6); // Tried 65, caused problems with Slalom(maybe others) Fixit2(); X6502_Run(85-6-16); // A semi-hack for Star Trek: 25th Anniversary if(GameHBIRQHook && (ScreenON || SpriteON) && ((PPU[0]&0x38)!=0x18)) GameHBIRQHook(); } DEBUG(FCEUD_UpdateNTView(scanline,0)); if(SpriteON) RefreshSprites(); if(GameHBIRQHook2 && (ScreenON || SpriteON)) GameHBIRQHook2(); scanline++; if(scanline<240) { ResetRL(XBuf+(scanline<<8)); } X6502_Run(16); } #define V_FLIP 0x80 #define H_FLIP 0x40 #define SP_BACK 0x20 typedef struct { uint8 y,no,atr,x; } SPR; typedef struct { uint8 ca[2],atr,x; } SPRB; void FCEUI_DisableSpriteLimitation(int a) { maxsprites=a?64:8; } static uint8 numsprites,SpriteBlurp; static void FetchSpriteData(void) { uint8 ns,sb; SPR *spr; uint8 H; int n; int vofs; uint8 P0=PPU[0]; spr=(SPR *)SPRAM; H=8; ns=sb=0; vofs=(unsigned int)(P0&0x8&(((P0&0x20)^0x20)>>2))<<9; H+=(P0&0x20)>>2; if(!PPU_hook) for(n=63;n>=0;n--,spr++) { if((unsigned int)(scanline-spr->y)>=H) continue; //printf("%d, %u\n",scanline,(unsigned int)(scanline-spr->y)); if(nsy); if(Sprite16) vadr = ((spr->no&1)<<12) + ((spr->no&0xFE)<<4); else vadr = (spr->no<<4)+vofs; if(spr->atr&V_FLIP) { vadr+=7; vadr-=t; vadr+=(P0&0x20)>>1; vadr-=t&8; } else { vadr+=t; vadr+=t&8; } /* Fix this geniestage hack */ if(MMC5Hack && geniestage!=1) C = MMC5SPRVRAMADR(vadr); else C = VRAMADR(vadr); dst.ca[0]=C[0]; dst.ca[1]=C[8]; dst.x=spr->x; dst.atr=spr->atr; *(uint32 *)&SPRBUF[ns<<2]=*(uint32 *)&dst; } ns++; } else { PPU_status|=0x20; break; } } else for(n=63;n>=0;n--,spr++) { if((unsigned int)(scanline-spr->y)>=H) continue; if(nsy); if(Sprite16) vadr = ((spr->no&1)<<12) + ((spr->no&0xFE)<<4); else vadr = (spr->no<<4)+vofs; if(spr->atr&V_FLIP) { vadr+=7; vadr-=t; vadr+=(P0&0x20)>>1; vadr-=t&8; } else { vadr+=t; vadr+=t&8; } if(MMC5Hack) C = MMC5SPRVRAMADR(vadr); else C = VRAMADR(vadr); dst.ca[0]=C[0]; if(ns<8) { PPU_hook(0x2000); PPU_hook(vadr); } dst.ca[1]=C[8]; dst.x=spr->x; dst.atr=spr->atr; *(uint32 *)&SPRBUF[ns<<2]=*(uint32 *)&dst; } ns++; } else { PPU_status|=0x20; break; } } //if(ns>=7) //printf("%d %d\n",scanline,ns); //Handle case when >8 sprites per scanline option is enabled. if(ns>8) PPU_status|=0x20; else if(PPU_hook) { for(n=0;n<(8-ns);n++) { PPU_hook(0x2000); PPU_hook(vofs); } } numsprites=ns; SpriteBlurp=sb; } static void RefreshSprites(void) { int n; SPRB *spr; spork=0; if(!numsprites) return; FCEU_dwmemset(sprlinebuf,0x80808080,256); numsprites--; spr = (SPRB*)SPRBUF+numsprites; for(n=numsprites;n>=0;n--,spr--) { uint32 pixdata; uint8 J,atr; int x=spr->x; uint8 *C; uint8 *VB; pixdata=ppulut1[spr->ca[0]]|ppulut2[spr->ca[1]]; J=spr->ca[0]|spr->ca[1]; atr=spr->atr; if(J) { if(n==0 && SpriteBlurp && !(PPU_status&0x40)) { sphitx=x; sphitdata=J; if(atr&H_FLIP) sphitdata= ((J<<7)&0x80) | ((J<<5)&0x40) | ((J<<3)&0x20) | ((J<<1)&0x10) | ((J>>1)&0x08) | ((J>>3)&0x04) | ((J>>5)&0x02) | ((J>>7)&0x01); } C = sprlinebuf+x; VB = (PALRAM+0x10)+((atr&3)<<2); if(atr&SP_BACK) { if(atr&H_FLIP) { if(J&0x80) C[7]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x40) C[6]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x20) C[5]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x10) C[4]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x08) C[3]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x04) C[2]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x02) C[1]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x01) C[0]=VB[pixdata]|0x40; } else { if(J&0x80) C[0]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x40) C[1]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x20) C[2]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x10) C[3]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x08) C[4]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x04) C[5]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x02) C[6]=VB[pixdata&3]|0x40; pixdata>>=4; if(J&0x01) C[7]=VB[pixdata]|0x40; } } else { if(atr&H_FLIP) { if(J&0x80) C[7]=VB[pixdata&3]; pixdata>>=4; if(J&0x40) C[6]=VB[pixdata&3]; pixdata>>=4; if(J&0x20) C[5]=VB[pixdata&3]; pixdata>>=4; if(J&0x10) C[4]=VB[pixdata&3]; pixdata>>=4; if(J&0x08) C[3]=VB[pixdata&3]; pixdata>>=4; if(J&0x04) C[2]=VB[pixdata&3]; pixdata>>=4; if(J&0x02) C[1]=VB[pixdata&3]; pixdata>>=4; if(J&0x01) C[0]=VB[pixdata]; }else{ if(J&0x80) C[0]=VB[pixdata&3]; pixdata>>=4; if(J&0x40) C[1]=VB[pixdata&3]; pixdata>>=4; if(J&0x20) C[2]=VB[pixdata&3]; pixdata>>=4; if(J&0x10) C[3]=VB[pixdata&3]; pixdata>>=4; if(J&0x08) C[4]=VB[pixdata&3]; pixdata>>=4; if(J&0x04) C[5]=VB[pixdata&3]; pixdata>>=4; if(J&0x02) C[6]=VB[pixdata&3]; pixdata>>=4; if(J&0x01) C[7]=VB[pixdata]; } } } } SpriteBlurp=0; spork=1; } static void CopySprites(uint8 *target) { uint8 n=((PPU[1]&4)^4)<<1; uint8 *P=target; if(!spork) return; spork=0; if(!rendersprites) return; //User asked to not display sprites. loopskie: { uint32 t=*(uint32 *)(sprlinebuf+n); if(t!=0x80808080) { #ifdef LSB_FIRST if(!(t&0x80)) { if(!(t&0x40) || (P[n]&0x40)) // Normal sprite || behind bg sprite P[n]=sprlinebuf[n]; } if(!(t&0x8000)) { if(!(t&0x4000) || (P[n+1]&0x40)) // Normal sprite || behind bg sprite P[n+1]=(sprlinebuf+1)[n]; } if(!(t&0x800000)) { if(!(t&0x400000) || (P[n+2]&0x40)) // Normal sprite || behind bg sprite P[n+2]=(sprlinebuf+2)[n]; } if(!(t&0x80000000)) { if(!(t&0x40000000) || (P[n+3]&0x40)) // Normal sprite || behind bg sprite P[n+3]=(sprlinebuf+3)[n]; } #else /* TODO: Simplify */ if(!(t&0x80000000)) { if(!(t&0x40000000)) // Normal sprite P[n]=sprlinebuf[n]; else if(P[n]&64) // behind bg sprite P[n]=sprlinebuf[n]; } if(!(t&0x800000)) { if(!(t&0x400000)) // Normal sprite P[n+1]=(sprlinebuf+1)[n]; else if(P[n+1]&64) // behind bg sprite P[n+1]=(sprlinebuf+1)[n]; } if(!(t&0x8000)) { if(!(t&0x4000)) // Normal sprite P[n+2]=(sprlinebuf+2)[n]; else if(P[n+2]&64) // behind bg sprite P[n+2]=(sprlinebuf+2)[n]; } if(!(t&0x80)) { if(!(t&0x40)) // Normal sprite P[n+3]=(sprlinebuf+3)[n]; else if(P[n+3]&64) // behind bg sprite P[n+3]=(sprlinebuf+3)[n]; } #endif } } n+=4; if(n) goto loopskie; } void FCEUPPU_SetVideoSystem(int w) { if(w) { scanlines_per_frame=312; FSettings.FirstSLine=FSettings.UsrFirstSLine[1]; FSettings.LastSLine=FSettings.UsrLastSLine[1]; } else { scanlines_per_frame=262; FSettings.FirstSLine=FSettings.UsrFirstSLine[0]; FSettings.LastSLine=FSettings.UsrLastSLine[0]; } } //Initializes the PPU void FCEUPPU_Init(void) { makeppulut(); } void PPU_ResetHooks() { FFCEUX_PPURead = FFCEUX_PPURead_Default; } void FCEUPPU_Reset(void) { VRAMBuffer=PPU[0]=PPU[1]=PPU_status=PPU[3]=0; PPUSPL=0; PPUGenLatch=0; RefreshAddr=TempAddr=0; vtoggle = 0; ppudead = 2; kook = 0; idleSynch = 1; // XOffset=0; ppur.reset(); spr_read.reset(); } void FCEUPPU_Power(void) { int x; memset(NTARAM,0x00,0x800); memset(PALRAM,0x00,0x20); memset(UPALRAM,0x00,0x03); memset(SPRAM,0x00,0x100); FCEUPPU_Reset(); for(x=0x2000;x<0x4000;x+=8) { ARead[x]=A200x; BWrite[x]=B2000; ARead[x+1]=A200x; BWrite[x+1]=B2001; ARead[x+2]=A2002; BWrite[x+2]=B2002; ARead[x+3]=A200x; BWrite[x+3]=B2003; ARead[x+4]=A2004; //A2004; BWrite[x+4]=B2004; ARead[x+5]=A200x; BWrite[x+5]=B2005; ARead[x+6]=A200x; BWrite[x+6]=B2006; ARead[x+7]=A2007; BWrite[x+7]=B2007; } BWrite[0x4014]=B4014; } int FCEUPPU_Loop(int skip) { if((newppu) && (GameInfo->type!=GIT_NSF)) { int FCEUX_PPU_Loop(int skip); return FCEUX_PPU_Loop(skip); } //Needed for Knight Rider, possibly others. if(ppudead) { memset(XBuf, 0x80, 256*240); X6502_Run(scanlines_per_frame*(256+85)); ppudead--; } else { X6502_Run(256+85); PPU_status |= 0x80; //Not sure if this is correct. According to Matt Conte and my own tests, it is. //Timing is probably off, though. //NOTE: Not having this here breaks a Super Donkey Kong game. PPU[3]=PPUSPL=0; //I need to figure out the true nature and length of this delay. X6502_Run(12); if(GameInfo->type==GIT_NSF) DoNSFFrame(); else { if(VBlankON) TriggerNMI(); } X6502_Run((scanlines_per_frame-242)*(256+85)-12); //-12); PPU_status&=0x1f; X6502_Run(256); { int x; if(ScreenON || SpriteON) { if(GameHBIRQHook && ((PPU[0]&0x38)!=0x18)) GameHBIRQHook(); if(PPU_hook) for(x=0;x<42;x++) {PPU_hook(0x2000); PPU_hook(0);} if(GameHBIRQHook2) GameHBIRQHook2(); } X6502_Run(85-16); if(ScreenON || SpriteON) { RefreshAddr=TempAddr; if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); } //Clean this stuff up later. spork=numsprites=0; ResetRL(XBuf); X6502_Run(16-kook); kook ^= 1; } if(GameInfo->type==GIT_NSF) X6502_Run((256+85)*240); #ifdef FRAMESKIP else if(skip) { int y; y=SPRAM[0]; y++; PPU_status|=0x20; // Fixes "Bee 52". Does it break anything? if(GameHBIRQHook) { X6502_Run(256); for(scanline=0;scanline<240;scanline++) { if(ScreenON || SpriteON) GameHBIRQHook(); if(scanline==y && SpriteON) PPU_status|=0x40; X6502_Run((scanline==239)?85:(256+85)); } } else if(y<240) { X6502_Run((256+85)*y); if(SpriteON) PPU_status|=0x40; // Quick and very dirty hack. X6502_Run((256+85)*(240-y)); } else X6502_Run((256+85)*240); } #endif else { int x,max,maxref; deemp=PPU[1]>>5; for(scanline=0;scanline<240;) //scanline is incremented in DoLine. Evil. :/ { deempcnt[deemp]++; DEBUG(FCEUD_UpdatePPUView(scanline, 1)); DoLine(); } if(MMC5Hack && (ScreenON || SpriteON)) MMC5_hb(scanline); for(x=1,max=0,maxref=0;x<7;x++) { if(deempcnt[x]>max) { max=deempcnt[x]; maxref=x; } deempcnt[x]=0; } //FCEU_DispMessage("%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x %d",deempcnt[0],deempcnt[1],deempcnt[2],deempcnt[3],deempcnt[4],deempcnt[5],deempcnt[6],deempcnt[7],maxref); //memset(deempcnt,0,sizeof(deempcnt)); SetNESDeemph(maxref,0); } } //else... to if(ppudead) #ifdef FRAMESKIP if(skip) { FCEU_PutImageDummy(); return(0); } else #endif { //mbg 6/21/08 - tileview is being ripped out since i dont know how long its been since it worked //if(tileview) TileView(); FCEU_PutImage(); return(1); } } int (*PPU_MASTER)(int skip) = FCEUPPU_Loop; static uint16 TempAddrT,RefreshAddrT; void FCEUPPU_LoadState(int version) { TempAddr=TempAddrT; RefreshAddr=RefreshAddrT; } SFORMAT FCEUPPU_STATEINFO[]={ { NTARAM, 0x800, "NTAR"}, { PALRAM, 0x20, "PRAM"}, { SPRAM, 0x100, "SPRA"}, { PPU, 0x4, "PPUR"}, { &kook, 1, "KOOK"}, { &ppudead, 1, "DEAD"}, { &PPUSPL, 1, "PSPL"}, { &XOffset, 1, "XOFF"}, { &vtoggle, 1, "VTOG"}, { &RefreshAddrT, 2|FCEUSTATE_RLSB, "RADD"}, { &TempAddrT, 2|FCEUSTATE_RLSB, "TADD"}, { &VRAMBuffer, 1, "VBUF"}, { &PPUGenLatch, 1, "PGEN"}, { 0 } }; SFORMAT FCEU_NEWPPU_STATEINFO[] = { { &idleSynch, 1, "IDLS" }, { &spr_read.num, 4|FCEUSTATE_RLSB, "SR_0" }, { &spr_read.count, 4|FCEUSTATE_RLSB, "SR_1" }, { &spr_read.fetch, 4|FCEUSTATE_RLSB, "SR_2" }, { &spr_read.found, 4|FCEUSTATE_RLSB, "SR_3" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx0" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx1" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx2" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx3" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx4" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx5" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx6" }, { &spr_read.found_pos[0], 4|FCEUSTATE_RLSB, "SRx7" }, { &spr_read.ret, 4|FCEUSTATE_RLSB, "SR_4" }, { &spr_read.last, 4|FCEUSTATE_RLSB, "SR_5" }, { &spr_read.mode, 4|FCEUSTATE_RLSB, "SR_6" }, { &ppur.fv, 4|FCEUSTATE_RLSB, "PFVx" }, { &ppur.v, 4|FCEUSTATE_RLSB, "PVxx" }, { &ppur.h, 4|FCEUSTATE_RLSB, "PHxx" }, { &ppur.vt, 4|FCEUSTATE_RLSB, "PVTx" }, { &ppur.ht, 4|FCEUSTATE_RLSB, "PHTx" }, { &ppur._fv, 4|FCEUSTATE_RLSB, "P_FV" }, { &ppur._v, 4|FCEUSTATE_RLSB, "P_Vx" }, { &ppur._h, 4|FCEUSTATE_RLSB, "P_Hx" }, { &ppur._vt, 4|FCEUSTATE_RLSB, "P_VT" }, { &ppur._ht, 4|FCEUSTATE_RLSB, "P_HT" }, { &ppur.fh, 4|FCEUSTATE_RLSB, "PFHx" }, { &ppur.s, 4|FCEUSTATE_RLSB, "PSxx" }, { &ppur.status.sl, 4|FCEUSTATE_RLSB, "PST0" }, { &ppur.status.cycle, 4|FCEUSTATE_RLSB, "PST1" }, { &ppur.status.end_cycle, 4|FCEUSTATE_RLSB, "PST2" }, { 0 } }; void FCEUPPU_SaveState(void) { TempAddrT=TempAddr; RefreshAddrT=RefreshAddr; } //--------------------- int pputime=0; int totpputime=0; const int kLineTime=341; const int kFetchTime=2; void runppu(int x) { //pputime+=x; //if(cputodo<200) return; ppur.status.cycle = (ppur.status.cycle + x) % ppur.status.end_cycle; X6502_Run(x); //pputime -= cputodo<<2; } //todo - consider making this a 3 or 4 slot fifo to keep from touching so much memory struct BGData { struct Record { uint8 nt, at, pt[2]; INLINE void Read() { RefreshAddr = ppur.get_ntread(); nt = CALL_PPUREAD(RefreshAddr); runppu(kFetchTime); RefreshAddr = ppur.get_atread(); at = CALL_PPUREAD(RefreshAddr); //modify at to get appropriate palette shift if(ppur.vt&2) at >>= 4; if(ppur.ht&2) at >>= 2; at &= 0x03; at <<= 2; //horizontal scroll clocked at cycle 3 and then //vertical scroll at 251 runppu(1); if (PPUON) { ppur.increment_hsc(); if (ppur.status.cycle == 251) ppur.increment_vs(); } runppu(1); ppur.par = nt; RefreshAddr = ppur.get_ptread(); pt[0] = CALL_PPUREAD(RefreshAddr); runppu(kFetchTime); RefreshAddr |= 8; pt[1] = CALL_PPUREAD(RefreshAddr); runppu(kFetchTime); } }; Record main[34]; //one at the end is junk, it can never be rendered } bgdata; int framectr=0; int FCEUX_PPU_Loop(int skip) { //262 scanlines if (ppudead) { /* not quite emulating all the NES power up behavior * since it is known that the NES ignores writes to some * register before around a full frame, but no games * should write to those regs during that time, it needs * to wait for vblank */ ppur.status.sl = 241; if (PAL) runppu(70*kLineTime); else runppu(20*kLineTime); ppur.status.sl = 0; runppu(242*kLineTime); ppudead = 0; goto finish; } { PPU_status |= 0x80; ppuphase = PPUPHASE_VBL; //Not sure if this is correct. According to Matt Conte and my own tests, it is. //Timing is probably off, though. //NOTE: Not having this here breaks a Super Donkey Kong game. PPU[3]=PPUSPL=0; const int delay = 20; //fceu used 12 here but I couldnt get it to work in marble madness and pirates. ppur.status.sl = 241; //for sprite reads runppu(delay); //X6502_Run(12); if(VBlankON) TriggerNMI(); if (PAL) runppu(70*(kLineTime)-delay); else runppu(20*(kLineTime)-delay); //this seems to run just before the dummy scanline begins PPU_status = 0; //this early out caused metroid to fail to boot. I am leaving it here as a reminder of what not to do //if(!PPUON) { runppu(kLineTime*242); goto finish; } //There are 2 conditions that update all 5 PPU scroll counters with the //contents of the latches adjacent to them. The first is after a write to //2006/2. The second, is at the beginning of scanline 20, when the PPU starts //rendering data for the first time in a frame (this update won't happen if //all rendering is disabled via 2001.3 and 2001.4). //if(PPUON) // ppur.install_latches(); static uint8 oams[2][64][8]; //[7] turned to [8] for faster indexing static int oamcounts[2]={0,0}; static int oamslot=0; static int oamcount; //capture the initial xscroll //int xscroll = ppur.fh; //render 241 scanlines (including 1 dummy at beginning) for(int sl=0;sl<241;sl++) { spr_read.start_scanline(); ppur.status.sl = sl; const int yp = sl-1; ppuphase = PPUPHASE_BG; if(sl != 0) { DEBUG(FCEUD_UpdatePPUView(scanline=yp,1)); DEBUG(FCEUD_UpdateNTView(scanline=yp,1)); } if(sl != 0) if(MMC5Hack && PPUON) MMC5_hb(yp); //twiddle the oam buffers const int scanslot = oamslot^1; const int renderslot = oamslot; oamslot ^= 1; oamcount = oamcounts[renderslot]; //the main scanline rendering loop: //32 times, we will fetch a tile and then render 8 pixels. //two of those tiles were read in the last scanline. for(int xt=0;xt<32;xt++) { bgdata.main[xt+2].Read(); //ok, we're also going to draw here. //unless we're on the first dummy scanline if(sl != 0) { int xstart = xt<<3; oamcount = oamcounts[renderslot]; uint8 * const target=XBuf+(yp<<8)+xstart; uint8 *ptr = target; int rasterpos = xstart; //check all the conditions that can cause things to render in these 8px const bool renderspritenow = SpriteON && rendersprites && (xt>0 || SpriteLeft8); const bool renderbgnow = ScreenON && renderbg && (xt>0 || BGLeft8); for(int xp=0;xp<8;xp++,rasterpos++) { //bg pos is different from raster pos due to its offsetability. //so adjust for that here const int bgpos = rasterpos + ppur.fh; const int bgpx = bgpos&7; const int bgtile = bgpos>>3; uint8 pixel=0, pixelcolor; //generate the BG data if(renderbgnow) { uint8* pt = bgdata.main[bgtile].pt; pixel = ((pt[0]>>(7-bgpx))&1) | (((pt[1]>>(7-bgpx))&1)<<1) | bgdata.main[bgtile].at; } pixelcolor = PALRAM[pixel]; //look for a sprite to be drawn bool havepixel = false; for(int s=0;s=x && rasterpos>= 1; oam[5] >>= 1; if(!renderspritenow) continue; //bail out if we already have a pixel from a higher priority sprite if(havepixel) continue; //transparent pixel bailout if(spixel==0) continue; //spritehit: //1. is it sprite#0? //2. is the bg pixel nonzero? //then, it is spritehit. if(oam[6] == 0 && pixel != 0) PPU_status |= 0x40; havepixel = true; //priority handling if(oam[2]&0x20) { //behind background: if((pixel&3)!=0) continue; } //bring in the palette bits and palettize spixel |= (oam[2]&3)<<2; pixelcolor = PALRAM[0x10+spixel]; } } //fceu rendering system requires that this be set //(so that it knows there is a valid pixel there?) pixelcolor |= 0x80; *ptr++ = pixelcolor; } } } //look for sprites (was supposed to run concurrent with bg rendering) oamcounts[scanslot] = 0; oamcount=0; const int spriteHeight = Sprite16?16:8; for(int i=0;i<64;i++) { uint8* spr = SPRAM+i*4; if(yp >= spr[0] && yp < spr[0]+spriteHeight) { //if we already have maxsprites, then this new one causes an overflow, //set the flag and bail out. if(oamcount == maxsprites) { PPU_status |= 0x20; break; } //just copy some bytes into the internal sprite buffer for(int j=0;j<4;j++) oams[scanslot][oamcount][j] = spr[j]; //note that we stuff the oam index into [6]. //i need to turn this into a struct so we can have fewer magic numbers oams[scanslot][oamcount][6] = (uint8)i; oamcount++; } } oamcounts[scanslot] = oamcount; //FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline. //well, according to (which?) tests, maybe at the end of hblank. //but, according to what it took to get crystalis working, it is at the beginning of hblank. //this is done at cycle 251 //rendering scanline, it doesn't need to be scanline 0, //because on the first scanline when the increment is 0, the vs_scroll is reloaded. //if(PPUON && sl != 0) // ppur.increment_vs(); //todo - think about clearing oams to a predefined value to force deterministic behavior //so.. this is the end of hblank. latch horizontal scroll values //do it cycle at 251 if(PPUON && sl != 0) ppur.install_h_latches(); ppuphase = PPUPHASE_OBJ; //fetch sprite patterns for(int s=0;s=8) break; //if this is a real sprite sprite, then it is not above the 8 sprite limit. //this is how we support the no 8 sprite limit feature. //not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters //this could be handy for the debugging tools also const bool realSprite = (s<8); uint8* const oam = oams[scanslot][s]; uint32 line = yp - oam[0]; if(oam[2]&0x80) //vflip line = spriteHeight-line-1; uint32 patternNumber = oam[1]; uint32 patternAddress; //8x16 sprite handling: if(Sprite16) { uint32 bank = (patternNumber&1)<<12; patternNumber = patternNumber&~1; patternNumber |= (line>>3); patternAddress = (patternNumber<<4) | bank; } else { patternAddress = (patternNumber<<4) | (SpAdrHI<<9); } //offset into the pattern for the current line. //tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above. //so we just need the line offset for the second pattern patternAddress += line&7; //garbage nametable fetches //reset the scroll counter, happens at cycle 304 if (realSprite) { if ((sl == 0) && PPUON) { if (ppur.status.cycle == 304) { runppu(1); ppur.install_latches(); runppu(1); } else runppu(kFetchTime); } else runppu(kFetchTime); } if(((PPU[0]&0x38)!=0x18) && s == 2 && SpriteON ) { //(The MMC3 scanline counter is based entirely on PPU A12, triggered on rising edges (after the line remains low for a sufficiently long period of time)) //http://nesdevwiki.org/wiki/index.php/Nintendo_MMC3 //test cases for timing: SMB3, Crystalis //crystalis requires deferring this til somewhere in sprite [1,3] //kirby requires deferring this til somewhere in sprite [2,5.. if(PPUON && GameHBIRQHook) { GameHBIRQHook(); } } if(realSprite) runppu(kFetchTime); //pattern table fetches RefreshAddr = patternAddress; oam[4] = CALL_PPUREAD(RefreshAddr); if(realSprite) runppu(kFetchTime); RefreshAddr += 8; oam[5] = CALL_PPUREAD(RefreshAddr); if(realSprite) runppu(kFetchTime); //hflip if(!(oam[2]&0x40)) { oam[4] = bitrevlut[oam[4]]; oam[5] = bitrevlut[oam[5]]; } } ppuphase = PPUPHASE_BG; //fetch BG: two tiles for next line for(int xt=0;xt<2;xt++) bgdata.main[xt].Read(); //I'm unclear of the reason why this particular access to memory is made. //The nametable address that is accessed 2 times in a row here, is also the //same nametable address that points to the 3rd tile to be rendered on the //screen (or basically, the first nametable address that will be accessed when //the PPU is fetching background data on the next scanline). //(not implemented yet) runppu(kFetchTime); if (sl == 0) { if (idleSynch && PPUON && !PAL) ppur.status.end_cycle = 340; else ppur.status.end_cycle = 341; idleSynch ^= 1; } else ppur.status.end_cycle = 341; runppu(kFetchTime); //After memory access 170, the PPU simply rests for 4 cycles (or the //equivelant of half a memory access cycle) before repeating the whole //pixel/scanline rendering process. If the scanline being rendered is the very //first one on every second frame, then this delay simply doesn't exist. if (ppur.status.end_cycle == 341) runppu(1); } if(MMC5Hack && PPUON) MMC5_hb(240); //idle for one line runppu(kLineTime); framectr++; } finish: FCEU_PutImage(); return 0; }