PAL support (PPU & APU)

This commit is contained in:
Souryo 2015-07-21 23:05:27 -04:00
parent 62d87d6bab
commit 41ae3cdcd2
20 changed files with 236 additions and 26 deletions

View file

@ -19,7 +19,6 @@ APU::APU(MemoryManager* memoryManager)
_memoryManager = memoryManager;
_blipBuffer.reset(new Blip_Buffer());
_blipBuffer->sample_rate(APU::SampleRate);
_blipBuffer->clock_rate(CPU::ClockRate);
_outputBuffer = new int16_t[APU::SamplesPerFrame];
@ -45,6 +44,23 @@ APU::~APU()
delete[] _outputBuffer;
}
void APU::SetNesModel(NesModel model, bool forceInit)
{
if(_nesModel != model || forceInit) {
//Finish the current apu frame before switching model
Run();
_nesModel = model;
_blipBuffer->clock_rate(model == NesModel::NTSC ? CPU::ClockRateNtsc : CPU::ClockRatePal);
_squareChannel[0]->SetNesModel(model);
_squareChannel[1]->SetNesModel(model);
_triangleChannel->SetNesModel(model);
_noiseChannel->SetNesModel(model);
_deltaModulationChannel->SetNesModel(model);
_frameCounter->SetNesModel(model);
}
}
void APU::FrameCounterTick(FrameType type)
{
//Quarter & half frame clock envelope & linear counter
@ -185,7 +201,6 @@ void APU::StopAudio()
}
}
void APU::Reset(bool softReset)
{
_currentCycle = 0;
@ -200,6 +215,7 @@ void APU::Reset(bool softReset)
void APU::StreamState(bool saving)
{
Stream<NesModel>(_nesModel);
Stream<uint32_t>(_currentCycle);
Stream<uint32_t>(_previousCycle);
Stream(_squareChannel[0].get());
@ -208,4 +224,8 @@ void APU::StreamState(bool saving)
Stream(_noiseChannel.get());
Stream(_deltaModulationChannel.get());
Stream(_frameCounter.get());
if(!saving) {
SetNesModel(_nesModel, true);
}
}

View file

@ -13,6 +13,7 @@ class DeltaModulationChannel;
class ApuFrameCounter;
class Blip_Buffer;
enum class FrameType;
enum class NesModel;
class APU : public Snapshotable, public IMemoryHandler
{
@ -34,6 +35,8 @@ class APU : public Snapshotable, public IMemoryHandler
int16_t* _outputBuffer;
MemoryManager* _memoryManager;
NesModel _nesModel;
private:
bool NeedToRun(uint32_t currentCycle);
void Run();
@ -59,6 +62,8 @@ class APU : public Snapshotable, public IMemoryHandler
APU::AudioDevice = audioDevice;
}
void SetNesModel(NesModel model, bool forceInit = false);
uint8_t ReadRAM(uint16_t addr);
void WriteRAM(uint16_t addr, uint8_t value);
void GetMemoryRanges(MemoryRanges &ranges);

View file

@ -2,6 +2,7 @@
#include "stdafx.h"
#include "IMemoryHandler.h"
#include "CPU.h"
#include "EmulationSettings.h"
enum class FrameType
{
@ -13,11 +14,15 @@ enum class FrameType
class ApuFrameCounter : public IMemoryHandler, public Snapshotable
{
private:
const vector<vector<int32_t>> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830},
{ 7457, 14913, 22371, 29829, 37281, 37282} } };
const vector<vector<int32_t>> _stepCyclesNtsc = { { { 7457, 14913, 22371, 29828, 29829, 29830},
{ 7457, 14913, 22371, 29829, 37281, 37282} } };
const vector<vector<int32_t>> _stepCyclesPal = { { { 8313, 16627, 24939, 33252, 33253, 33254},
{ 8313, 16627, 24939, 33253, 41565, 41566} } };
const vector<vector<FrameType>> _frameType = { { { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None },
{ FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None } } };
vector<vector<int32_t>> _stepCycles;
NesModel _nesModel;
int32_t _nextIrqCycle;
int32_t _previousCycle;
uint32_t _currentStep;
@ -57,7 +62,22 @@ public:
Stream<uint32_t>(_currentStep);
Stream<uint32_t>(_stepMode);
Stream<bool>(_inhibitIRQ);
}
Stream<NesModel>(_nesModel);
if(!saving) {
SetNesModel(_nesModel);
}
}
void SetNesModel(NesModel model)
{
if(_nesModel != model || _stepCycles.size() == 0) {
_nesModel = model;
_stepCycles.clear();
_stepCycles.push_back(model == NesModel::NTSC ? _stepCyclesNtsc[0] : _stepCyclesPal[0]);
_stepCycles.push_back(model == NesModel::NTSC ? _stepCyclesNtsc[1] : _stepCyclesPal[1]);
}
}
uint32_t Run(int32_t &cyclesToRun)
{

View file

@ -16,6 +16,7 @@ private:
uint32_t _previousCycle;
AudioChannel _channel;
double _baseVolume;
NesModel _nesModel;
protected:
uint16_t _timer = 0;
@ -66,12 +67,23 @@ public:
Stream<uint32_t>(_previousCycle);
Stream<uint16_t>(_timer);
Stream<uint16_t>(_period);
Stream<NesModel>(_nesModel);
if(!saving) {
_buffer->clear();
}
}
void SetNesModel(NesModel model)
{
_nesModel = model;
}
NesModel GetNesModel()
{
return _nesModel;
}
virtual void Run(uint32_t targetCycle)
{
while(_previousCycle < targetCycle) {

View file

@ -27,6 +27,7 @@ class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificat
bool _hasCHRRAM;
bool _hasBattery;
bool _isPalRom;
string _romFilename;
MirroringType _mirroringType;
@ -159,6 +160,7 @@ class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificat
_prgSize = romLoader.GetPRGSize();
_chrSize = romLoader.GetCHRSize();
_hasBattery = romLoader.HasBattery();
_isPalRom = romLoader.IsPalRom();
_romFilename = romLoader.GetFilename();
_hasExpansionRAM = false;
@ -252,6 +254,11 @@ class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificat
return _hasBattery;
}
bool IsPalRom()
{
return _isPalRom;
}
MirroringType GetMirroringType()
{
return _mirroringType;

View file

@ -830,7 +830,8 @@ protected:
void StreamState(bool saving);
public:
static const uint32_t ClockRate = 1789773;
static const uint32_t ClockRateNtsc = 1789773;
static const uint32_t ClockRatePal = 1662607;
CPU(MemoryManager *memoryManager);
static int32_t GetCycleCount() { return CPU::Instance->_cycleCount; }

View file

@ -153,13 +153,15 @@ void Console::Resume()
void Console::Run()
{
Timer clockTimer;
double targetTime;
double elapsedTime = 0;
double targetTime = 16.63926405550947; //~60.0988fps
uint32_t lastFrameNumber = -1;
_runLock.Acquire();
_stopLock.Acquire();
uint32_t lastFrameNumber = -1;
UpdateNesModel(targetTime, true);
while(true) {
_cpu->Exec();
uint32_t currentFrameNumber = PPU::GetFrameCount();
@ -200,6 +202,8 @@ void Console::Run()
_runLock.Acquire();
MessageManager::SendNotification(ConsoleNotificationType::GameResumed);
}
UpdateNesModel(targetTime, false);
clockTimer.Reset();
if(_stop) {
@ -213,6 +217,17 @@ void Console::Run()
_runLock.Release();
}
void Console::UpdateNesModel(double &frameDelay, bool showMessage)
{
NesModel model = EmulationSettings::GetNesModel();
if(model == NesModel::Auto) {
model = _mapper->IsPalRom() ? NesModel::PAL : NesModel::NTSC;
}
frameDelay = (model == NesModel::NTSC ? 16.63926405550947 : 19.99720920217466); //60.1fps (NTSC), 50.01fps (PAL)
_ppu->SetNesModel(model);
_apu->SetNesModel(model);
}
void Console::SaveState(ostream &saveStream)
{
if(Instance->_initialized) {

View file

@ -35,6 +35,7 @@ class Console
void ResetComponents(bool softReset);
void Initialize(string filename);
void UpdateNesModel(double &frameDelay, bool showMessage);
public:
Console();

View file

@ -8,7 +8,8 @@
class DeltaModulationChannel : public BaseApuChannel<127>
{
private:
const vector<uint16_t> _dmcPeriodLookupTable = { { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 } };
const vector<uint16_t> _dmcPeriodLookupTableNtsc = { { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 } };
const vector<uint16_t> _dmcPeriodLookupTablePal = { { 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 } };
MemoryManager *_memoryManager = nullptr;
@ -164,7 +165,7 @@ public:
//"The rate determines for how many CPU cycles happen between changes in the output level during automatic delta-encoded sample playback."
//Because BaseApuChannel does not decrement when setting _timer, we need to actually set the value to 1 less than the lookup table
_period = _dmcPeriodLookupTable[value & 0x0F] - 1;
_period = (GetNesModel() == NesModel::NTSC ? _dmcPeriodLookupTableNtsc : _dmcPeriodLookupTablePal)[value & 0x0F] - 1;
if(!_irqEnabled) {
CPU::ClearIRQSource(IRQSource::DMC);

View file

@ -3,4 +3,5 @@
uint32_t EmulationSettings::Flags = 0;
uint32_t EmulationSettings::AudioLatency = 20000;
double EmulationSettings::ChannelVolume[5] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
double EmulationSettings::ChannelVolume[5] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
NesModel EmulationSettings::Model;

View file

@ -18,12 +18,20 @@ enum class AudioChannel
DMC = 4
};
enum class NesModel
{
Auto = 0,
NTSC = 1,
PAL = 2,
};
class EmulationSettings
{
private:
static uint32_t Flags;
static uint32_t AudioLatency;
static double ChannelVolume[5];
static NesModel Model;
public:
static void SetFlags(uint32_t flags)
@ -41,6 +49,16 @@ public:
return (Flags & flag) == flag;
}
static void SetNesModel(NesModel model)
{
Model = model;
}
static NesModel GetNesModel()
{
return Model;
}
//0: Muted, 0.5: Default, 1.0: Max volume
static void SetChannelVolume(AudioChannel channel, double volume)
{

View file

@ -8,7 +8,8 @@
class NoiseChannel : public ApuEnvelope
{
private:
const vector<uint16_t> _noisePeriodLookupTable = { { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 } };
const vector<uint16_t> _noisePeriodLookupTableNtsc = { { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 } };
const vector<uint16_t> _noisePeriodLookupTablePal = { { 4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 } };
//On power-up, the shift register is loaded with the value 1.
uint16_t _shiftRegister = 1;
@ -73,7 +74,7 @@ public:
break;
case 2: //400E
_period = _noisePeriodLookupTable[value & 0x0F];
_period = (GetNesModel() == NesModel::NTSC ? _noisePeriodLookupTableNtsc : _noisePeriodLookupTablePal)[value & 0x0F];
break;
case 3: //400F

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "PPU.h"
#include "CPU.h"
#include "EmulationSettings.h"
PPU* PPU::Instance = nullptr;
IVideoDevice *PPU::VideoDevice = nullptr;
@ -50,6 +51,12 @@ void PPU::Reset()
memset(_spriteRAM, 0xFF, 0x100);
}
void PPU::SetNesModel(NesModel model)
{
_nesModel = model;
_vblankEnd = (model == NesModel::NTSC ? 260 : 311);
}
PPUDebugState PPU::GetState()
{
PPUDebugState state;
@ -473,7 +480,8 @@ void PPU::ProcessPrerenderScanline()
//copy vertical scrolling value from t
_state.VideoRamAddr = (_state.VideoRamAddr & ~0x7BE0) | (_state.TmpVideoRamAddr & 0x7BE0);
}
} else if(_cycle == 339 && IsRenderingEnabled() && (_frameCount & 0x01)) {
} else if(_nesModel == NesModel::NTSC && _cycle == 339 && IsRenderingEnabled() && (_frameCount & 0x01)) {
//This behavior is NTSC-specific - PAL frames are always the same number of cycles
//"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340)
_cycle = -1;
_scanline = 0;
@ -626,15 +634,14 @@ void PPU::Exec()
ProcessPrerenderScanline();
} else if(_scanline == 241) {
BeginVBlank();
} else if(_scanline == 260) {
} else if(_scanline == _vblankEnd) {
EndVBlank();
}
if(_cycle == 340) {
_cycle = -1;
_scanline++;
if(_scanline == 261) {
if(_scanline++ == _vblankEnd) {
_scanline = -1;
}
}
@ -646,6 +653,10 @@ void PPU::ExecStatic()
PPU::Instance->Exec();
PPU::Instance->Exec();
PPU::Instance->Exec();
if(PPU::Instance->_nesModel == NesModel::PAL && CPU::GetCycleCount() % 5 == 0) {
//PAL PPU runs 3.2 clocks for every CPU clock, so we need to run an extra clock every 5 CPU clocks
PPU::Instance->Exec();
}
}
void PPU::StreamState(bool saving)
@ -717,4 +728,10 @@ void PPU::StreamState(bool saving)
Stream<bool>(_writeOAMData);
Stream<uint32_t>(_overflowCounter);
Stream<bool>(_sprite0Added);
Stream<NesModel>(_nesModel);
if(!saving) {
SetNesModel(_nesModel);
}
}

View file

@ -5,6 +5,8 @@
#include "MemoryManager.h"
#include "IVideoDevice.h"
enum class NesModel;
enum PPURegisters
{
Control = 0x00,
@ -92,11 +94,11 @@ class PPU : public IMemoryHandler, public Snapshotable
MemoryManager *_memoryManager;
PPUState _state;
int32_t _scanline = 0;
uint32_t _cycle = 0;
uint32_t _frameCount = 0;
uint8_t _memoryReadBuffer = 0;
int32_t _scanline;
uint32_t _cycle;
uint32_t _frameCount;
uint8_t _memoryReadBuffer;
uint8_t _paletteRAM[0x100];
uint8_t _spriteRAM[0x100];
@ -104,6 +106,9 @@ class PPU : public IMemoryHandler, public Snapshotable
uint32_t *_outputBuffer;
NesModel _nesModel;
uint16_t _vblankEnd;
PPUControlFlags _flags;
PPUStatusFlags _statusFlags;
@ -192,6 +197,8 @@ class PPU : public IMemoryHandler, public Snapshotable
uint8_t ReadRAM(uint16_t addr);
void WriteRAM(uint16_t addr, uint8_t value);
void SetNesModel(NesModel model);
void Exec();
static void ExecStatic();

View file

@ -40,6 +40,11 @@ struct NESHeader
return (Flags1 & 0x04) == 0x04;
}
bool IsPalRom()
{
return (CartType & 0x01) == 0x01;
}
MirroringType GetMirroringType()
{
if(Flags1 & 0x08) {
@ -218,6 +223,11 @@ class ROMLoader
return _header.HasBattery();
}
bool IsPalRom()
{
return _header.IsPalRom() || _filename.find("(e)") != string::npos || _filename.find("(E)") != string::npos;
}
string GetFilename()
{
return _filename;

View file

@ -21,6 +21,7 @@ namespace Mesen.GUI.Config
public List<CheatInfo> Cheats;
public List<ControllerInfo> Controllers;
public bool ShowOnlyCheatsForCurrentGame;
public NesModel Region;
public Configuration()
{
@ -33,6 +34,15 @@ namespace Mesen.GUI.Config
Controllers = new List<ControllerInfo>();
}
public void ApplyConfig()
{
ControllerInfo.ApplyConfig();
VideoInfo.ApplyConfig();
AudioInfo.ApplyConfig();
InteropEmu.SetNesModel(Region);
}
private void InitializeDefaults()
{
while(Controllers.Count < 4) {

View file

@ -78,6 +78,10 @@
this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator();
this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem();
this.dxViewer = new Mesen.GUI.Controls.DXViewer();
this.mnuRegion = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRegionAuto = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRegionNtsc = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRegionPal = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip.SuspendLayout();
this.SuspendLayout();
//
@ -201,9 +205,10 @@
this.mnuLimitFPS,
this.mnuShowFPS,
this.toolStripMenuItem1,
this.mnuAudioConfig,
this.mnuInput,
this.mnuVideoConfig,
this.mnuAudioConfig});
this.mnuRegion,
this.mnuVideoConfig});
this.mnuOptions.Name = "mnuOptions";
this.mnuOptions.Size = new System.Drawing.Size(61, 20);
this.mnuOptions.Text = "Options";
@ -439,6 +444,37 @@
this.dxViewer.Size = new System.Drawing.Size(1024, 896);
this.dxViewer.TabIndex = 1;
//
// mnuRegion
//
this.mnuRegion.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.mnuRegionAuto,
this.mnuRegionNtsc,
this.mnuRegionPal});
this.mnuRegion.Name = "mnuRegion";
this.mnuRegion.Size = new System.Drawing.Size(152, 22);
this.mnuRegion.Text = "Region";
//
// mnuRegionAuto
//
this.mnuRegionAuto.Name = "mnuRegionAuto";
this.mnuRegionAuto.Size = new System.Drawing.Size(152, 22);
this.mnuRegionAuto.Text = "Auto";
this.mnuRegionAuto.Click += new System.EventHandler(this.mnuRegion_Click);
//
// mnuRegionNtsc
//
this.mnuRegionNtsc.Name = "mnuRegionNtsc";
this.mnuRegionNtsc.Size = new System.Drawing.Size(152, 22);
this.mnuRegionNtsc.Text = "NTSC";
this.mnuRegionNtsc.Click += new System.EventHandler(this.mnuRegion_Click);
//
// mnuRegionPal
//
this.mnuRegionPal.Name = "mnuRegionPal";
this.mnuRegionPal.Size = new System.Drawing.Size(152, 22);
this.mnuRegionPal.Text = "PAL";
this.mnuRegionPal.Click += new System.EventHandler(this.mnuRegion_Click);
//
// frmMain
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@ -504,6 +540,10 @@
private System.Windows.Forms.ToolStripMenuItem mnuLoadState;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7;
private System.Windows.Forms.ToolStripMenuItem mnuCheats;
private System.Windows.Forms.ToolStripMenuItem mnuRegion;
private System.Windows.Forms.ToolStripMenuItem mnuRegionAuto;
private System.Windows.Forms.ToolStripMenuItem mnuRegionNtsc;
private System.Windows.Forms.ToolStripMenuItem mnuRegionPal;
}
}

View file

@ -58,8 +58,7 @@ namespace Mesen.GUI.Forms
InteropEmu.AddKnowGameFolder(System.IO.Path.GetDirectoryName(romPath).ToLowerInvariant());
}
ControllerInfo.ApplyConfig();
AudioInfo.ApplyConfig();
ConfigManager.Config.ApplyConfig();
UpdateEmulationFlags();
}
@ -135,6 +134,10 @@ namespace Mesen.GUI.Forms
mnuDebugger.Enabled = !netPlay && _emuThread != null;
mnuTakeScreenshot.Enabled = _emuThread != null;
mnuRegionAuto.Checked = ConfigManager.Config.Region == NesModel.Auto;
mnuRegionNtsc.Checked = ConfigManager.Config.Region == NesModel.NTSC;
mnuRegionPal.Checked = ConfigManager.Config.Region == NesModel.PAL;
}
} catch { }
}
@ -403,5 +406,17 @@ namespace Mesen.GUI.Forms
frmAudioConfig frm = new frmAudioConfig();
frm.ShowDialog();
}
private void mnuRegion_Click(object sender, EventArgs e)
{
if(sender == mnuRegionAuto) {
ConfigManager.Config.Region = NesModel.Auto;
} else if(sender == mnuRegionNtsc) {
ConfigManager.Config.Region = NesModel.NTSC;
} else if(sender == mnuRegionPal) {
ConfigManager.Config.Region = NesModel.PAL;
}
ConfigManager.Config.ApplyConfig();
}
}
}

