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 4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
}; };
public sealed class PulseUnit public sealed class PulseUnit
{ {
public PulseUnit(APU apu, int unit) { this.unit = unit; this.apu = apu; } 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("sequencer_irq_flag", ref sequencer_irq_flag);
ser.Sync("len_clock_active", ref len_clock_active); 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[0].SyncState(ser);
pulse[1].SyncState(ser); pulse[1].SyncState(ser);
triangle.SyncState(ser); triangle.SyncState(ser);
@ -1259,127 +1262,118 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int pending_length_change; 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[0].Run();
pulse[1].len_halt = false; pulse[1].Run();
noise.len_halt = false; triangle.Run();
noise.Run();
dmc.Run();
} pulse[0].len_halt = false;
else pulse[1].len_halt = false;
{ noise.len_halt = false;
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--;
}
}
} }
public struct Delta public void RunOneLast()
{ {
public uint time; if (pending_length_change > 0)
public int value;
public Delta(uint time, int value)
{ {
this.time = time; pending_length_change--;
this.value = value; 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> /// <summary>only call in board.ClockCPU()</summary>
/// <param name="value"></param> /// <param name="value"></param>
public void ExternalQueue(int value) public void ExternalQueue(int value)
{ {
// sampleclock is incremented right before board.ClockCPU() cart_sound = value + old_cart_sound;
dlist.Add(new Delta(sampleclock - 1, value));
if (cart_sound != old_cart_sound)
{
recalculate = true;
old_cart_sound = cart_sound;
}
} }
public uint sampleclock = 0; public uint sampleclock = 0;
int oldmix = 0; int oldmix = 0;
int cart_sound = 0;
int old_cart_sound = 0;
void EmitSample() public int EmitSample()
{ {
if (recalculate) if (recalculate)
{ {
@ -1391,16 +1385,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int s_noise = noise.sample; int s_noise = noise.sample;
int s_dmc = dmc.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 // more properly correct
float pulse_out, tnd_out; float pulse_out, tnd_out;
if (s_pulse0 == 0 && s_pulse1 == 0) if (s_pulse0 == 0 && s_pulse1 == 0)
@ -1412,13 +1396,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
float output = pulse_out + tnd_out; float output = pulse_out + tnd_out;
// output = output * 2 - 1; // 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. // 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; 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 namespace BizHawk.Emulation.Cores.Nintendo.NES
{ {
public partial class NES : IEmulator, ICycleTiming public partial class NES : IEmulator, ISoundProvider, ICycleTiming
{ {
//hardware/state //hardware/state
public MOS6502X<CpuLink> cpu; public MOS6502X<CpuLink> cpu;
@ -72,92 +72,65 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
return Board; 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() public void Dispose()
{ {
if (magicSoundProvider != null) if (blip != null)
magicSoundProvider.Dispose(); {
magicSoundProvider = null; blip.Dispose();
blip = null;
}
} }
public class MagicSoundProvider : ISoundProvider, IDisposable public void GetSamplesSync(out short[] samples, out int nsamp)
{ {
BlipBuffer blip; blip.EndFrame(apu.sampleclock);
NES nes; apu.sampleclock = 0;
const int blipbuffsize = 4096; nsamp = blip.SamplesAvailable();
samples = new short[nsamp * 2];
public MagicSoundProvider(NES nes, uint infreq) blip.ReadSamples(samples, nsamp, true);
{ // duplicate to stereo
this.nes = nes; for (int i = 0; i < nsamp * 2; i += 2)
samples[i + 1] = samples[i];
blip = new BlipBuffer(blipbuffsize); Board.ApplyCustomAudio(samples);
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;
}
}
} }
public MagicSoundProvider magicSoundProvider;
public void DiscardSamples()
{
blip.Clear();
apu.sampleclock = 0;
}
#endregion
public void HardReset() public void HardReset()
{ {
@ -243,8 +216,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
default: default:
throw new Exception("Unknown displaytype!"); throw new Exception("Unknown displaytype!");
} }
if (magicSoundProvider == null)
magicSoundProvider = new MagicSoundProvider(this, (uint)cpuclockrate); blip.SetRates((uint)cpuclockrate, 44100);
BoardSystemHardReset(); BoardSystemHardReset();
@ -563,7 +536,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
///////////////////////////// /////////////////////////////
// dmc dma end // dmc dma end
///////////////////////////// /////////////////////////////
apu.RunOne(true); apu.RunOneFirst();
if (cpu.RDY && !IRQ_delay) if (cpu.RDY && !IRQ_delay)
{ {
@ -576,7 +549,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
cpu.ExecuteOne(); 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) if (ppu.double_2007_read > 0)
ppu.double_2007_read--; ppu.double_2007_read--;
@ -592,8 +576,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
cpu.RDY = true; cpu.RDY = true;
IRQ_delay = true; IRQ_delay = true;
} }
Board.ClockCPU();
} }
public byte ReadReg(int addr) 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("sprdma_countdown", ref sprdma_countdown);
ser.Sync("cpu_deadcounter", ref cpu_deadcounter); ser.Sync("cpu_deadcounter", ref cpu_deadcounter);
ser.Sync("old_s", ref old_s);
//oam related //oam related
ser.Sync("Oam_Dma_Index", ref oam_dma_index); ser.Sync("Oam_Dma_Index", ref oam_dma_index);
ser.Sync("Oam_Dma_Exec", ref oam_dma_exec); 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 }; Tracer = new TraceBuffer { Header = cpu.TraceHeader };
ser.Register<ITraceable>(Tracer); ser.Register<ITraceable>(Tracer);
ser.Register<IVideoProvider>(videoProvider); ser.Register<IVideoProvider>(videoProvider);
ser.Register<ISoundProvider>(magicSoundProvider); ser.Register<ISoundProvider>(this);
if (Board is BANDAI_FCG_1) 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("xstart", ref xstart);
ser.Sync("rasterpos", ref rasterpos); ser.Sync("rasterpos", ref rasterpos);
ser.Sync("renderspritenow", ref renderspritenow); ser.Sync("renderspritenow", ref renderspritenow);
ser.Sync("renderbgnow", ref renderbgnow);
ser.Sync("s", ref s); ser.Sync("s", ref s);
ser.Sync("ppu_aux_index", ref ppu_aux_index); ser.Sync("ppu_aux_index", ref ppu_aux_index);
ser.Sync("junksprite", ref junksprite); ser.Sync("junksprite", ref junksprite);
@ -350,10 +349,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
void runppu() 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
if (install_2006 > 0)
if (install_2006>0)
{ {
install_2006--; install_2006--;
if (install_2006==0) if (install_2006==0)
@ -386,7 +383,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ppur.status.cycle++; ppur.status.cycle++;
is_even_cycle = !is_even_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; 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) if (HasClockPPU)
{ {
nes.Board.ClockPPU(); nes.Board.ClockPPU();

View File

@ -78,13 +78,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
pixelcolor_latch_1 = pixelcolor; pixelcolor_latch_1 = pixelcolor;
} }
void Read_bgdata(int cycle, ref BGDataRecord bgdata) void Read_bgdata(int cycle, int i)
{ {
switch (cycle) switch (cycle)
{ {
case 0: case 0:
ppu_addr_temp = ppur.get_ntread(); 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; break;
case 1: case 1:
break; break;
@ -98,20 +98,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if ((ppur.ht & 2) != 0) at >>= 2; if ((ppur.ht & 2) != 0) at >>= 2;
at &= 0x03; at &= 0x03;
at <<= 2; at <<= 2;
bgdata.at = at; bgdata[i].at = at;
break; break;
} }
case 3: case 3:
break; break;
case 4: case 4:
ppu_addr_temp = ppur.get_ptread(bgdata.nt); ppu_addr_temp = ppur.get_ptread(bgdata[i].nt);
bgdata.pt_0 = ppubus_read(ppu_addr_temp, true, true); bgdata[i].pt_0 = ppubus_read(ppu_addr_temp, true, true);
break; break;
case 5: case 5:
break; break;
case 6: case 6:
ppu_addr_temp |= 8; 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; break;
case 7: case 7:
break; break;
@ -134,7 +134,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int xstart; int xstart;
int rasterpos; int rasterpos;
bool renderspritenow; bool renderspritenow;
bool renderbgnow;
int s; int s;
int ppu_aux_index; int ppu_aux_index;
bool junksprite; bool junksprite;
@ -149,8 +148,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ppur.status.cycle = 0; ppur.status.cycle = 0;
// These things happen at the start of every frame // These things happen at the start of every frame
//Reg2002_vblank_active = true;
//Reg2002_vblank_active_pending = true;
ppuphase = PPU_PHASE_VBL; ppuphase = PPU_PHASE_VBL;
bgdata = new BGDataRecord[34]; bgdata = new BGDataRecord[34];
} }
@ -188,6 +185,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (ppur.status.cycle == 341) 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.cycle = 0;
ppur.status.sl++; ppur.status.sl++;
if (ppur.status.sl == 241 + preNMIlines + postNMIlines) if (ppur.status.sl == 241 + preNMIlines + postNMIlines)
@ -201,100 +204,97 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public void TickPPU_active() 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 < 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) if (ppur.status.sl != 0)
{ {
///////////////////////////////////////////// /////////////////////////////////////////////
// Sprite Evaluation Start // 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 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
// this is a bit of a shortcut to save some instructions // this is a bit of a shortcut to save some instructions
// data is read from OAM as normal but never used // data is read from OAM as normal but never used
soam[soam_index] = 0xFF; if (!is_even_cycle)
soam_index++; {
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 // 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 (sprite_eval_cycle >= 64) else
{ {
if (sprite_eval_cycle == 64)
{
soam_index = 0;
oam_index = reg_2003;
}
if (oam_index >= 256) if (oam_index >= 256)
{ {
oam_index = 0; oam_index = 0;
sprite_eval_write = false; sprite_eval_write = false;
} }
if (is_even_cycle && oam_index < 256) if (is_even_cycle)
{ {
if ((oam_index + soam_m_index) < 256) if ((oam_index + soam_m_index) < 256)
read_value = OAM[oam_index + soam_m_index]; read_value = OAM[oam_index + soam_m_index];
else else
read_value = OAM[oam_index + soam_m_index - 256]; 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) else if (sprite_eval_write)
{ {
//look for sprites //look for sprites
@ -341,7 +341,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
oam_index += 4; oam_index += 4;
} }
} }
else if (soam_index >= 8) else
{ {
if (yp >= read_value && yp < read_value + spriteHeight && PPUON) 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 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 // Sprite Evaluation End
///////////////////////////////////////////// /////////////////////////////////////////////
int pixel = 0, pixelcolor = PALRAM[pixel];
//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 //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 // due to the cpu not running while the sprite renders below
if (PPUON) { Read_bgdata(xp, ref bgdata[xt + 2]); } if (PPUON) { Read_bgdata(xp, 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];
//according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx //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. //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) //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 // if there's anything wrong with how we're doing this, someone please chime in
int addr = ppur.get_2007access(); int addr = ppur.get_2007access();
@ -412,15 +410,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
pixel = addr & 0x1F; pixel = addr & 0x1F;
} }
pixelcolor = PALRAM[pixel]; 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 //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_0 = bgdata[bgtile].pt_0;
byte pt_1 = bgdata[bgtile].pt_1; 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); pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1);
if (pixel != 0) if (pixel != 0)
pixel |= bgdata[bgtile].at; pixel |= bgdata[bgtile].at;
@ -428,7 +427,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
if (!nes.Settings.DispBackground) 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 //check if the pixel has a sprite in it
if (sl_sprites[256 + xt * 8 + xp] != 0 && renderspritenow) if (sl_sprites[256 + xt * 8 + xp] != 0 && renderspritenow)
@ -457,13 +456,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
runppu(); runppu();
if (PPUON && xp == 6) if (xp == 6 && PPUON)
{ {
ppu_was_on = true; ppu_was_on = true;
if (ppur.status.cycle == 255) { race_2006 = true; } if (ppur.status.cycle == 255) { race_2006 = true; }
} }
if (PPUON && xp == 7) if (xp == 7 && PPUON)
{ {
ppur.increment_hsc(); ppur.increment_hsc();
@ -526,18 +525,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
else else
{ {
// if scanline is the pre-render line, we just read BG data // 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(); runppu();
if (PPUON && xp == 6) if (xp == 6 && PPUON)
{ {
ppu_was_on = true; ppu_was_on = true;
if (ppur.status.cycle == 255) { race_2006 = true; } if (ppur.status.cycle == 255) { race_2006 = true; }
} }
if (PPUON && xp == 7) if (xp == 7 && PPUON)
{ {
ppur.increment_hsc(); ppur.increment_hsc();
@ -623,7 +622,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
s = 0; s = 0;
ppu_aux_index = 0; ppu_aux_index = 0;
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];
@ -662,7 +661,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
runppu(); runppu();
break; break;
case 1: 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(); ppur.install_latches();
@ -670,7 +669,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
runppu(); 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) if (target <= 61441 && target > 0 && s == 0)
@ -739,15 +738,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
break; break;
case 5: case 5:
// if the PPU is off, we don't put anything on the bus runppu();
if (junksprite)
{
runppu();
}
else
{
runppu();
}
break; break;
case 6: case 6:
// 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
@ -825,7 +816,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (s < 8) if (s < 8)
{ {
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];
@ -925,26 +916,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
else else
{ {
if (ppur.status.cycle == 320)
{
ppuphase = PPU_PHASE_BG;
xt = 0;
xp = 0;
}
if (ppur.status.cycle < 336) 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 // if scanline is the pre-render line, we just read BG data
Read_bgdata(xp, ref bgdata[xt]); Read_bgdata(xp, xt);
runppu(); runppu();
if (PPUON && xp == 6) if (xp == 6 && PPUON)
{ {
ppu_was_on = true; ppu_was_on = true;
} }
if (PPUON && xp == 7) if (xp == 7 && PPUON)
{ {
if (!race_2006) if (!race_2006)
ppur.increment_hsc(); ppur.increment_hsc();
@ -1009,6 +1000,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (ppur.status.cycle == 341) 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.cycle = 0;
ppur.status.sl++; ppur.status.sl++;
if (ppur.status.sl == 241 + preNMIlines) 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 (ppur.status.cycle == 241 * 341 - start_up_offset)
{ {
if (Reg2002_vblank_active_pending)
{
Reg2002_vblank_active = 1;
Reg2002_vblank_active_pending = false;
}
ppudead--; ppudead--;
ppu_init_frame(); ppu_init_frame();

View File

@ -34,7 +34,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk
game, rom, subnesSettings, subnesSyncSettings); game, rom, subnesSettings, subnesSyncSettings);
ser.Register<IVideoProvider>(subnes.videoProvider); 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" }; _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); ser.Register<ITraceable>(_tracer);