NesHawk: Single Tick PPU
Should allow for breaking out into a debugger.
This commit is contained in:
parent
6f021653aa
commit
40ec613982
|
@ -366,7 +366,43 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
VS_coin_inserted &= 1;
|
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)
|
if (lagged)
|
||||||
{
|
{
|
||||||
_lagcount++;
|
_lagcount++;
|
||||||
|
@ -385,6 +421,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
num_cheats = 0;
|
num_cheats = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool FrameGo;
|
||||||
|
|
||||||
//PAL:
|
//PAL:
|
||||||
//0 15 30 45 60 -> 12 27 42 57 -> 9 24 39 54 -> 6 21 36 51 -> 3 18 33 48 -> 0
|
//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
|
//sequence of ppu clocks per cpu clock: 3,3,3,3,4
|
||||||
|
|
|
@ -194,7 +194,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
}
|
}
|
||||||
|
|
||||||
//state
|
//state
|
||||||
int ppudead; //measured in frames
|
public int ppudead; //measured in frames
|
||||||
bool idleSynch;
|
bool idleSynch;
|
||||||
int NMI_PendingInstructions;
|
int NMI_PendingInstructions;
|
||||||
byte PPUGenLatch;
|
byte PPUGenLatch;
|
||||||
|
@ -253,6 +253,51 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
ser.Sync("xbuf", ref xbuf, false);
|
ser.Sync("xbuf", ref xbuf, false);
|
||||||
|
|
||||||
ser.Sync("_totalCycles", ref _totalCycles);
|
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()
|
public void Reset()
|
||||||
|
@ -264,11 +309,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
ppu_open_bus_decay_timer = new int[8];
|
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
|
//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;
|
race_2006 = false;
|
||||||
if (install_2006>0)
|
if (install_2006>0)
|
||||||
{
|
{
|
||||||
|
@ -349,8 +393,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
{
|
{
|
||||||
nes.Board.ClockPPU();
|
nes.Board.ClockPPU();
|
||||||
}
|
}
|
||||||
}
|
_totalCycles += 1;
|
||||||
_totalCycles += x;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
public byte pt_0, pt_1;
|
public byte pt_0, pt_1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BGDataRecord[] bgdata = new BGDataRecord[34];
|
||||||
|
|
||||||
public short[] xbuf = new short[256 * 240];
|
public short[] xbuf = new short[256 * 240];
|
||||||
|
|
||||||
// values here are used in sprite evaluation
|
// 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 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 reg_2001_color_disable_latch; // the value used here is taken
|
||||||
public bool ppu_was_on;
|
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
|
// installing vram address is delayed after second write to 2006, set this up here
|
||||||
public int install_2006;
|
public int install_2006;
|
||||||
|
@ -62,30 +64,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
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);
|
|
||||||
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
|
// attempt to emulate graphics pipeline behaviour
|
||||||
// experimental
|
// experimental
|
||||||
|
@ -151,48 +129,77 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
} //switch(cycle)
|
} //switch(cycle)
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void FrameAdvance()
|
// these are states for the ppu incrementer
|
||||||
{
|
public bool do_vbl;
|
||||||
BGDataRecord* bgdata = stackalloc BGDataRecord[34]; //one at the end is junk, it can never be rendered
|
public bool do_active_sl;
|
||||||
|
public bool do_pre_vbl;
|
||||||
|
public int ppu_tick_counter;
|
||||||
|
|
||||||
//262 scanlines
|
int scanline_counter;
|
||||||
if (ppudead != 0)
|
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()
|
||||||
{
|
{
|
||||||
FrameAdvance_ppudead();
|
scanline_counter = 0;
|
||||||
return;
|
ppu_tick_counter = 0;
|
||||||
}
|
|
||||||
|
// These things happen at the start of every frame
|
||||||
|
|
||||||
Reg2002_vblank_active_pending = true;
|
Reg2002_vblank_active_pending = true;
|
||||||
ppuphase = PPUPHASE.VBL;
|
ppuphase = PPUPHASE.VBL;
|
||||||
ppur.status.sl = 241;
|
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.
|
public void TickPPU_VBL()
|
||||||
//NOTE: Not having this here breaks a Super Donkey Kong game.
|
{
|
||||||
//if (PPUON) reg_2003 = 0;
|
if (ppu_tick_counter == 3)
|
||||||
|
{
|
||||||
//this was repeatedly finetuned from the fceux days thrugh the old cpu core and into the new one to pass 05-nmi_timing.nes
|
nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active;
|
||||||
//note that there is still some leniency. for instance, 4,2 will pass in addition to 3,3
|
}
|
||||||
const int delay = 6;
|
else if (ppu_tick_counter == 6)
|
||||||
runppu(3);
|
{
|
||||||
bool nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active;
|
if (nmi_destiny) { nes.cpu.NMI = true; }
|
||||||
runppu(3);
|
|
||||||
if (nmi_destiny) nes.cpu.NMI = true;
|
|
||||||
|
|
||||||
nes.Board.AtVsyncNMI();
|
nes.Board.AtVsyncNMI();
|
||||||
runppu(postNMIlines * kLineTime - delay);
|
}
|
||||||
|
|
||||||
//this seems to happen just before the dummy scanline begins
|
runppu();
|
||||||
|
ppu_tick_counter++;
|
||||||
|
|
||||||
|
if (ppu_tick_counter == postNMIlines * kLineTime)
|
||||||
|
{
|
||||||
Reg2002_objhit = Reg2002_objoverflow = 0;
|
Reg2002_objhit = Reg2002_objoverflow = 0;
|
||||||
Reg2002_vblank_clear_pending = true;
|
Reg2002_vblank_clear_pending = true;
|
||||||
|
|
||||||
idleSynch ^= true;
|
idleSynch ^= true;
|
||||||
|
|
||||||
//render 241 scanlines (including 1 dummy at beginning)
|
do_vbl = false;
|
||||||
for (int sl = 0; sl < 241; sl++)
|
ppu_tick_counter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TickPPU_active()
|
||||||
|
{
|
||||||
|
if (ppu_tick_counter == 0)
|
||||||
{
|
{
|
||||||
ppur.status.cycle = 0;
|
ppur.status.cycle = 0;
|
||||||
|
|
||||||
ppur.status.sl = sl;
|
ppur.status.sl = scanline_counter;
|
||||||
|
|
||||||
spr_true_count = 0;
|
spr_true_count = 0;
|
||||||
soam_index = 0;
|
soam_index = 0;
|
||||||
|
@ -206,12 +213,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
|
|
||||||
sprite_zero_in_range = false;
|
sprite_zero_in_range = false;
|
||||||
|
|
||||||
yp = sl - 1;
|
yp = scanline_counter - 1;
|
||||||
ppuphase = PPUPHASE.BG;
|
ppuphase = PPUPHASE.BG;
|
||||||
|
|
||||||
// "If PPUADDR is not less then 8 when rendering starts, the first 8 bytes in OAM are written to from
|
// "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"
|
// 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++)
|
for (int i = 0; i < 8; i++)
|
||||||
{
|
{
|
||||||
|
@ -222,33 +229,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
if (NTViewCallback != null && yp == NTViewCallback.Scanline) NTViewCallback.Callback();
|
if (NTViewCallback != null && yp == NTViewCallback.Scanline) NTViewCallback.Callback();
|
||||||
if (PPUViewCallback != null && yp == PPUViewCallback.Scanline) PPUViewCallback.Callback();
|
if (PPUViewCallback != null && yp == PPUViewCallback.Scanline) PPUViewCallback.Callback();
|
||||||
|
|
||||||
//ok, we're also going to draw here.
|
// set up intial values to use later
|
||||||
//unless we're on the first dummy scanline
|
yp_shift = yp << 8;
|
||||||
if (sl != 0)
|
xt = 0;
|
||||||
{
|
xp = 0;
|
||||||
//the main scanline rendering loop:
|
|
||||||
//32 times, we will fetch a tile and then render 8 pixels.
|
sprite_eval_cycle = 0;
|
||||||
//two of those tiles were read in the last scanline.
|
|
||||||
int yp_shift = yp << 8;
|
xstart = xt << 3;
|
||||||
for (int xt = 0; xt < 32; xt++)
|
|
||||||
{
|
|
||||||
int xstart = xt << 3;
|
|
||||||
target = yp_shift + xstart;
|
target = yp_shift + xstart;
|
||||||
int rasterpos = xstart;
|
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
|
//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);
|
renderspritenow = show_obj_new && (xt > 0 || reg_2001.show_obj_leftmost);
|
||||||
bool renderbgnow;
|
hit_pending = false;
|
||||||
bool hit_pending = false;
|
|
||||||
|
|
||||||
for (int xp = 0; xp < 8; xp++, rasterpos++)
|
}
|
||||||
|
|
||||||
|
if (ppu_tick_counter < 256)
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////
|
if (scanline_counter != 0)
|
||||||
//Sprite Evaluation Start
|
{
|
||||||
//////////////////////////////////////////////////
|
/////////////////////////////////////////////
|
||||||
if (ppur.status.cycle <= 63 && !is_even_cycle)
|
// Sprite Evaluation End
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
if (sprite_eval_cycle <= 63 && !is_even_cycle)
|
||||||
{
|
{
|
||||||
// the first 64 cycles of each scanline are used to initialize sceondary OAM
|
// 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
|
// the actual effect setting a flag that always returns 0xFF from a OAM read
|
||||||
|
@ -257,7 +265,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
soam[soam_index] = 0xFF;
|
soam[soam_index] = 0xFF;
|
||||||
soam_index++;
|
soam_index++;
|
||||||
}
|
}
|
||||||
if (ppur.status.cycle == 64)
|
if (sprite_eval_cycle == 64)
|
||||||
{
|
{
|
||||||
soam_index = 0;
|
soam_index = 0;
|
||||||
oam_index = reg_2003;
|
oam_index = reg_2003;
|
||||||
|
@ -265,7 +273,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
|
|
||||||
// otherwise, scan through OAM and test if sprites are in range
|
// otherwise, scan through OAM and test if sprites are in range
|
||||||
// if they are, they get copied to the secondary OAM
|
// if they are, they get copied to the secondary OAM
|
||||||
if (ppur.status.cycle >= 64)
|
if (sprite_eval_cycle >= 64)
|
||||||
{
|
{
|
||||||
if (oam_index >= 256)
|
if (oam_index >= 256)
|
||||||
{
|
{
|
||||||
|
@ -371,23 +379,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
|
|
||||||
read_value = soam[0]; //writes change to reads
|
read_value = soam[0]; //writes change to reads
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/////////////////////////////////////////////
|
||||||
|
// Sprite Evaluation End
|
||||||
//////////////////////////////////////////////////
|
/////////////////////////////////////////////
|
||||||
//Sprite Evaluation End
|
|
||||||
//////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//process the current clock's worth of bg data fetching
|
//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
|
//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)
|
|
||||||
Read_bgdata(xp, ref bgdata[xt + 2]);
|
|
||||||
|
|
||||||
runppu(1);
|
|
||||||
|
|
||||||
if (PPUON && xp == 6)
|
if (PPUON && xp == 6)
|
||||||
{
|
{
|
||||||
|
@ -451,11 +455,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
pixelcolor = 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
|
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
|
//check if the pixel has a sprite in it
|
||||||
if (sl_sprites[1, xt * 8 + xp] != 0 && renderspritenow)
|
if (sl_sprites[256 + xt * 8 + xp] != 0 && renderspritenow)
|
||||||
{
|
{
|
||||||
int s = sl_sprites[0, xt * 8 + xp];
|
int s = sl_sprites[xt * 8 + xp];
|
||||||
int spixel = sl_sprites[1, xt * 8 + xp];
|
int spixel = sl_sprites[256 + xt * 8 + xp];
|
||||||
int temp_attr = sl_sprites[2, 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
|
//TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ
|
||||||
//spritehit:
|
//spritehit:
|
||||||
|
@ -480,18 +484,70 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
target++;
|
target++;
|
||||||
|
|
||||||
// clear out previous sprites from scanline buffer
|
// clear out previous sprites from scanline buffer
|
||||||
sl_sprites[0, xt * 8 + xp] = 0;
|
//sl_sprites[xt * 8 + xp] = 0;
|
||||||
sl_sprites[1, xt * 8 + xp] = 0;
|
sl_sprites[256 + xt * 8 + xp] = 0;
|
||||||
sl_sprites[2, xt * 8 + xp] = 0;
|
//sl_sprites[512 + xt * 8 + xp] = 0;
|
||||||
|
|
||||||
|
// end of visible part of the scanline
|
||||||
|
sprite_eval_cycle++;
|
||||||
|
xp++;
|
||||||
|
rasterpos++;
|
||||||
|
|
||||||
} //loop across 8 pixels
|
if (xp == 8)
|
||||||
} //loop across 32 tiles
|
{
|
||||||
|
xp = 0;
|
||||||
|
xt++;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
for (int xt = 0; xt < 32; xt++)
|
{
|
||||||
Read_bgdata(ref bgdata[xt + 2]);
|
// 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
|
// 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
|
// this extra bit takes care of it quickly
|
||||||
soam_index_aux = 8;
|
soam_index_aux = 8;
|
||||||
|
@ -527,22 +583,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
|
|
||||||
spriteHeight = reg_2000.obj_size_16 ? 16 : 8;
|
spriteHeight = reg_2000.obj_size_16 ? 16 : 8;
|
||||||
|
|
||||||
for (int s = 0; s < 8; s++)
|
s = 0;
|
||||||
{
|
ppu_aux_index = 0;
|
||||||
bool junksprite = (!PPUON);
|
|
||||||
|
junksprite = (!PPUON);
|
||||||
|
|
||||||
t_oam[s].oam_y = soam[s * 4];
|
t_oam[s].oam_y = soam[s * 4];
|
||||||
t_oam[s].oam_ind = soam[s * 4 + 1];
|
t_oam[s].oam_ind = soam[s * 4 + 1];
|
||||||
t_oam[s].oam_attr = soam[s * 4 + 2];
|
t_oam[s].oam_attr = soam[s * 4 + 2];
|
||||||
t_oam[s].oam_x = soam[s * 4 + 3];
|
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
|
if ((t_oam[s].oam_attr & 0x80) != 0) //vflip
|
||||||
line = spriteHeight - line - 1;
|
line = spriteHeight - line - 1;
|
||||||
|
|
||||||
int patternNumber = t_oam[s].oam_ind;
|
patternNumber = t_oam[s].oam_ind;
|
||||||
int patternAddress;
|
}
|
||||||
|
|
||||||
|
switch (ppu_aux_index)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
//8x16 sprite handling:
|
//8x16 sprite handling:
|
||||||
if (reg_2000.obj_size_16)
|
if (reg_2000.obj_size_16)
|
||||||
{
|
{
|
||||||
|
@ -559,34 +619,23 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
//so we just need the line offset for the second pattern
|
//so we just need the line offset for the second pattern
|
||||||
patternAddress += line & 7;
|
patternAddress += line & 7;
|
||||||
|
|
||||||
//garbage nametable fetches + scroll resets
|
|
||||||
int garbage_todo = 2;
|
|
||||||
|
|
||||||
ppubus_read(ppur.get_ntread(), true, true);
|
ppubus_read(ppur.get_ntread(), true, true);
|
||||||
|
|
||||||
if (PPUON)
|
|
||||||
{
|
|
||||||
if (sl == 0 && ppur.status.cycle == 304)
|
|
||||||
{
|
|
||||||
|
|
||||||
read_value = t_oam[s].oam_y;
|
read_value = t_oam[s].oam_y;
|
||||||
runppu(1);
|
runppu();
|
||||||
|
break;
|
||||||
if (PPUON) ppur.install_latches();
|
case 1:
|
||||||
|
if (PPUON && scanline_counter == 0 && ppur.status.cycle == 305)
|
||||||
|
{
|
||||||
|
ppur.install_latches();
|
||||||
|
|
||||||
read_value = t_oam[s].oam_ind;
|
read_value = t_oam[s].oam_ind;
|
||||||
runppu(1);
|
runppu();
|
||||||
|
|
||||||
|
|
||||||
garbage_todo = 0;
|
|
||||||
}
|
}
|
||||||
if ((sl != 0) && ppur.status.cycle == 256)
|
else if (PPUON && (scanline_counter != 0) && ppur.status.cycle == 257)
|
||||||
{
|
{
|
||||||
|
|
||||||
read_value = t_oam[s].oam_y;
|
|
||||||
|
|
||||||
runppu(1);
|
|
||||||
|
|
||||||
if (target <= 61441 && target > 0 && s == 0)
|
if (target <= 61441 && target > 0 && s == 0)
|
||||||
{
|
{
|
||||||
pipeline(0, target, 256);
|
pipeline(0, target, 256);
|
||||||
|
@ -596,68 +645,92 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
//at 257: 3d world runner is ugly if we do this at 256
|
//at 257: 3d world runner is ugly if we do this at 256
|
||||||
if (PPUON) ppur.install_h_latches();
|
if (PPUON) ppur.install_h_latches();
|
||||||
read_value = t_oam[s].oam_ind;
|
read_value = t_oam[s].oam_ind;
|
||||||
runppu(1);
|
runppu();
|
||||||
|
|
||||||
if (target <= 61441 && target > 0 && s == 0)
|
if (target <= 61441 && target > 0 && s == 0)
|
||||||
{
|
{
|
||||||
pipeline(0, target, 257); // last pipeline call option 1 of 2
|
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
|
else
|
||||||
read_value = t_oam[s].oam_ind;
|
|
||||||
|
|
||||||
runppu(1);
|
|
||||||
|
|
||||||
if (i == 0)
|
|
||||||
{
|
{
|
||||||
if (target <= 61441 && target > 0 && s == 0)
|
if (target <= 61441 && target > 0 && s == 0)
|
||||||
{
|
{
|
||||||
pipeline(0, target, 256);
|
pipeline(0, target, 256);
|
||||||
target++;
|
target++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
read_value = t_oam[s].oam_ind;
|
||||||
{
|
runppu();
|
||||||
|
|
||||||
if (target <= 61441 && target > 0 && s == 0)
|
if (target <= 61441 && target > 0 && s == 0)
|
||||||
{
|
{
|
||||||
pipeline(0, target, 257); // last pipeline call option 2 of 2
|
pipeline(0, target, 257); // last pipeline call option 2 of 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
ppubus_read(ppur.get_atread(), true, true); //at or nt?
|
ppubus_read(ppur.get_atread(), true, true); //at or nt?
|
||||||
|
|
||||||
read_value = t_oam[s].oam_attr;
|
read_value = t_oam[s].oam_attr;
|
||||||
runppu(1);
|
runppu();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
read_value = t_oam[s].oam_x;
|
read_value = t_oam[s].oam_x;
|
||||||
runppu(1);
|
runppu();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
// if the PPU is off, we don't put anything on the bus
|
// if the PPU is off, we don't put anything on the bus
|
||||||
if (junksprite)
|
if (junksprite)
|
||||||
{
|
{
|
||||||
ppubus_read(patternAddress, true, false);
|
ppubus_read(patternAddress, true, false);
|
||||||
ppubus_read(patternAddress, true, false);
|
runppu();
|
||||||
runppu(kFetchTime * 2);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int addr = patternAddress;
|
temp_addr = patternAddress;
|
||||||
t_oam[s].patterns_0 = ppubus_read(addr, true, true);
|
t_oam[s].patterns_0 = ppubus_read(temp_addr, true, true);
|
||||||
read_value = t_oam[s].oam_x;
|
read_value = t_oam[s].oam_x;
|
||||||
runppu(kFetchTime);
|
runppu();
|
||||||
|
}
|
||||||
addr += 8;
|
break;
|
||||||
t_oam[s].patterns_1 = ppubus_read(addr, true, true);
|
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;
|
read_value = t_oam[s].oam_x;
|
||||||
runppu(kFetchTime);
|
runppu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
// if the PPU is off, we don't put anything on the bus
|
||||||
|
if (junksprite)
|
||||||
|
{
|
||||||
|
runppu();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runppu();
|
||||||
|
|
||||||
// hflip
|
// hflip
|
||||||
if ((t_oam[s].oam_attr & 0x40) == 0)
|
if ((t_oam[s].oam_attr & 0x40) == 0)
|
||||||
|
@ -676,7 +749,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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
|
// 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
|
// this saves quite a bit of processing compared to checking each pixel
|
||||||
|
|
||||||
|
@ -685,25 +763,43 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
int temp_x = t_oam[s].oam_x;
|
int temp_x = t_oam[s].oam_x;
|
||||||
for (int i = 0; (temp_x + i) < 256 && i < 8; i++)
|
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))
|
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;
|
int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0;
|
||||||
spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0);
|
spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0);
|
||||||
|
|
||||||
sl_sprites[0, temp_x + i] = (byte)s;
|
sl_sprites[temp_x + i] = (byte)s;
|
||||||
sl_sprites[1, temp_x + i] = (byte)spixel;
|
sl_sprites[256 + temp_x + i] = (byte)spixel;
|
||||||
sl_sprites[2, temp_x + i] = t_oam[s].oam_attr;
|
sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // sprite pattern fetch loop
|
ppu_aux_index = 0;
|
||||||
|
s++;
|
||||||
|
|
||||||
//now do the same for extra sprites, but without any cycles run
|
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)
|
if (soam_index_aux > 8)
|
||||||
{
|
{
|
||||||
for (int s = 8; s < soam_index_aux; s++)
|
for (int s = 8; s < soam_index_aux; s++)
|
||||||
|
@ -765,62 +861,125 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
int temp_x = t_oam[s].oam_x;
|
int temp_x = t_oam[s].oam_x;
|
||||||
for (int i = 0; (temp_x + i) < 256 && i < 8; i++)
|
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))
|
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;
|
int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0;
|
||||||
spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0);
|
spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0);
|
||||||
|
|
||||||
sl_sprites[0, temp_x + i] = (byte)s;
|
sl_sprites[temp_x + i] = (byte)s;
|
||||||
sl_sprites[1, temp_x + i] = (byte)spixel;
|
sl_sprites[256 + temp_x + i] = (byte)spixel;
|
||||||
sl_sprites[2, temp_x + i] = t_oam[s].oam_attr;
|
sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // sprite pattern fetch loop
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ppuphase = PPUPHASE.BG;
|
}
|
||||||
|
}
|
||||||
// fetch BG: two tiles for next line
|
}
|
||||||
for (int xt = 0; xt < 2; xt++)
|
else
|
||||||
{
|
{
|
||||||
Read_bgdata(ref bgdata[xt]);
|
if (ppu_tick_counter == 320)
|
||||||
|
{
|
||||||
|
ppuphase = PPUPHASE.BG;
|
||||||
|
xt = 0;
|
||||||
|
xp = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this sequence is tuned to pass 10-even_odd_timing.nes
|
if (ppu_tick_counter < 336)
|
||||||
runppu(4);
|
{
|
||||||
|
// if scanline is the pre-render line, we just read BG data
|
||||||
|
Read_bgdata(xp, ref bgdata[xt]);
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
bool evenOddDestiny = PPUON;
|
bool evenOddDestiny = PPUON;
|
||||||
|
|
||||||
// After memory access 170, the PPU simply rests for 4 cycles (or the
|
// After memory access 170, the PPU simply rests for 4 cycles (or the
|
||||||
// equivelant of half a memory access cycle) before repeating the whole
|
// equivelant of half a memory access cycle) before repeating the whole
|
||||||
// pixel/scanline rendering process. If the scanline being rendered is the very
|
// 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.
|
// first one on every second frame, then this delay simply doesn't exist.
|
||||||
if (sl == 0 && idleSynch && evenOddDestiny && chopdot)
|
if (scanline_counter == 0 && idleSynch && evenOddDestiny && chopdot)
|
||||||
{ }
|
{}
|
||||||
else
|
else
|
||||||
runppu(1);
|
{ runppu(); }
|
||||||
} // scanline loop
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ppur.status.sl = 241;
|
ppu_tick_counter++;
|
||||||
|
if (ppu_tick_counter == 341)
|
||||||
//idle for pre NMI lines
|
|
||||||
runppu(preNMIlines * kLineTime);
|
|
||||||
} //FrameAdvance
|
|
||||||
|
|
||||||
void FrameAdvance_ppudead()
|
|
||||||
{
|
{
|
||||||
|
ppu_tick_counter = 0;
|
||||||
|
scanline_counter++;
|
||||||
|
|
||||||
|
if (scanline_counter == 241)
|
||||||
|
{
|
||||||
|
do_active_sl = false;
|
||||||
|
ppur.status.sl = 241;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TickPPU_preVBL()
|
||||||
|
{
|
||||||
|
runppu();
|
||||||
|
|
||||||
|
|
||||||
|
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
|
//not quite emulating all the NES power up behavior
|
||||||
//since it is known that the NES ignores writes to some
|
//since it is known that the NES ignores writes to some
|
||||||
//register before around a full frame, but no games
|
//register before around a full frame, but no games
|
||||||
//should write to those regs during that time, it needs
|
//should write to those regs during that time, it needs
|
||||||
//to wait for vblank
|
//to wait for vblank
|
||||||
|
public void NewDeadPPU()
|
||||||
|
{
|
||||||
|
runppu();
|
||||||
|
|
||||||
runppu(241 * kLineTime - 3);// -8*3);
|
ppu_tick_counter++;
|
||||||
|
if (ppu_tick_counter == 241 * kLineTime - 3)
|
||||||
|
{
|
||||||
ppudead--;
|
ppudead--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue