From 40ec613982313127f31e6bbc1a5cca07195eaadc Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Fri, 1 Sep 2017 22:11:41 -0400 Subject: [PATCH] NesHawk: Single Tick PPU Should allow for breaking out into a debugger. --- .../Consoles/Nintendo/NES/NES.Core.cs | 40 +- .../Consoles/Nintendo/NES/PPU.cs | 205 ++- .../Consoles/Nintendo/NES/PPU.run.cs | 1333 +++++++++-------- 3 files changed, 909 insertions(+), 669 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index 18752340e5..1775dcd523 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -366,7 +366,43 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES VS_coin_inserted &= 1; } - ppu.FrameAdvance(); + FrameGo = true; + ppu.ppu_tick_counter = 0; + + if (ppu.ppudead > 0) + { + while (ppu.ppudead > 0) + { + ppu.NewDeadPPU(); + } + } + else + { + ppu.ppu_init_frame(); + + ppu.do_vbl = true; + ppu.do_active_sl = true; + ppu.do_pre_vbl = true; + + // do the vbl ticks seperate, that will save us a few checks that don't happen in active region + while (ppu.do_vbl) + { + ppu.TickPPU_VBL(); + } + + // now do the rest of the frame + while (ppu.do_active_sl) + { + ppu.TickPPU_active(); + } + + // now do the pre-NMI lines + while (ppu.do_pre_vbl) + { + ppu.TickPPU_preVBL(); + } + } + if (lagged) { _lagcount++; @@ -385,6 +421,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES num_cheats = 0; } + public bool FrameGo; + //PAL: //0 15 30 45 60 -> 12 27 42 57 -> 9 24 39 54 -> 6 21 36 51 -> 3 18 33 48 -> 0 //sequence of ppu clocks per cpu clock: 3,3,3,3,4 diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index 4468e90dd2..14a40e2d5e 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -194,7 +194,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } //state - int ppudead; //measured in frames + public int ppudead; //measured in frames bool idleSynch; int NMI_PendingInstructions; byte PPUGenLatch; @@ -253,6 +253,51 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("xbuf", ref xbuf, false); ser.Sync("_totalCycles", ref _totalCycles); + + ser.Sync("do_vbl", ref do_vbl); + ser.Sync("do_active_sl", ref do_active_sl); + ser.Sync("do_pre_vbl", ref do_pre_vbl); + ser.Sync("ppu_tick_counter", ref ppu_tick_counter); + + ser.Sync("scanline_counter", ref scanline_counter); + ser.Sync("nmi_destiny", ref nmi_destiny); + ser.Sync("yp_shift", ref yp_shift); + ser.Sync("sprite_eval_cycle", ref sprite_eval_cycle); + ser.Sync("xt", ref xt); + ser.Sync("xp", ref xp); + ser.Sync("xstart", ref xstart); + ser.Sync("rasterpos", ref rasterpos); + ser.Sync("renderspritenow", ref renderspritenow); + ser.Sync("renderbgnow", ref renderbgnow); + ser.Sync("hit_pending", ref hit_pending); + ser.Sync("s", ref s); + ser.Sync("ppu_aux_index", ref ppu_aux_index); + ser.Sync("junksprite", ref junksprite); + ser.Sync("line", ref line); + ser.Sync("patternNumber", ref patternNumber); + ser.Sync("patternAddress", ref patternAddress); + ser.Sync("temp_addr", ref temp_addr); + ser.Sync("sl_sprites", ref sl_sprites, false); + + byte bg_byte; + for (int i = 0; i < 34; i++) + { + bg_byte = bgdata[i].at; ser.Sync("bgdata", ref bg_byte); bgdata[i].at = bg_byte; + bg_byte = bgdata[i].nt; ser.Sync("bgdata", ref bg_byte); bgdata[i].nt = bg_byte; + bg_byte = bgdata[i].pt_0; ser.Sync("bgdata", ref bg_byte); bgdata[i].pt_0 = bg_byte; + bg_byte = bgdata[i].pt_1; ser.Sync("bgdata", ref bg_byte); bgdata[i].pt_1 = bg_byte; + } + + byte oam_byte; + for (int i = 0; i < 64; i++) + { + oam_byte = t_oam[i].oam_y; ser.Sync("bgdata", ref oam_byte); t_oam[i].oam_y = oam_byte; + oam_byte = t_oam[i].oam_ind; ; ser.Sync("bgdata", ref oam_byte); t_oam[i].oam_ind = oam_byte; + oam_byte = t_oam[i].oam_attr; ser.Sync("bgdata", ref oam_byte); t_oam[i].oam_attr = oam_byte; + oam_byte = t_oam[i].oam_x; ser.Sync("bgdata", ref oam_byte); t_oam[i].oam_x = oam_byte; + oam_byte = t_oam[i].patterns_0; ser.Sync("bgdata", ref oam_byte); t_oam[i].patterns_0 = oam_byte; + oam_byte = t_oam[i].patterns_1; ser.Sync("bgdata", ref oam_byte); t_oam[i].patterns_1 = oam_byte; + } } public void Reset() @@ -264,93 +309,91 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ppu_open_bus_decay_timer = new int[8]; } - void runppu(int x) + void runppu() { //run one ppu cycle at a time so we can interact with the ppu and clockPPU at high granularity - for (int i = 0; i < x; i++) + + race_2006 = false; + if (install_2006>0) { - race_2006 = false; - if (install_2006>0) + install_2006--; + if (install_2006==0) { - install_2006--; - if (install_2006==0) - { - ppur.install_latches(); + ppur.install_latches(); - //nes.LogLine("addr wrote vt = {0}, ht = {1}", ppur._vt, ppur._ht); - //normally the address isnt observed by the board till it gets clocked by a read or write. - //but maybe thats just because a ppu read/write shoves it on the address bus - //apparently this shoves it on the address bus, too, or else blargg's mmc3 tests dont pass - //ONLY if the ppu is not rendering - if (ppur.status.sl == 241 || !PPUON) - nes.Board.AddressPPU(ppur.get_2007access()); + //nes.LogLine("addr wrote vt = {0}, ht = {1}", ppur._vt, ppur._ht); + //normally the address isnt observed by the board till it gets clocked by a read or write. + //but maybe thats just because a ppu read/write shoves it on the address bus + //apparently this shoves it on the address bus, too, or else blargg's mmc3 tests dont pass + //ONLY if the ppu is not rendering + if (ppur.status.sl == 241 || !PPUON) + nes.Board.AddressPPU(ppur.get_2007access()); - race_2006 = true; - } - } - - if (install_2001 > 0) - { - install_2001--; - if (install_2001 == 0) - { - show_bg_new = reg_2001.show_bg; - show_obj_new = reg_2001.show_obj; - } - } - - ppur.status.cycle++; - is_even_cycle = !is_even_cycle; - - if (PPUON && ppur.status.cycle >= 257 && ppur.status.cycle <= 320 && 0 <= ppur.status.sl && ppur.status.sl <= 240) - { - reg_2003 = 0; - } - - // Here we execute a CPU instruction if enough PPU cycles have passed - // also do other things that happen at instruction level granularity - cpu_stepcounter++; - if (cpu_stepcounter == nes.cpu_sequence[cpu_step]) - { - cpu_step++; - if (cpu_step == 5) cpu_step = 0; - cpu_stepcounter = 0; - - // this is where the CPU instruction is called - nes.RunCpuOne(); - - // decay the ppu bus, approximating real behaviour - PpuOpenBusDecay(DecayType.None); - - // Check for NMIs - if (NMI_PendingInstructions > 0) - { - NMI_PendingInstructions--; - if (NMI_PendingInstructions <= 0) - { - nes.cpu.NMI = true; - } - } - } - - if (Reg2002_vblank_active_pending) - { - Reg2002_vblank_active = 1; - Reg2002_vblank_active_pending = false; - } - - if (Reg2002_vblank_clear_pending) - { - Reg2002_vblank_active = 0; - Reg2002_vblank_clear_pending = false; - } - - if (HasClockPPU) - { - nes.Board.ClockPPU(); + race_2006 = true; } } - _totalCycles += x; - } + + if (install_2001 > 0) + { + install_2001--; + if (install_2001 == 0) + { + show_bg_new = reg_2001.show_bg; + show_obj_new = reg_2001.show_obj; + } + } + + ppur.status.cycle++; + is_even_cycle = !is_even_cycle; + + if (PPUON && ppur.status.cycle >= 257 && ppur.status.cycle <= 320 && 0 <= ppur.status.sl && ppur.status.sl <= 240) + { + reg_2003 = 0; + } + + // Here we execute a CPU instruction if enough PPU cycles have passed + // also do other things that happen at instruction level granularity + cpu_stepcounter++; + if (cpu_stepcounter == nes.cpu_sequence[cpu_step]) + { + cpu_step++; + if (cpu_step == 5) cpu_step = 0; + cpu_stepcounter = 0; + + // this is where the CPU instruction is called + nes.RunCpuOne(); + + // decay the ppu bus, approximating real behaviour + PpuOpenBusDecay(DecayType.None); + + // Check for NMIs + if (NMI_PendingInstructions > 0) + { + NMI_PendingInstructions--; + if (NMI_PendingInstructions <= 0) + { + nes.cpu.NMI = true; + } + } + } + + if (Reg2002_vblank_active_pending) + { + Reg2002_vblank_active = 1; + Reg2002_vblank_active_pending = false; + } + + if (Reg2002_vblank_clear_pending) + { + Reg2002_vblank_active = 0; + Reg2002_vblank_clear_pending = false; + } + + if (HasClockPPU) + { + nes.Board.ClockPPU(); + } + _totalCycles += 1; + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index 894df6d75b..233aaec96b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -17,6 +17,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public byte pt_0, pt_1; }; + BGDataRecord[] bgdata = new BGDataRecord[34]; + public short[] xbuf = new short[256 * 240]; // values here are used in sprite evaluation @@ -40,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public byte[] soam = new byte[512]; // in a real nes, this would only be 32, but we wish to allow more then 8 sprites per scanline public bool reg_2001_color_disable_latch; // the value used here is taken public bool ppu_was_on; - public byte[,] sl_sprites = new byte[3, 256]; + public unsafe byte[] sl_sprites = new byte[3 * 256]; // installing vram address is delayed after second write to 2006, set this up here public int install_2006; @@ -62,30 +64,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES TempOAM[] t_oam = new TempOAM[64]; int ppu_addr_temp; - void Read_bgdata(ref BGDataRecord bgdata) - { - for (int i = 0; i < 8; i++) - { - Read_bgdata(i, ref bgdata); - runppu(1); - - if (PPUON && i == 6) - { - ppu_was_on = true; - } - - if (PPUON && i == 7) - { - if (!race_2006) - ppur.increment_hsc(); - - if (ppur.status.cycle == 256 && !race_2006) - ppur.increment_vs(); - - ppu_was_on = false; - } - } - } // attempt to emulate graphics pipeline behaviour // experimental @@ -151,48 +129,77 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } //switch(cycle) } - public unsafe void FrameAdvance() - { - BGDataRecord* bgdata = stackalloc BGDataRecord[34]; //one at the end is junk, it can never be rendered + // these are states for the ppu incrementer + public bool do_vbl; + public bool do_active_sl; + public bool do_pre_vbl; + public int ppu_tick_counter; - //262 scanlines - if (ppudead != 0) - { - FrameAdvance_ppudead(); - return; - } + int scanline_counter; + bool nmi_destiny; + int yp_shift; + int sprite_eval_cycle; + int xt; + int xp; + int xstart; + int rasterpos; + bool renderspritenow; + bool renderbgnow; + bool hit_pending; + int s; + int ppu_aux_index; + bool junksprite; + int line; + int patternNumber; + int patternAddress; + int temp_addr; + + public void ppu_init_frame() + { + scanline_counter = 0; + ppu_tick_counter = 0; + + // These things happen at the start of every frame Reg2002_vblank_active_pending = true; ppuphase = PPUPHASE.VBL; ppur.status.sl = 241; + bgdata = new BGDataRecord[34]; + } - //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. - //if (PPUON) reg_2003 = 0; + public void TickPPU_VBL() + { + if (ppu_tick_counter == 3) + { + nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active; + } + else if (ppu_tick_counter == 6) + { + if (nmi_destiny) { nes.cpu.NMI = true; } + nes.Board.AtVsyncNMI(); + } - //this was repeatedly finetuned from the fceux days thrugh the old cpu core and into the new one to pass 05-nmi_timing.nes - //note that there is still some leniency. for instance, 4,2 will pass in addition to 3,3 - const int delay = 6; - runppu(3); - bool nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active; - runppu(3); - if (nmi_destiny) nes.cpu.NMI = true; + runppu(); + ppu_tick_counter++; - nes.Board.AtVsyncNMI(); - runppu(postNMIlines * kLineTime - delay); + if (ppu_tick_counter == postNMIlines * kLineTime) + { + Reg2002_objhit = Reg2002_objoverflow = 0; + Reg2002_vblank_clear_pending = true; + idleSynch ^= true; - //this seems to happen just before the dummy scanline begins - Reg2002_objhit = Reg2002_objoverflow = 0; - Reg2002_vblank_clear_pending = true; + do_vbl = false; + ppu_tick_counter = 0; + } + } - idleSynch ^= true; - - //render 241 scanlines (including 1 dummy at beginning) - for (int sl = 0; sl < 241; sl++) + public void TickPPU_active() + { + if (ppu_tick_counter == 0) { ppur.status.cycle = 0; - ppur.status.sl = sl; + ppur.status.sl = scanline_counter; spr_true_count = 0; soam_index = 0; @@ -206,12 +213,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES sprite_zero_in_range = false; - yp = sl - 1; + yp = scanline_counter - 1; ppuphase = PPUPHASE.BG; // "If PPUADDR is not less then 8 when rendering starts, the first 8 bytes in OAM are written to from // the current location of PPUADDR" - if (sl == 0 && PPUON && reg_2003 >= 8 && region == Region.NTSC) + if (scanline_counter == 0 && PPUON && reg_2003 >= 8 && region == Region.NTSC) { for (int i = 0; i < 8; i++) { @@ -222,504 +229,380 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (NTViewCallback != null && yp == NTViewCallback.Scanline) NTViewCallback.Callback(); if (PPUViewCallback != null && yp == PPUViewCallback.Scanline) PPUViewCallback.Callback(); - //ok, we're also going to draw here. - //unless we're on the first dummy scanline - if (sl != 0) + // set up intial values to use later + yp_shift = yp << 8; + xt = 0; + xp = 0; + + sprite_eval_cycle = 0; + + xstart = xt << 3; + target = yp_shift + xstart; + rasterpos = xstart; + + spriteHeight = reg_2000.obj_size_16 ? 16 : 8; + + //check all the conditions that can cause things to render in these 8px + renderspritenow = show_obj_new && (xt > 0 || reg_2001.show_obj_leftmost); + hit_pending = false; + + } + + if (ppu_tick_counter < 256) + { + if (scanline_counter != 0) { - //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. - int yp_shift = yp << 8; - for (int xt = 0; xt < 32; xt++) + ///////////////////////////////////////////// + // Sprite Evaluation End + ///////////////////////////////////////////// + + if (sprite_eval_cycle <= 63 && !is_even_cycle) { - int xstart = xt << 3; + // the first 64 cycles of each scanline are used to initialize sceondary OAM + // the actual effect setting a flag that always returns 0xFF from a OAM read + // this is a bit of a shortcut to save some instructions + // data is read from OAM as normal but never used + soam[soam_index] = 0xFF; + soam_index++; + } + if (sprite_eval_cycle == 64) + { + soam_index = 0; + oam_index = reg_2003; + } + + // otherwise, scan through OAM and test if sprites are in range + // if they are, they get copied to the secondary OAM + if (sprite_eval_cycle >= 64) + { + if (oam_index >= 256) + { + oam_index = 0; + sprite_eval_write = false; + } + + if (is_even_cycle && oam_index < 256) + { + if ((oam_index + soam_m_index) < 256) + read_value = OAM[oam_index + soam_m_index]; + else + read_value = OAM[oam_index + soam_m_index - 256]; + } + else if (!sprite_eval_write) + { + // if we don't write sprites anymore, just scan through the oam + read_value = soam[0]; + oam_index += 4; + } + else if (sprite_eval_write) + { + //look for sprites + if (spr_true_count == 0 && soam_index < 8) + { + soam[soam_index * 4] = read_value; + } + + if (soam_index < 8) + { + if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0) + { + //a flag gets set if sprite zero is in range + if (oam_index == reg_2003) + sprite_zero_in_range = true; + + spr_true_count++; + soam_m_index++; + } + else if (spr_true_count > 0 && spr_true_count < 4) + { + soam[soam_index * 4 + soam_m_index] = read_value; + + soam_m_index++; + + spr_true_count++; + if (spr_true_count == 4) + { + oam_index += 4; + soam_index++; + if (soam_index == 8) + { + // oam_index could be pathologically misaligned at this point, so we have to find the next + // nearest actual sprite to work on >8 sprites per scanline option + oam_index_aux = (oam_index % 4) * 4; + } + + soam_m_index = 0; + spr_true_count = 0; + } + } + else + { + oam_index += 4; + } + } + else if (soam_index >= 8) + { + if (yp >= read_value && yp < read_value + spriteHeight && PPUON) + { + hit_pending = true; + //Reg2002_objoverflow = true; + } + + if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0) + { + spr_true_count++; + soam_m_index++; + } + else if (spr_true_count > 0 && spr_true_count < 4) + { + soam_m_index++; + + spr_true_count++; + if (spr_true_count == 4) + { + oam_index += 4; + soam_index++; + soam_m_index = 0; + spr_true_count = 0; + } + } + else + { + oam_index += 4; + if (soam_index == 8) + { + soam_m_index++; // glitchy increment + soam_m_index &= 3; + } + + } + + read_value = soam[0]; //writes change to reads + } + } + } + + ///////////////////////////////////////////// + // Sprite Evaluation End + ///////////////////////////////////////////// + + //process the current clock's worth of bg data fetching + //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work + // due to the cpu not running while the sprite renders below + if (PPUON) { Read_bgdata(xp, ref bgdata[xt + 2]); } + + runppu(); + + if (PPUON && xp == 6) + { + ppu_was_on = true; + } + + if (PPUON && xp == 7) + { + if (!race_2006) + ppur.increment_hsc(); + + if (ppur.status.cycle == 256 && !race_2006) + ppur.increment_vs(); + + ppu_was_on = false; + } + + if (hit_pending) + { + hit_pending = false; + Reg2002_objoverflow = true; + } + + renderbgnow = show_bg_new && (xt > 0 || reg_2001.show_bg_leftmost); + //bg pos is different from raster pos due to its offsetability. + //so adjust for that here + int bgpos = rasterpos + ppur.fh; + int bgpx = bgpos & 7; + int bgtile = bgpos >> 3; + + int pixel = 0, pixelcolor = PALRAM[pixel]; + + //according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx + //at one point I commented this out to fix bottom-left garbage in DW4. but it's needed for full_nes_palette. + //solution is to only run when PPU is actually OFF (left-suppression doesnt count) + if (!PPUON) + { + // if there's anything wrong with how we're doing this, someone please chime in + int addr = ppur.get_2007access(); + if ((addr & 0x3F00) == 0x3F00) + { + pixel = addr & 0x1F; + } + pixelcolor = PALRAM[pixel]; + pixelcolor |= 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later + } + + //generate the BG data + if (renderbgnow) + { + byte pt_0 = bgdata[bgtile].pt_0; + byte pt_1 = bgdata[bgtile].pt_1; + int sel = 7 - bgpx; + pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1); + if (pixel != 0) + pixel |= bgdata[bgtile].at; + pixelcolor = PALRAM[pixel]; + } + + if (!nes.Settings.DispBackground) + pixelcolor = 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later + + //check if the pixel has a sprite in it + if (sl_sprites[256 + xt * 8 + xp] != 0 && renderspritenow) + { + int s = sl_sprites[xt * 8 + xp]; + int spixel = sl_sprites[256 + xt * 8 + xp]; + int temp_attr = sl_sprites[512 + xt * 8 + xp]; + + //TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ + //spritehit: + //1. is it sprite#0? + //2. is the bg pixel nonzero? + //then, it is spritehit. + Reg2002_objhit |= (sprite_zero_go && s == 0 && pixel != 0 && rasterpos < 255 && show_bg_new && show_obj_new); + + //priority handling, if in front of BG: + bool drawsprite = !(((temp_attr & 0x20) != 0) && ((pixel & 3) != 0)); + if (drawsprite && nes.Settings.DispSprites) + { + //bring in the palette bits and palettize + spixel |= (temp_attr & 3) << 2; + //save it for use in the framebuffer + pixelcolor = PALRAM[0x10 + spixel]; + } + } //oamcount loop + + + pipeline(pixelcolor, target, xt * 8 + xp); + target++; + + // clear out previous sprites from scanline buffer + //sl_sprites[xt * 8 + xp] = 0; + sl_sprites[256 + xt * 8 + xp] = 0; + //sl_sprites[512 + xt * 8 + xp] = 0; + + // end of visible part of the scanline + sprite_eval_cycle++; + xp++; + rasterpos++; + + if (xp == 8) + { + xp = 0; + xt++; + + xstart = xt << 3; target = yp_shift + xstart; - int rasterpos = xstart; + rasterpos = xstart; spriteHeight = reg_2000.obj_size_16 ? 16 : 8; //check all the conditions that can cause things to render in these 8px - bool renderspritenow = show_obj_new && (xt > 0 || reg_2001.show_obj_leftmost); - bool renderbgnow; - bool hit_pending = false; + renderspritenow = show_obj_new && (xt > 0 || reg_2001.show_obj_leftmost); + hit_pending = false; - for (int xp = 0; xp < 8; xp++, rasterpos++) - { - ////////////////////////////////////////////////// - //Sprite Evaluation Start - ////////////////////////////////////////////////// - if (ppur.status.cycle <= 63 && !is_even_cycle) - { - // the first 64 cycles of each scanline are used to initialize sceondary OAM - // the actual effect setting a flag that always returns 0xFF from a OAM read - // this is a bit of a shortcut to save some instructions - // data is read from OAM as normal but never used - soam[soam_index] = 0xFF; - soam_index++; - } - if (ppur.status.cycle == 64) - { - soam_index = 0; - oam_index = reg_2003; - } - - // otherwise, scan through OAM and test if sprites are in range - // if they are, they get copied to the secondary OAM - if (ppur.status.cycle >= 64) - { - if (oam_index >= 256) - { - oam_index = 0; - sprite_eval_write = false; - } - - if (is_even_cycle && oam_index < 256) - { - if ((oam_index + soam_m_index) < 256) - read_value = OAM[oam_index + soam_m_index]; - else - read_value = OAM[oam_index + soam_m_index - 256]; - } - else if (!sprite_eval_write) - { - // if we don't write sprites anymore, just scan through the oam - read_value = soam[0]; - oam_index += 4; - } - else if (sprite_eval_write) - { - //look for sprites - if (spr_true_count == 0 && soam_index < 8) - { - soam[soam_index * 4] = read_value; - } - - if (soam_index < 8) - { - if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0) - { - //a flag gets set if sprite zero is in range - if (oam_index == reg_2003) - sprite_zero_in_range = true; - - spr_true_count++; - soam_m_index++; - } - else if (spr_true_count > 0 && spr_true_count < 4) - { - soam[soam_index * 4 + soam_m_index] = read_value; - - soam_m_index++; - - spr_true_count++; - if (spr_true_count == 4) - { - oam_index += 4; - soam_index++; - if (soam_index == 8) - { - // oam_index could be pathologically misaligned at this point, so we have to find the next - // nearest actual sprite to work on >8 sprites per scanline option - oam_index_aux = (oam_index % 4) * 4; - } - - soam_m_index = 0; - spr_true_count = 0; - } - } - else - { - oam_index += 4; - } - } - else if (soam_index >= 8) - { - if (yp >= read_value && yp < read_value + spriteHeight && PPUON) - { - hit_pending = true; - //Reg2002_objoverflow = true; - } - - if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0) - { - spr_true_count++; - soam_m_index++; - } - else if (spr_true_count > 0 && spr_true_count < 4) - { - soam_m_index++; - - spr_true_count++; - if (spr_true_count == 4) - { - oam_index += 4; - soam_index++; - soam_m_index = 0; - spr_true_count = 0; - } - } - else - { - oam_index += 4; - if (soam_index == 8) - { - soam_m_index++; // glitchy increment - soam_m_index &= 3; - } - - } - - read_value = soam[0]; //writes change to reads - } - - } - - } - - ////////////////////////////////////////////////// - //Sprite Evaluation End - ////////////////////////////////////////////////// - - //process the current clock's worth of bg data fetching - //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work due to the cpu not running while the sprite renders below - - - if (PPUON) - Read_bgdata(xp, ref bgdata[xt + 2]); - - runppu(1); - - if (PPUON && xp == 6) - { - ppu_was_on = true; - } - - if (PPUON && xp == 7) - { - if (!race_2006) - ppur.increment_hsc(); - - if (ppur.status.cycle == 256 && !race_2006) - ppur.increment_vs(); - - ppu_was_on = false; - } - - if (hit_pending) - { - hit_pending = false; - Reg2002_objoverflow = true; - } - - renderbgnow = show_bg_new && (xt > 0 || reg_2001.show_bg_leftmost); - //bg pos is different from raster pos due to its offsetability. - //so adjust for that here - int bgpos = rasterpos + ppur.fh; - int bgpx = bgpos & 7; - int bgtile = bgpos >> 3; - - int pixel = 0, pixelcolor = PALRAM[pixel]; - - //according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx - //at one point I commented this out to fix bottom-left garbage in DW4. but it's needed for full_nes_palette. - //solution is to only run when PPU is actually OFF (left-suppression doesnt count) - if (!PPUON) - { - // if there's anything wrong with how we're doing this, someone please chime in - int addr = ppur.get_2007access(); - if ((addr & 0x3F00) == 0x3F00) - { - pixel = addr & 0x1F; - } - pixelcolor = PALRAM[pixel]; - pixelcolor |= 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later - } - - //generate the BG data - if (renderbgnow) - { - byte pt_0 = bgdata[bgtile].pt_0; - byte pt_1 = bgdata[bgtile].pt_1; - int sel = 7 - bgpx; - pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1); - if (pixel != 0) - pixel |= bgdata[bgtile].at; - pixelcolor = PALRAM[pixel]; - } - - if (!nes.Settings.DispBackground) - pixelcolor = 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later - - //check if the pixel has a sprite in it - if (sl_sprites[1, xt * 8 + xp] != 0 && renderspritenow) - { - int s = sl_sprites[0, xt * 8 + xp]; - int spixel = sl_sprites[1, xt * 8 + xp]; - int temp_attr = sl_sprites[2, xt * 8 + xp]; - - //TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ - //spritehit: - //1. is it sprite#0? - //2. is the bg pixel nonzero? - //then, it is spritehit. - Reg2002_objhit |= (sprite_zero_go && s == 0 && pixel != 0 && rasterpos < 255 && show_bg_new && show_obj_new); - - //priority handling, if in front of BG: - bool drawsprite = !(((temp_attr & 0x20) != 0) && ((pixel & 3) != 0)); - if (drawsprite && nes.Settings.DispSprites) - { - //bring in the palette bits and palettize - spixel |= (temp_attr & 3) << 2; - //save it for use in the framebuffer - pixelcolor = PALRAM[0x10 + spixel]; - } - } //oamcount loop - - - pipeline(pixelcolor, target, xt * 8 + xp); - target++; - - // clear out previous sprites from scanline buffer - sl_sprites[0, xt * 8 + xp] = 0; - sl_sprites[1, xt * 8 + xp] = 0; - sl_sprites[2, xt * 8 + xp] = 0; - - - } //loop across 8 pixels - } //loop across 32 tiles - } - else - for (int xt = 0; xt < 32; xt++) - Read_bgdata(ref bgdata[xt + 2]); - - // normally only 8 sprites are allowed, but with a particular setting we can have more then that - // this extra bit takes care of it quickly - soam_index_aux = 8; - - if (nes.Settings.AllowMoreThanEightSprites) - { - while (oam_index_aux < 64 && soam_index_aux < 64) - { - //look for sprites - soam[soam_index_aux * 4] = OAM[oam_index_aux * 4]; - read_value_aux = OAM[oam_index_aux * 4]; - if (yp >= read_value_aux && yp < read_value_aux + spriteHeight) - { - soam[soam_index_aux * 4 + 1] = OAM[oam_index_aux * 4 + 1]; - soam[soam_index_aux * 4 + 2] = OAM[oam_index_aux * 4 + 2]; - soam[soam_index_aux * 4 + 3] = OAM[oam_index_aux * 4 + 3]; - soam_index_aux++; - oam_index_aux++; - } - else - { - oam_index_aux++; - } } } - - soam_index_prev = soam_index_aux; - - if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites) - soam_index_prev = 8; - - ppuphase = PPUPHASE.OBJ; - - spriteHeight = reg_2000.obj_size_16 ? 16 : 8; - - for (int s = 0; s < 8; s++) + else { - bool junksprite = (!PPUON); + // if scanline is the pre-render line, we just read BG data + Read_bgdata(xp, ref bgdata[xt + 2]); + + runppu(); + + if (PPUON && xp == 6) + { + ppu_was_on = true; + } + + if (PPUON && xp == 7) + { + if (!race_2006) + ppur.increment_hsc(); + + if (ppur.status.cycle == 256 && !race_2006) + ppur.increment_vs(); + + ppu_was_on = false; + } + + xp++; + + if (xp == 8) + { + xp = 0; + xt++; + } + } + } + else if (ppu_tick_counter < 320) + { + // after we are done with the visible part of the frame, we reach sprite transfer to temp OAM tables and such + if (ppu_tick_counter == 256) + { + // do the more then 8 sprites stuff here where it is convenient + // normally only 8 sprites are allowed, but with a particular setting we can have more then that + // this extra bit takes care of it quickly + soam_index_aux = 8; + + if (nes.Settings.AllowMoreThanEightSprites) + { + while (oam_index_aux < 64 && soam_index_aux < 64) + { + //look for sprites + soam[soam_index_aux * 4] = OAM[oam_index_aux * 4]; + read_value_aux = OAM[oam_index_aux * 4]; + if (yp >= read_value_aux && yp < read_value_aux + spriteHeight) + { + soam[soam_index_aux * 4 + 1] = OAM[oam_index_aux * 4 + 1]; + soam[soam_index_aux * 4 + 2] = OAM[oam_index_aux * 4 + 2]; + soam[soam_index_aux * 4 + 3] = OAM[oam_index_aux * 4 + 3]; + soam_index_aux++; + oam_index_aux++; + } + else + { + oam_index_aux++; + } + } + } + + soam_index_prev = soam_index_aux; + + if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites) + soam_index_prev = 8; + + ppuphase = PPUPHASE.OBJ; + + spriteHeight = reg_2000.obj_size_16 ? 16 : 8; + + s = 0; + ppu_aux_index = 0; + + junksprite = (!PPUON); t_oam[s].oam_y = soam[s * 4]; t_oam[s].oam_ind = soam[s * 4 + 1]; t_oam[s].oam_attr = soam[s * 4 + 2]; t_oam[s].oam_x = soam[s * 4 + 3]; - int line = yp - t_oam[s].oam_y; + line = yp - t_oam[s].oam_y; if ((t_oam[s].oam_attr & 0x80) != 0) //vflip line = spriteHeight - line - 1; - int patternNumber = t_oam[s].oam_ind; - int patternAddress; + patternNumber = t_oam[s].oam_ind; + } - //8x16 sprite handling: - if (reg_2000.obj_size_16) - { - int bank = (patternNumber & 1) << 12; - patternNumber = patternNumber & ~1; - patternNumber |= (line >> 3) & 1; - patternAddress = (patternNumber << 4) | bank; - } - else - patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 12); - - //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 + scroll resets - int garbage_todo = 2; - - ppubus_read(ppur.get_ntread(), true, true); - - if (PPUON) - { - if (sl == 0 && ppur.status.cycle == 304) - { - - read_value = t_oam[s].oam_y; - runppu(1); - - if (PPUON) ppur.install_latches(); - - read_value = t_oam[s].oam_ind; - runppu(1); - - - garbage_todo = 0; - } - if ((sl != 0) && ppur.status.cycle == 256) - { - - read_value = t_oam[s].oam_y; - - runppu(1); - - if (target <= 61441 && target > 0 && s == 0) - { - pipeline(0, target, 256); - target++; - } - - //at 257: 3d world runner is ugly if we do this at 256 - if (PPUON) ppur.install_h_latches(); - read_value = t_oam[s].oam_ind; - runppu(1); - - if (target <= 61441 && target > 0 && s == 0) - { - pipeline(0, target, 257); // last pipeline call option 1 of 2 - } - garbage_todo = 0; - } - } - - for (int i = 0; i < garbage_todo; i++) - { - if (i == 0) - read_value = t_oam[s].oam_y; - else - read_value = t_oam[s].oam_ind; - - runppu(1); - - if (i == 0) - { - if (target <= 61441 && target > 0 && s == 0) - { - pipeline(0, target, 256); - target++; - } - } - else - { - if (target <= 61441 && target > 0 && s == 0) - { - pipeline(0, target, 257); // last pipeline call option 2 of 2 - } - } - } - - ppubus_read(ppur.get_atread(), true, true); //at or nt? - - read_value = t_oam[s].oam_attr; - runppu(1); - - read_value = t_oam[s].oam_x; - runppu(1); - - // if the PPU is off, we don't put anything on the bus - if (junksprite) - { - ppubus_read(patternAddress, true, false); - ppubus_read(patternAddress, true, false); - runppu(kFetchTime * 2); - } - else - { - int addr = patternAddress; - t_oam[s].patterns_0 = ppubus_read(addr, true, true); - read_value = t_oam[s].oam_x; - runppu(kFetchTime); - - addr += 8; - t_oam[s].patterns_1 = ppubus_read(addr, true, true); - read_value = t_oam[s].oam_x; - runppu(kFetchTime); - - // hflip - if ((t_oam[s].oam_attr & 0x40) == 0) - { - t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0]; - t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1]; - } - - // if the sprites attribute is 0xFF, then this indicates a non-existent sprite - // I think the logic here is that bits 2-4 in OAM are disabled, but soam is initialized with 0xFF - // so the only way a sprite could have an 0xFF attribute is if it is not in the scope of the scanline - if (t_oam[s].oam_attr == 0xFF) - { - t_oam[s].patterns_0 = 0; - t_oam[s].patterns_1 = 0; - } - - } - - // now that we have a sprite, we can fill in the next scnaline's sprite pixels with it - // this saves quite a bit of processing compared to checking each pixel - - if (s < soam_index_prev) - { - int temp_x = t_oam[s].oam_x; - for (int i = 0; (temp_x + i) < 256 && i < 8; i++) - { - if (sl_sprites[1, temp_x + i] == 0) - { - if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i)) - { - int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0; - spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0); - - sl_sprites[0, temp_x + i] = (byte)s; - sl_sprites[1, temp_x + i] = (byte)spixel; - sl_sprites[2, temp_x + i] = t_oam[s].oam_attr; - - } - } - } - } - - } // sprite pattern fetch loop - - //now do the same for extra sprites, but without any cycles run - if (soam_index_aux > 8) + switch (ppu_aux_index) { - for (int s = 8; s < soam_index_aux; s++) - { - t_oam[s].oam_y = soam[s * 4]; - t_oam[s].oam_ind = soam[s * 4 + 1]; - t_oam[s].oam_attr = soam[s * 4 + 2]; - t_oam[s].oam_x = soam[s * 4 + 3]; - - int line = yp - t_oam[s].oam_y; - if ((t_oam[s].oam_attr & 0x80) != 0) //vflip - line = spriteHeight - line - 1; - - int patternNumber = t_oam[s].oam_ind; - int patternAddress; - + case 0: //8x16 sprite handling: if (reg_2000.obj_size_16) { @@ -736,91 +619,367 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //so we just need the line offset for the second pattern patternAddress += line & 7; - ppubus_read(ppur.get_ntread(), true, false); + ppubus_read(ppur.get_ntread(), true, true); - ppubus_read(ppur.get_atread(), true, false); //at or nt? - - int addr = patternAddress; - t_oam[s].patterns_0 = ppubus_read(addr, true, false); - - addr += 8; - t_oam[s].patterns_1 = ppubus_read(addr, true, false); - - // hflip - if ((t_oam[s].oam_attr & 0x40) == 0) + read_value = t_oam[s].oam_y; + runppu(); + break; + case 1: + if (PPUON && scanline_counter == 0 && ppur.status.cycle == 305) { - t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0]; - t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1]; - } + ppur.install_latches(); - // if the sprites attribute is 0xFF, then this indicates a non-existent sprite - // I think the logic here is that bits 2-4 in OAM are disabled, but soam is initialized with 0xFF - // so the only way a sprite could have an 0xFF attribute is if it is not in the scope of the scanline - if (t_oam[s].oam_attr == 0xFF) + read_value = t_oam[s].oam_ind; + runppu(); + + } + else if (PPUON && (scanline_counter != 0) && ppur.status.cycle == 257) { - t_oam[s].patterns_0 = 0; - t_oam[s].patterns_1 = 0; - } + if (target <= 61441 && target > 0 && s == 0) + { + pipeline(0, target, 256); + target++; + } + + //at 257: 3d world runner is ugly if we do this at 256 + if (PPUON) ppur.install_h_latches(); + read_value = t_oam[s].oam_ind; + runppu(); + + if (target <= 61441 && target > 0 && s == 0) + { + pipeline(0, target, 257); // last pipeline call option 1 of 2 + } + } + else + { + if (target <= 61441 && target > 0 && s == 0) + { + pipeline(0, target, 256); + target++; + } + + read_value = t_oam[s].oam_ind; + runppu(); + + if (target <= 61441 && target > 0 && s == 0) + { + pipeline(0, target, 257); // last pipeline call option 2 of 2 + } + } + break; + + case 2: + ppubus_read(ppur.get_atread(), true, true); //at or nt? + read_value = t_oam[s].oam_attr; + runppu(); + break; + + case 3: + read_value = t_oam[s].oam_x; + runppu(); + break; + + case 4: + // if the PPU is off, we don't put anything on the bus + if (junksprite) + { + ppubus_read(patternAddress, true, false); + runppu(); + } + else + { + temp_addr = patternAddress; + t_oam[s].patterns_0 = ppubus_read(temp_addr, true, true); + read_value = t_oam[s].oam_x; + runppu(); + } + break; + case 5: + // if the PPU is off, we don't put anything on the bus + if (junksprite) + { + runppu(); + } + else + { + runppu(); + } + break; + case 6: + // if the PPU is off, we don't put anything on the bus + if (junksprite) + { + ppubus_read(patternAddress, true, false); + runppu(); + } + else + { + temp_addr += 8; + t_oam[s].patterns_1 = ppubus_read(temp_addr, true, true); + read_value = t_oam[s].oam_x; + runppu(); + } + break; + case 7: + // if the PPU is off, we don't put anything on the bus + if (junksprite) + { + runppu(); + } + else + { + runppu(); + + // hflip + if ((t_oam[s].oam_attr & 0x40) == 0) + { + t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0]; + t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1]; + } + + // if the sprites attribute is 0xFF, then this indicates a non-existent sprite + // I think the logic here is that bits 2-4 in OAM are disabled, but soam is initialized with 0xFF + // so the only way a sprite could have an 0xFF attribute is if it is not in the scope of the scanline + if (t_oam[s].oam_attr == 0xFF) + { + t_oam[s].patterns_0 = 0; + t_oam[s].patterns_1 = 0; + } + + } + break; + } + + ppu_aux_index++; + if (ppu_aux_index == 8) + { + // now that we have a sprite, we can fill in the next scnaline's sprite pixels with it + // this saves quite a bit of processing compared to checking each pixel + + if (s < soam_index_prev) + { int temp_x = t_oam[s].oam_x; for (int i = 0; (temp_x + i) < 256 && i < 8; i++) { - if (sl_sprites[1, temp_x + i] == 0) + if (sl_sprites[256 + temp_x + i] == 0) { if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i)) { int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0; spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0); - sl_sprites[0, temp_x + i] = (byte)s; - sl_sprites[1, temp_x + i] = (byte)spixel; - sl_sprites[2, temp_x + i] = t_oam[s].oam_attr; + sl_sprites[temp_x + i] = (byte)s; + sl_sprites[256 + temp_x + i] = (byte)spixel; + sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr; } } } - } // sprite pattern fetch loop + } + ppu_aux_index = 0; + s++; + + if (s < 8) + { + junksprite = (!PPUON); + + t_oam[s].oam_y = soam[s * 4]; + t_oam[s].oam_ind = soam[s * 4 + 1]; + t_oam[s].oam_attr = soam[s * 4 + 2]; + t_oam[s].oam_x = soam[s * 4 + 3]; + + line = yp - t_oam[s].oam_y; + if ((t_oam[s].oam_attr & 0x80) != 0) //vflip + line = spriteHeight - line - 1; + + patternNumber = t_oam[s].oam_ind; + } + else + { + // repeat all the above steps for more then 8 sprites but don't run any cycles + if (soam_index_aux > 8) + { + for (int s = 8; s < soam_index_aux; s++) + { + t_oam[s].oam_y = soam[s * 4]; + t_oam[s].oam_ind = soam[s * 4 + 1]; + t_oam[s].oam_attr = soam[s * 4 + 2]; + t_oam[s].oam_x = soam[s * 4 + 3]; + + int line = yp - t_oam[s].oam_y; + if ((t_oam[s].oam_attr & 0x80) != 0) //vflip + line = spriteHeight - line - 1; + + int patternNumber = t_oam[s].oam_ind; + int patternAddress; + + //8x16 sprite handling: + if (reg_2000.obj_size_16) + { + int bank = (patternNumber & 1) << 12; + patternNumber = patternNumber & ~1; + patternNumber |= (line >> 3) & 1; + patternAddress = (patternNumber << 4) | bank; + } + else + patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 12); + + //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; + + ppubus_read(ppur.get_ntread(), true, false); + + ppubus_read(ppur.get_atread(), true, false); //at or nt? + + int addr = patternAddress; + t_oam[s].patterns_0 = ppubus_read(addr, true, false); + + addr += 8; + t_oam[s].patterns_1 = ppubus_read(addr, true, false); + + // hflip + if ((t_oam[s].oam_attr & 0x40) == 0) + { + t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0]; + t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1]; + } + + // if the sprites attribute is 0xFF, then this indicates a non-existent sprite + // I think the logic here is that bits 2-4 in OAM are disabled, but soam is initialized with 0xFF + // so the only way a sprite could have an 0xFF attribute is if it is not in the scope of the scanline + if (t_oam[s].oam_attr == 0xFF) + { + t_oam[s].patterns_0 = 0; + t_oam[s].patterns_1 = 0; + } + + int temp_x = t_oam[s].oam_x; + for (int i = 0; (temp_x + i) < 256 && i < 8; i++) + { + if (sl_sprites[256 + temp_x + i] == 0) + { + if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i)) + { + int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0; + spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0); + + sl_sprites[temp_x + i] = (byte)s; + sl_sprites[256 + temp_x + i] = (byte)spixel; + sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr; + + } + } + } + } + } + } } - - ppuphase = PPUPHASE.BG; - - // fetch BG: two tiles for next line - for (int xt = 0; xt < 2; xt++) + } + else + { + if (ppu_tick_counter == 320) { - Read_bgdata(ref bgdata[xt]); + ppuphase = PPUPHASE.BG; + xt = 0; + xp = 0; } - // this sequence is tuned to pass 10-even_odd_timing.nes - runppu(4); - bool evenOddDestiny = PPUON; + if (ppu_tick_counter < 336) + { + // if scanline is the pre-render line, we just read BG data + Read_bgdata(xp, ref bgdata[xt]); - // 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 (sl == 0 && idleSynch && evenOddDestiny && chopdot) - { } + runppu(); + + if (PPUON && xp == 6) + { + ppu_was_on = true; + } + + if (PPUON && xp == 7) + { + if (!race_2006) + ppur.increment_hsc(); + + if (ppur.status.cycle == 256 && !race_2006) + ppur.increment_vs(); + + ppu_was_on = false; + } + + xp++; + + if (xp == 8) + { + xp = 0; + xt++; + } + } + else if (ppu_tick_counter < 340) + { + runppu(); + } else - runppu(1); - } // scanline loop + { + bool evenOddDestiny = PPUON; - ppur.status.sl = 241; + // 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 (scanline_counter == 0 && idleSynch && evenOddDestiny && chopdot) + {} + else + { runppu(); } + } + } - //idle for pre NMI lines - runppu(preNMIlines * kLineTime); - } //FrameAdvance + ppu_tick_counter++; + if (ppu_tick_counter == 341) + { + ppu_tick_counter = 0; + scanline_counter++; - void FrameAdvance_ppudead() + if (scanline_counter == 241) + { + do_active_sl = false; + ppur.status.sl = 241; + } + } + } + + public void TickPPU_preVBL() { - //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 + runppu(); - runppu(241 * kLineTime - 3);// -8*3); - ppudead--; + + ppu_tick_counter++; + if (ppu_tick_counter == preNMIlines * kLineTime) + { + ppu_tick_counter = 0; + do_pre_vbl = false; + } + } + + //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 + public void NewDeadPPU() + { + runppu(); + + ppu_tick_counter++; + if (ppu_tick_counter == 241 * kLineTime - 3) + { + ppudead--; + } } } }