From 1d098aa242539b7bed9f53a2a42a52ad4d9f6ca7 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Fri, 16 Sep 2016 07:46:56 -0400 Subject: [PATCH] Fix BG check timing, implment pipeline --- .../Consoles/Nintendo/NES/PPU.run.cs | 544 ++++++++++-------- 1 file changed, 290 insertions(+), 254 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index 90222b7827..9336b9a5ff 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -13,49 +13,74 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { const int kFetchTime = 2; - struct BGDataRecord { + struct BGDataRecord + { public byte nt, at; public byte pt_0, pt_1; }; - public short[] xbuf = new short[256*240]; + public short[] xbuf = new short[256 * 240]; - // values here are used in sprite evaluation - public bool sprite_eval_write; - public byte read_value; - public int soam_index; - public int soam_index_prev; - public int soam_m_index; - public int oam_index; - public int read_value_aux; - public int soam_m_index_aux; - public int oam_index_aux; - public bool is_even_cycle; - public bool sprite_zero_in_range=false; - public bool sprite_zero_go = false; - public int yp; - public int spriteHeight; - public int o_bug; // this is incramented when checks for sprite overflow start, mirroring a hardware bug - 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 + // values here are used in sprite evaluation + public bool sprite_eval_write; + public byte read_value; + public int soam_index; + public int soam_index_prev; + public int soam_m_index; + public int oam_index; + public int read_value_aux; + public int soam_m_index_aux; + public int oam_index_aux; + public bool is_even_cycle; + public bool sprite_zero_in_range = false; + public bool sprite_zero_go = false; + public int yp; + public int spriteHeight; + public int o_bug; // this is incramented when checks for sprite overflow start, mirroring a hardware bug + 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 - struct TempOAM - { - public byte oam_y; - public byte oam_ind; - public byte oam_attr; - public byte oam_x; - public byte patterns_0; - public byte patterns_1; - } + struct TempOAM + { + public byte oam_y; + public byte oam_ind; + public byte oam_attr; + public byte oam_x; + public byte patterns_0; + public byte patterns_1; + } - TempOAM[] t_oam = new TempOAM[64]; + TempOAM[] t_oam = new TempOAM[64]; - int ppu_addr_temp; + int ppu_addr_temp; void Read_bgdata(ref BGDataRecord bgdata) { for (int i = 0; i < 8; i++) - Read_bgdata(i,ref bgdata); + Read_bgdata(i, ref bgdata); } + + // attempt to emulate graphics pipeline behaviour + // experimental + int pixelcolor_latch_1; + int pixelcolor_latch_2; + void pipeline(int pixelcolor, int target) + { + if (target > 1) + { + if (reg_2001.color_disable) + pixelcolor_latch_2 &= 0x30; + + xbuf[(target - 2)] = PaletteAdjustPixel(pixelcolor_latch_2); + } + + if (target != 0) + { + pixelcolor_latch_2 = pixelcolor_latch_1; + } + + pixelcolor_latch_1 = pixelcolor; + } + void Read_bgdata(int cycle, ref BGDataRecord bgdata) { switch (cycle) @@ -123,7 +148,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES const int kLineTime = 341; public unsafe void FrameAdvance() { - BGDataRecord *bgdata = stackalloc BGDataRecord[34]; //one at the end is junk, it can never be rendered + BGDataRecord* bgdata = stackalloc BGDataRecord[34]; //one at the end is junk, it can never be rendered //262 scanlines if (ppudead != 0) @@ -136,13 +161,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ppuphase = PPUPHASE.VBL; ppur.status.sl = 241; - //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 (reg_2001.show_obj || reg_2001.show_bg) reg_2003 = 0; + //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 (reg_2001.show_obj || reg_2001.show_bg) reg_2003 = 0; - //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; + //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); @@ -162,19 +187,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ppur.status.sl = sl; - soam_index = 0; - soam_m_index = 0; - soam_m_index_aux = 0; - oam_index_aux = 0; - oam_index = 0; - o_bug = 0; - is_even_cycle = true; - sprite_eval_write = true; - sprite_zero_go = false; - if (sprite_zero_in_range) - sprite_zero_go = true; + soam_index = 0; + soam_m_index = 0; + soam_m_index_aux = 0; + oam_index_aux = 0; + oam_index = 0; + o_bug = 0; + is_even_cycle = true; + sprite_eval_write = true; + sprite_zero_go = false; + if (sprite_zero_in_range) + sprite_zero_go = true; - sprite_zero_in_range = false; + sprite_zero_in_range = false; yp = sl - 1; ppuphase = PPUPHASE.BG; @@ -182,9 +207,9 @@ 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) + //ok, we're also going to draw here. + //unless we're on the first dummy scanline + if (sl != 0) { //the main scanline rendering loop: //32 times, we will fetch a tile and then render 8 pixels. @@ -196,114 +221,116 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES int target = yp_shift + xstart; int rasterpos = xstart; - spriteHeight = reg_2000.obj_size_16 ? 16 : 8; + spriteHeight = reg_2000.obj_size_16 ? 16 : 8; - //check all the conditions that can cause things to render in these 8px - bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost); - bool renderbgnow; + //check all the conditions that can cause things to render in these 8px + bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost); + bool renderbgnow; 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; + ////////////////////////////////////////////////// + //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; - // 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==64) - { - oam_index_aux = 0; - oam_index = 0; - sprite_eval_write = false; - } + // 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 == 64) + { + oam_index_aux = 0; + oam_index = 0; + sprite_eval_write = false; + } - if (is_even_cycle) - { - read_value = OAM[oam_index * 4+soam_m_index]; + if (is_even_cycle) + { + read_value = OAM[oam_index * 4 + soam_m_index]; - if (oam_index_aux>63) - oam_index_aux = 63; - - read_value_aux = OAM[oam_index_aux * 4 + soam_m_index_aux]; - } - else if (sprite_eval_write) - { - if (soam_index >= 8) - { - // this code mirrors sprite overflow bug behaviour - // see http://wiki.nesdev.com/w/index.php/PPU_sprite_evaluation - if (yp >= read_value && yp < read_value + spriteHeight && reg_2001.PPUON) - { - Reg2002_objoverflow = true; - } - else - { - soam_m_index++; - oam_index++; - if (soam_m_index == 4) - soam_m_index = 0; - - } + if (oam_index_aux > 63) + oam_index_aux = 63; - } - - //look for sprites - soam[soam_index * 4] = OAM[oam_index_aux * 4]; - if (yp >= read_value_aux && yp < read_value_aux + spriteHeight && soam_m_index_aux == 0) - { - //a flag gets set if sprite zero is in range - if (oam_index_aux == 0) - sprite_zero_in_range = true; + read_value_aux = OAM[oam_index_aux * 4 + soam_m_index_aux]; + } + else if (sprite_eval_write) + { + if (soam_index >= 8) + { + // this code mirrors sprite overflow bug behaviour + // see http://wiki.nesdev.com/w/index.php/PPU_sprite_evaluation + if (yp >= read_value && yp < read_value + spriteHeight && reg_2001.PPUON) + { + Reg2002_objoverflow = true; + } + else + { + soam_m_index++; + oam_index++; + if (soam_m_index == 4) + soam_m_index = 0; - soam_m_index_aux++; + } - } else if (soam_m_index_aux > 0 && soam_m_index_aux < 4) - { - soam[soam_index * 4 + soam_m_index_aux] = OAM[oam_index_aux * 4 + soam_m_index_aux]; - soam_m_index_aux++; - if (soam_m_index_aux == 4) - { - oam_index_aux++; - soam_index++; - soam_m_index_aux = 0; - } - } else - { - oam_index_aux++; - } - - if (soam_index<8) - { - soam_m_index = soam_m_index_aux; - oam_index = oam_index_aux; - } - - - } + } - } + //look for sprites + soam[soam_index * 4] = OAM[oam_index_aux * 4]; + if (yp >= read_value_aux && yp < read_value_aux + spriteHeight && soam_m_index_aux == 0) + { + //a flag gets set if sprite zero is in range + if (oam_index_aux == 0) + sprite_zero_in_range = true; + + soam_m_index_aux++; + + } + else if (soam_m_index_aux > 0 && soam_m_index_aux < 4) + { + soam[soam_index * 4 + soam_m_index_aux] = OAM[oam_index_aux * 4 + soam_m_index_aux]; + soam_m_index_aux++; + if (soam_m_index_aux == 4) + { + oam_index_aux++; + soam_index++; + soam_m_index_aux = 0; + } + } + else + { + oam_index_aux++; + } + + if (soam_index < 8) + { + soam_m_index = soam_m_index_aux; + oam_index = oam_index_aux; + } + + + } + + } ////////////////////////////////////////////////// //Sprite Evaluation End ////////////////////////////////////////////////// - renderbgnow = reg_2001.show_bg && (xt > 0 || reg_2001.show_bg_leftmost); - + //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 Read_bgdata(xp, ref bgdata[xt + 2]); + renderbgnow = reg_2001.show_bg && (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; @@ -341,9 +368,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES 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 - - //look for a sprite to be drawn - bool havepixel = false; + + //look for a sprite to be drawn + bool havepixel = false; for (int s = 0; s < soam_index_prev; s++) { int x = t_oam[s].oam_x; @@ -354,23 +381,23 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES int spixel = t_oam[s].patterns_0 & 1; spixel |= (t_oam[s].patterns_1 & 1) << 1; - //shift down the patterns so the next pixel is in the LSB - t_oam[s].patterns_0 >>= 1; - t_oam[s].patterns_1 >>= 1; + //shift down the patterns so the next pixel is in the LSB + t_oam[s].patterns_0 >>= 1; + t_oam[s].patterns_1 >>= 1; - //bail out if we already have a pixel from a higher priority sprite. - //notice that we continue looping anyway, so that we can shift down the patterns - //transparent pixel bailout - if (!renderspritenow || havepixel || spixel == 0) continue; + //bail out if we already have a pixel from a higher priority sprite. + //notice that we continue looping anyway, so that we can shift down the patterns + //transparent pixel bailout + if (!renderspritenow || havepixel || spixel == 0) continue; havepixel = true; - + //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 && reg_2001.show_bg && reg_2001.show_obj); + Reg2002_objhit |= (sprite_zero_go && s == 0 && pixel != 0 && rasterpos < 255 && reg_2001.show_bg && reg_2001.show_obj); //priority handling, if in front of BG: bool drawsprite = !(((t_oam[s].oam_attr & 0x20) != 0) && ((pixel & 3) != 0)); @@ -379,41 +406,45 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //bring in the palette bits and palettize spixel |= (t_oam[s].oam_attr & 3) << 2; //save it for use in the framebuffer - pixelcolor = PALRAM[0x10 + spixel]; + pixelcolor = PALRAM[0x10 + spixel]; } } //rasterpos in sprite range } //oamcount loop - if (reg_2001.color_disable) - pixelcolor &= 0x30; - xbuf[target] = PaletteAdjustPixel(pixelcolor); - + /* + if (reg_2001.color_disable) + pixelcolor &= 0x30; + xbuf[target] = PaletteAdjustPixel(pixelcolor); + */ + pipeline(pixelcolor, target); target++; } //loop across 8 pixels } //loop across 32 tiles + pipeline(0, 256); } else for (int xt = 0; xt < 32; xt++) - Read_bgdata(ref bgdata[xt + 2]); + Read_bgdata(ref bgdata[xt + 2]); - // normally only 8 sprites are allowed, but with a particular setting we can have more then that - soam_index_prev = soam_index; - if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites) - soam_index_prev = 8; + // normally only 8 sprites are allowed, but with a particular setting we can have more then that + soam_index_prev = soam_index; + if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites) + soam_index_prev = 8; ppuphase = PPUPHASE.OBJ; - spriteHeight = reg_2000.obj_size_16 ? 16 : 8; + spriteHeight = reg_2000.obj_size_16 ? 16 : 8; - // if there are less then 8 evaluated sprites, we still process 8 sprites - int bound; + // if there are less then 8 evaluated sprites, we still process 8 sprites + int bound; - if (soam_index_prev>8) - { - bound = soam_index_prev; - } else - { - bound = 8; - } + if (soam_index_prev > 8) + { + bound = soam_index_prev; + } + else + { + bound = 8; + } for (int s = 0; s < bound; s++) { @@ -423,14 +454,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //this could be handy for the debugging tools also bool realSprite = (s < 8); bool junksprite = (!reg_2001.PPUON); - bool extra_sprite = (s >= 8); + bool extra_sprite = (s >= 8); - 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]; + 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; + int line = yp - t_oam[s].oam_y; if ((t_oam[s].oam_attr & 0x80) != 0) //vflip line = spriteHeight - line - 1; @@ -461,85 +492,91 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (sl == 0 && ppur.status.cycle == 304) { runppu(1); - read_value = t_oam[s].oam_y; - if (reg_2001.PPUON) ppur.install_latches(); + pipeline(0, 257); // last pipeline call option 1 of 3 + + read_value = t_oam[s].oam_y; + if (reg_2001.PPUON) ppur.install_latches(); runppu(1); - read_value = t_oam[s].oam_ind; - garbage_todo = 0; + read_value = t_oam[s].oam_ind; + garbage_todo = 0; } if ((sl != 0) && ppur.status.cycle == 256) { runppu(1); - read_value = t_oam[s].oam_y; - //at 257: 3d world runner is ugly if we do this at 256 - if (reg_2001.PPUON) ppur.install_h_latches(); + pipeline(0, 257); // last pipeline call option 2 of 3 + read_value = t_oam[s].oam_y; + //at 257: 3d world runner is ugly if we do this at 256 + if (reg_2001.PPUON) ppur.install_h_latches(); runppu(1); - read_value = t_oam[s].oam_ind; - garbage_todo = 0; + read_value = t_oam[s].oam_ind; + garbage_todo = 0; + } + } + if (realSprite) + { + for (int i = 0; i < garbage_todo; i++) + { + runppu(1); + + if (i == 0) + { + pipeline(0, 257); // last pipeline call option 3 of 3 + read_value = t_oam[s].oam_y; + } + else + { + read_value = t_oam[s].oam_ind; + } } } - if (realSprite) - { - for (int i=0;i