NESHawk: Optimizations and Audio changes

This commit is contained in:
alyosha-tas 2019-02-18 14:52:17 -06:00
parent ee45e3114e
commit 469fc4836f
7 changed files with 297 additions and 340 deletions

View File

@ -74,7 +74,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
};
public sealed class PulseUnit
{
public PulseUnit(APU apu, int unit) { this.unit = unit; this.apu = apu; }
@ -968,6 +967,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ser.Sync("sequencer_irq_flag", ref sequencer_irq_flag);
ser.Sync("len_clock_active", ref len_clock_active);
ser.Sync("oldmix", ref oldmix);
ser.Sync("cart_sound", ref cart_sound);
ser.Sync("old_cart_sound", ref old_cart_sound);
pulse[0].SyncState(ser);
pulse[1].SyncState(ser);
triangle.SyncState(ser);
@ -1259,127 +1262,118 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int pending_length_change;
public void RunOne(bool read)
public void RunOneFirst()
{
if (read)
{
pulse[0].Run();
pulse[1].Run();
triangle.Run();
noise.Run();
dmc.Run();
pulse[0].len_halt = false;
pulse[1].len_halt = false;
noise.len_halt = false;
pulse[0].Run();
pulse[1].Run();
triangle.Run();
noise.Run();
dmc.Run();
}
else
{
if (pending_length_change>0)
{
pending_length_change--;
if (pending_length_change==0)
{
dmc.sample_length--;
}
}
EmitSample();
// we need to predict if there will be a length clock here, because the sequencer ticks last, but the
// timer reload shouldn't happen if length clock and write happen simultaneously
// I'm not sure if we can avoid this by simply processing the sequencer first
// but at the moment that would break everything, so this is good enough for now
if (sequencer_counter == (sequencer_lut[0][1] - 1) ||
(sequencer_counter == sequencer_lut[0][3] - 2 && sequencer_mode==0) ||
(sequencer_counter == sequencer_lut[1][4] - 2 && sequencer_mode == 1))
{
len_clock_active = true;
}
// handle writes
// notes: this set up is a bit convoluded at the moment, mainly because APU behaviour is not entirely understood
// in partiuclar, there are several clock pulses affecting the APU, and when new written are latched is not known in detail
// the current code simply matches known behaviour
if (pending_reg != -1)
{
if (pending_reg == 0x4015 || pending_reg == 0x4015 || pending_reg == 0x4003 || pending_reg==0x4007)
{
_WriteReg(pending_reg, pending_val);
pending_reg = -1;
}
else if (dmc.timer%2==0)
{
_WriteReg(pending_reg, pending_val);
pending_reg = -1;
}
}
len_clock_active = false;
sequencer_tick();
sequencer_write_tick(seq_val);
doing_tick_quarter = false;
if (sequencer_irq_assert>0) {
sequencer_irq_assert--;
if (sequencer_irq_assert==0)
{
sequencer_irq = true;
}
}
SyncIRQ();
nes._irq_apu = irq_pending;
// since the units run concurrently, the APU frame sequencer is ran last because
// it can change the ouput values of the pulse/triangle channels
// we want the changes to affect it on the *next* cycle.
if (sequencer_irq_flag == false)
sequencer_irq = false;
if (DebugCallbackDivider != 0)
{
if (DebugCallbackTimer == 0)
{
if (DebugCallback != null)
DebugCallback();
DebugCallbackTimer = DebugCallbackDivider;
}
else DebugCallbackTimer--;
}
}
pulse[0].len_halt = false;
pulse[1].len_halt = false;
noise.len_halt = false;
}
public struct Delta
public void RunOneLast()
{
public uint time;
public int value;
public Delta(uint time, int value)
if (pending_length_change > 0)
{
this.time = time;
this.value = value;
pending_length_change--;
if (pending_length_change == 0)
{
dmc.sample_length--;
}
}
// we need to predict if there will be a length clock here, because the sequencer ticks last, but the
// timer reload shouldn't happen if length clock and write happen simultaneously
// I'm not sure if we can avoid this by simply processing the sequencer first
// but at the moment that would break everything, so this is good enough for now
if (sequencer_counter == (sequencer_lut[0][1] - 1) ||
(sequencer_counter == sequencer_lut[0][3] - 2 && sequencer_mode == 0) ||
(sequencer_counter == sequencer_lut[1][4] - 2 && sequencer_mode == 1))
{
len_clock_active = true;
}
// handle writes
// notes: this set up is a bit convoluded at the moment, mainly because APU behaviour is not entirely understood
// in partiuclar, there are several clock pulses affecting the APU, and when new written are latched is not known in detail
// the current code simply matches known behaviour
if (pending_reg != -1)
{
if (pending_reg == 0x4015 || pending_reg == 0x4015 || pending_reg == 0x4003 || pending_reg == 0x4007)
{
_WriteReg(pending_reg, pending_val);
pending_reg = -1;
}
else if (dmc.timer % 2 == 0)
{
_WriteReg(pending_reg, pending_val);
pending_reg = -1;
}
}
len_clock_active = false;
sequencer_tick();
sequencer_write_tick(seq_val);
doing_tick_quarter = false;
if (sequencer_irq_assert > 0)
{
sequencer_irq_assert--;
if (sequencer_irq_assert == 0)
{
sequencer_irq = true;
}
}
SyncIRQ();
nes._irq_apu = irq_pending;
// since the units run concurrently, the APU frame sequencer is ran last because
// it can change the ouput values of the pulse/triangle channels
// we want the changes to affect it on the *next* cycle.
if (sequencer_irq_flag == false)
sequencer_irq = false;
if (DebugCallbackDivider != 0)
{
if (DebugCallbackTimer == 0)
{
if (DebugCallback != null)
DebugCallback();
DebugCallbackTimer = DebugCallbackDivider;
}
else DebugCallbackTimer--;
}
}
public List<Delta> dlist = new List<Delta>();
/// <summary>only call in board.ClockCPU()</summary>
/// <param name="value"></param>
public void ExternalQueue(int value)
{
// sampleclock is incremented right before board.ClockCPU()
dlist.Add(new Delta(sampleclock - 1, value));
cart_sound = value + old_cart_sound;
if (cart_sound != old_cart_sound)
{
recalculate = true;
old_cart_sound = cart_sound;
}
}
public uint sampleclock = 0;
int oldmix = 0;
int cart_sound = 0;
int old_cart_sound = 0;
void EmitSample()
public int EmitSample()
{
if (recalculate)
{
@ -1391,16 +1385,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int s_noise = noise.sample;
int s_dmc = dmc.sample;
// int s_ext = 0; //gamepak
/*
if (!EnableSquare1) s_pulse0 = 0;
if (!EnableSquare2) s_pulse1 = 0;
if (!EnableTriangle) s_tri = 0;
if (!EnableNoise) s_noise = 0;
if (!EnableDMC) s_dmc = 0;
*/
// more properly correct
float pulse_out, tnd_out;
if (s_pulse0 == 0 && s_pulse1 == 0)
@ -1412,13 +1396,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
float output = pulse_out + tnd_out;
// output = output * 2 - 1;
// this needs to leave enough headroom for straying DC bias due to the DMC unit getting stuck outputs. smb3 is bad about that.
int mix = (int)(20000 * output * (1 + m_vol/5));
int mix = (int)(20000 * output * (1 + m_vol/5)) + cart_sound;
dlist.Add(new Delta(sampleclock, mix - oldmix));
oldmix = mix;
return mix;
}
sampleclock++;
return oldmix;
}
}
}

View File

@ -11,7 +11,7 @@ using BizHawk.Emulation.Cores.Components.M6502;
namespace BizHawk.Emulation.Cores.Nintendo.NES
{
public partial class NES : IEmulator, ICycleTiming
public partial class NES : IEmulator, ISoundProvider, ICycleTiming
{
//hardware/state
public MOS6502X<CpuLink> cpu;
@ -72,92 +72,65 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
return Board;
}
#region Audio
BlipBuffer blip = new BlipBuffer(4096);
const int blipbuffsize = 4096;
public int old_s = 0;
public bool CanProvideAsync { get { return false; } }
public void SetSyncMode(SyncSoundMode mode)
{
if (mode != SyncSoundMode.Sync)
{
throw new NotSupportedException("Only sync mode is supported");
}
}
public void GetSamplesAsync(short[] samples)
{
throw new NotSupportedException("Async not supported");
}
public SyncSoundMode SyncMode
{
get { return SyncSoundMode.Sync; }
}
public void Dispose()
{
if (magicSoundProvider != null)
magicSoundProvider.Dispose();
magicSoundProvider = null;
if (blip != null)
{
blip.Dispose();
blip = null;
}
}
public class MagicSoundProvider : ISoundProvider, IDisposable
public void GetSamplesSync(out short[] samples, out int nsamp)
{
BlipBuffer blip;
NES nes;
blip.EndFrame(apu.sampleclock);
apu.sampleclock = 0;
const int blipbuffsize = 4096;
nsamp = blip.SamplesAvailable();
samples = new short[nsamp * 2];
public MagicSoundProvider(NES nes, uint infreq)
{
this.nes = nes;
blip.ReadSamples(samples, nsamp, true);
// duplicate to stereo
for (int i = 0; i < nsamp * 2; i += 2)
samples[i + 1] = samples[i];
blip = new BlipBuffer(blipbuffsize);
blip.SetRates(infreq, 44100);
//var actualMetaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
//1.789773mhz NTSC
//resampler = new Sound.Utilities.SpeexResampler(2, infreq, 44100 * APU.DECIMATIONFACTOR, infreq, 44100, actualMetaspu.buffer.enqueue_samples);
//output = new Sound.Utilities.DCFilter(actualMetaspu);
}
public bool CanProvideAsync
{
get { return false; }
}
public SyncSoundMode SyncMode
{
get { return SyncSoundMode.Sync; }
}
public void SetSyncMode(SyncSoundMode mode)
{
if (mode != SyncSoundMode.Sync)
{
throw new NotSupportedException("Only sync mode is supported");
}
}
public void GetSamplesAsync(short[] samples)
{
throw new NotSupportedException("Async not supported");
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
//Console.WriteLine("ASync: {0}", nes.apu.dlist.Count);
foreach (var d in nes.apu.dlist)
blip.AddDelta(d.time, d.value);
nes.apu.dlist.Clear();
blip.EndFrame(nes.apu.sampleclock);
nes.apu.sampleclock = 0;
nsamp = blip.SamplesAvailable();
samples = new short[nsamp * 2];
blip.ReadSamples(samples, nsamp, true);
// duplicate to stereo
for (int i = 0; i < nsamp * 2; i += 2)
samples[i + 1] = samples[i];
nes.Board.ApplyCustomAudio(samples);
}
public void DiscardSamples()
{
nes.apu.dlist.Clear();
nes.apu.sampleclock = 0;
}
public void Dispose()
{
if (blip != null)
{
blip.Dispose();
blip = null;
}
}
Board.ApplyCustomAudio(samples);
}
public MagicSoundProvider magicSoundProvider;
public void DiscardSamples()
{
blip.Clear();
apu.sampleclock = 0;
}
#endregion
public void HardReset()
{
@ -243,8 +216,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
default:
throw new Exception("Unknown displaytype!");
}
if (magicSoundProvider == null)
magicSoundProvider = new MagicSoundProvider(this, (uint)cpuclockrate);
blip.SetRates((uint)cpuclockrate, 44100);
BoardSystemHardReset();
@ -563,7 +536,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
/////////////////////////////
// dmc dma end
/////////////////////////////
apu.RunOne(true);
apu.RunOneFirst();
if (cpu.RDY && !IRQ_delay)
{
@ -576,7 +549,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
cpu.ExecuteOne();
apu.RunOne(false);
Board.ClockCPU();
int s = apu.EmitSample();
if (s != old_s)
{
blip.AddDelta(apu.sampleclock, s - old_s);
old_s = s;
}
apu.sampleclock++;
apu.RunOneLast();
if (ppu.double_2007_read > 0)
ppu.double_2007_read--;
@ -592,8 +576,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
cpu.RDY = true;
IRQ_delay = true;
}
Board.ClockCPU();
}
public byte ReadReg(int addr)

View File

@ -61,6 +61,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ser.Sync("sprdma_countdown", ref sprdma_countdown);
ser.Sync("cpu_deadcounter", ref cpu_deadcounter);
ser.Sync("old_s", ref old_s);
//oam related
ser.Sync("Oam_Dma_Index", ref oam_dma_index);
ser.Sync("Oam_Dma_Exec", ref oam_dma_exec);

View File

@ -62,7 +62,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
Tracer = new TraceBuffer { Header = cpu.TraceHeader };
ser.Register<ITraceable>(Tracer);
ser.Register<IVideoProvider>(videoProvider);
ser.Register<ISoundProvider>(magicSoundProvider);
ser.Register<ISoundProvider>(this);
if (Board is BANDAI_FCG_1)
{

View File

@ -298,7 +298,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ser.Sync("xstart", ref xstart);
ser.Sync("rasterpos", ref rasterpos);
ser.Sync("renderspritenow", ref renderspritenow);
ser.Sync("renderbgnow", ref renderbgnow);
ser.Sync("s", ref s);
ser.Sync("ppu_aux_index", ref ppu_aux_index);
ser.Sync("junksprite", ref junksprite);
@ -350,10 +349,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
void runppu()
{
//run one ppu cycle at a time so we can interact with the ppu and clockPPU at high granularity
if (install_2006>0)
//run one ppu cycle at a time so we can interact with the ppu and clockPPU at high granularity
if (install_2006 > 0)
{
install_2006--;
if (install_2006==0)
@ -386,7 +383,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ppur.status.cycle++;
is_even_cycle = !is_even_cycle;
if (PPUON && ppur.status.cycle >= 257 && ppur.status.cycle <= 320 && ppur.status.sl <= 240)
if (ppur.status.cycle >= 257 && ppur.status.cycle <= 320 && ppur.status.sl <= 240 && PPUON)
{
reg_2003 = 0;
}
@ -417,18 +414,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
}
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();

View File

@ -78,13 +78,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
pixelcolor_latch_1 = pixelcolor;
}
void Read_bgdata(int cycle, ref BGDataRecord bgdata)
void Read_bgdata(int cycle, int i)
{
switch (cycle)
{
case 0:
ppu_addr_temp = ppur.get_ntread();
bgdata.nt = ppubus_read(ppu_addr_temp, true, true);
bgdata[i].nt = ppubus_read(ppu_addr_temp, true, true);
break;
case 1:
break;
@ -98,20 +98,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if ((ppur.ht & 2) != 0) at >>= 2;
at &= 0x03;
at <<= 2;
bgdata.at = at;
bgdata[i].at = at;
break;
}
case 3:
break;
case 4:
ppu_addr_temp = ppur.get_ptread(bgdata.nt);
bgdata.pt_0 = ppubus_read(ppu_addr_temp, true, true);
ppu_addr_temp = ppur.get_ptread(bgdata[i].nt);
bgdata[i].pt_0 = ppubus_read(ppu_addr_temp, true, true);
break;
case 5:
break;
case 6:
ppu_addr_temp |= 8;
bgdata.pt_1 = ppubus_read(ppu_addr_temp, true, true);
bgdata[i].pt_1 = ppubus_read(ppu_addr_temp, true, true);
break;
case 7:
break;
@ -134,7 +134,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int xstart;
int rasterpos;
bool renderspritenow;
bool renderbgnow;
int s;
int ppu_aux_index;
bool junksprite;
@ -149,8 +148,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ppur.status.cycle = 0;
// These things happen at the start of every frame
//Reg2002_vblank_active = true;
//Reg2002_vblank_active_pending = true;
ppuphase = PPU_PHASE_VBL;
bgdata = new BGDataRecord[34];
}
@ -188,6 +185,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (ppur.status.cycle == 341)
{
if (Reg2002_vblank_clear_pending)
{
Reg2002_vblank_active = 0;
Reg2002_vblank_clear_pending = false;
}
ppur.status.cycle = 0;
ppur.status.sl++;
if (ppur.status.sl == 241 + preNMIlines + postNMIlines)
@ -201,100 +204,97 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public void TickPPU_active()
{
if (ppur.status.cycle == 0)
{
ppur.status.cycle = 0;
spr_true_count = 0;
soam_index = 0;
soam_m_index = 0;
oam_index_aux = 0;
oam_index = 0;
is_even_cycle = true;
sprite_eval_write = true;
sprite_zero_go = sprite_zero_in_range;
sprite_zero_in_range = false;
yp = ppur.status.sl - 1;
ppuphase = PPU_PHASE_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 (ppur.status.sl == 0 && PPUON && reg_2003 >= 8 && region == Region.NTSC)
{
for (int i = 0; i < 8; i++)
{
OAM[i] = OAM[(reg_2003 & 0xF8) + i];
}
}
if (NTViewCallback != null && yp == NTViewCallback.Scanline) NTViewCallback.Callback();
if (PPUViewCallback != null && yp == PPUViewCallback.Scanline) PPUViewCallback.Callback();
// 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);
}
if (ppur.status.cycle < 256)
{
if (ppur.status.cycle == 0)
{
ppur.status.cycle = 0;
spr_true_count = 0;
soam_index = 0;
soam_m_index = 0;
oam_index_aux = 0;
oam_index = 0;
is_even_cycle = true;
sprite_eval_write = true;
sprite_zero_go = sprite_zero_in_range;
sprite_zero_in_range = false;
yp = ppur.status.sl - 1;
ppuphase = PPU_PHASE_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 (ppur.status.sl == 0 && PPUON && reg_2003 >= 8 && region == Region.NTSC)
{
for (int i = 0; i < 8; i++)
{
OAM[i] = OAM[(reg_2003 & 0xF8) + i];
}
}
if (NTViewCallback != null && yp == NTViewCallback.Scanline) NTViewCallback.Callback();
if (PPUViewCallback != null && yp == PPUViewCallback.Scanline) PPUViewCallback.Callback();
// 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);
}
if (ppur.status.sl != 0)
{
/////////////////////////////////////////////
// Sprite Evaluation Start
/////////////////////////////////////////////
if (sprite_eval_cycle <= 63 && !is_even_cycle)
if (sprite_eval_cycle < 64)
{
// 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 (!is_even_cycle)
{
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)
else
{
if (sprite_eval_cycle == 64)
{
soam_index = 0;
oam_index = reg_2003;
}
if (oam_index >= 256)
{
oam_index = 0;
sprite_eval_write = false;
}
if (is_even_cycle && oam_index < 256)
if (is_even_cycle)
{
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
@ -341,7 +341,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
oam_index += 4;
}
}
else if (soam_index >= 8)
else
{
if (yp >= read_value && yp < read_value + spriteHeight && PPUON)
{
@ -380,30 +380,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
read_value = soam[0]; //writes change to reads
}
}
else
{
// if we don't write sprites anymore, just scan through the oam
read_value = soam[0];
oam_index += 4;
}
}
/////////////////////////////////////////////
// Sprite Evaluation End
/////////////////////////////////////////////
int pixel = 0, pixelcolor = PALRAM[pixel];
//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]); }
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];
if (PPUON) { Read_bgdata(xp, xt + 2); }
//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)
else
{
// if there's anything wrong with how we're doing this, someone please chime in
int addr = ppur.get_2007access();
@ -412,15 +410,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
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
pixelcolor |= 0x8000;
}
//generate the BG data
if (renderbgnow)
if (show_bg_new && (xt > 0 || reg_2001.show_bg_leftmost))
{
int bgtile = (rasterpos + ppur.fh) >> 3;
byte pt_0 = bgdata[bgtile].pt_0;
byte pt_1 = bgdata[bgtile].pt_1;
int sel = 7 - bgpx;
int sel = 7 - (rasterpos + ppur.fh) & 7;
pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1);
if (pixel != 0)
pixel |= bgdata[bgtile].at;
@ -428,7 +427,7 @@ 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
pixelcolor = 0x8000;
//check if the pixel has a sprite in it
if (sl_sprites[256 + xt * 8 + xp] != 0 && renderspritenow)
@ -457,13 +456,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
runppu();
if (PPUON && xp == 6)
if (xp == 6 && PPUON)
{
ppu_was_on = true;
if (ppur.status.cycle == 255) { race_2006 = true; }
}
if (PPUON && xp == 7)
if (xp == 7 && PPUON)
{
ppur.increment_hsc();
@ -526,18 +525,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
else
{
// if scanline is the pre-render line, we just read BG data
Read_bgdata(xp, ref bgdata[xt + 2]);
Read_bgdata(xp, xt + 2);
runppu();
if (PPUON && xp == 6)
if (xp == 6 && PPUON)
{
ppu_was_on = true;
if (ppur.status.cycle == 255) { race_2006 = true; }
}
if (PPUON && xp == 7)
if (xp == 7 && PPUON)
{
ppur.increment_hsc();
@ -623,7 +622,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
s = 0;
ppu_aux_index = 0;
junksprite = (!PPUON);
junksprite = !PPUON;
t_oam[s].oam_y = soam[s * 4];
t_oam[s].oam_ind = soam[s * 4 + 1];
@ -662,7 +661,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
runppu();
break;
case 1:
if (PPUON && ppur.status.sl == 0 && ppur.status.cycle == 305)
if (ppur.status.sl == 0 && ppur.status.cycle == 305 && PPUON)
{
ppur.install_latches();
@ -670,7 +669,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
runppu();
}
else if (PPUON && (ppur.status.sl != 0) && ppur.status.cycle == 257)
else if ((ppur.status.sl != 0) && ppur.status.cycle == 257 && PPUON)
{
if (target <= 61441 && target > 0 && s == 0)
@ -739,15 +738,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
break;
case 5:
// if the PPU is off, we don't put anything on the bus
if (junksprite)
{
runppu();
}
else
{
runppu();
}
runppu();
break;
case 6:
// if the PPU is off, we don't put anything on the bus
@ -825,7 +816,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (s < 8)
{
junksprite = (!PPUON);
junksprite = !PPUON;
t_oam[s].oam_y = soam[s * 4];
t_oam[s].oam_ind = soam[s * 4 + 1];
@ -925,26 +916,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
else
{
if (ppur.status.cycle == 320)
{
ppuphase = PPU_PHASE_BG;
xt = 0;
xp = 0;
}
if (ppur.status.cycle < 336)
{
if (ppur.status.cycle == 320)
{
ppuphase = PPU_PHASE_BG;
xt = 0;
xp = 0;
}
// if scanline is the pre-render line, we just read BG data
Read_bgdata(xp, ref bgdata[xt]);
Read_bgdata(xp, xt);
runppu();
if (PPUON && xp == 6)
if (xp == 6 && PPUON)
{
ppu_was_on = true;
}
if (PPUON && xp == 7)
if (xp == 7 && PPUON)
{
if (!race_2006)
ppur.increment_hsc();
@ -1009,6 +1000,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (ppur.status.cycle == 341)
{
if (Reg2002_vblank_active_pending)
{
Reg2002_vblank_active = 1;
Reg2002_vblank_active_pending = false;
}
ppur.status.cycle = 0;
ppur.status.sl++;
if (ppur.status.sl == 241 + preNMIlines)
@ -1038,6 +1035,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (ppur.status.cycle == 241 * 341 - start_up_offset)
{
if (Reg2002_vblank_active_pending)
{
Reg2002_vblank_active = 1;
Reg2002_vblank_active_pending = false;
}
ppudead--;
ppu_init_frame();

View File

@ -34,7 +34,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk
game, rom, subnesSettings, subnesSyncSettings);
ser.Register<IVideoProvider>(subnes.videoProvider);
ser.Register<ISoundProvider>(subnes.magicSoundProvider);
ser.Register<ISoundProvider>(subnes);
_tracer = new TraceBuffer { Header = "6502: PC, machine code, mnemonic, operands, registers (A, X, Y, P, SP), flags (NVTBDIZCR), CPU Cycle, PPU Cycle" };
ser.Register<ITraceable>(_tracer);