View file

@ -65,6 +65,7 @@ namespace Mesen.GUI
[DllImport(DLLPath)] public static extern void ClearFlags(UInt32 flags);
[DllImport(DLLPath)] public static extern void SetChannelVolume(UInt32 channel, double volume);
[DllImport(DLLPath)] public static extern void SetAudioLatency(UInt32 msLatency);
[DllImport(DLLPath)] public static extern void SetNesModel(NesModel model);
[DllImport(DLLPath)] public static extern void DebugInitialize();
[DllImport(DLLPath)] public static extern void DebugRelease();
@ -272,6 +273,13 @@ namespace Mesen.GUI
Write = 2
};
public enum NesModel
{
Auto = 0,
NTSC = 1,
PAL = 2
}
public class MD5Helper
{
public static string GetMD5Hash(string filename)

View file

@ -162,6 +162,7 @@ namespace InteropEmu {
DllExport void __stdcall ClearFlags(uint32_t flags) { EmulationSettings::ClearFlags(flags); }
DllExport void __stdcall SetChannelVolume(uint32_t channel, double volume) { EmulationSettings::SetChannelVolume((AudioChannel)channel, volume); }
DllExport void __stdcall SetAudioLatency(uint32_t msLatency) { EmulationSettings::SetAudioLatency(msLatency); }
DllExport void __stdcall SetNesModel(uint32_t model) { EmulationSettings::SetNesModel((NesModel)model); }
}
}