CPU/PPU: Improved timing and implemented catch-up in PPU when registers are written to in the middle of a scanline
This commit is contained in:
parent
944f94b271
commit
4139f6dca8
8 changed files with 80 additions and 39 deletions
|
@ -137,7 +137,7 @@ void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile)
|
|||
_memoryManager->Initialize(shared_from_this());
|
||||
|
||||
_cpu.reset(new Cpu(_memoryManager.get()));
|
||||
_memoryManager->IncrementMasterClockValue<160>();
|
||||
_memoryManager->IncrementMasterClockValue<162>();
|
||||
|
||||
//if(_debugger) {
|
||||
//Reset debugger if it was running before
|
||||
|
|
|
@ -268,11 +268,13 @@ void Cpu::SEI()
|
|||
|
||||
void Cpu::REP()
|
||||
{
|
||||
Idle();
|
||||
ClearFlags((uint8_t)_operand);
|
||||
}
|
||||
|
||||
void Cpu::SEP()
|
||||
{
|
||||
Idle();
|
||||
SetFlags((uint8_t)_operand);
|
||||
if(CheckFlag(ProcFlags::IndexMode8)) {
|
||||
//Truncate X/Y when 8-bit indexes are enabled
|
||||
|
|
15
Core/Cpu.cpp
15
Core/Cpu.cpp
|
@ -11,7 +11,9 @@ Cpu::Cpu(MemoryManager* memoryManager)
|
|||
_state.SP = 0x1FF;
|
||||
_state.EmulationMode = true;
|
||||
_nmiFlag = false;
|
||||
_prevNmiFlag = false;
|
||||
_irqSource = (uint8_t)IrqSource::None;
|
||||
_prevIrqSource = (uint8_t)IrqSource::None;
|
||||
SetFlags(ProcFlags::MemoryMode8);
|
||||
SetFlags(ProcFlags::IndexMode8);
|
||||
}
|
||||
|
@ -286,11 +288,12 @@ void Cpu::Exec()
|
|||
case 0xFE: AddrMode_AbsIdxX(); INC(); break;
|
||||
case 0xFF: AddrMode_AbsLngIdxX(); SBC(); break;
|
||||
}
|
||||
|
||||
if(_nmiFlag) {
|
||||
|
||||
//Use the state of the IRQ/NMI flags on the previous cycle to determine if an IRQ is processed or not
|
||||
if(_prevNmiFlag) {
|
||||
ProcessInterrupt(_state.EmulationMode ? Cpu::LegacyNmiVector : Cpu::NmiVector);
|
||||
_nmiFlag = false;
|
||||
} else if(_irqSource && !CheckFlag(ProcFlags::IrqDisable)) {
|
||||
} else if(_prevIrqSource && !CheckFlag(ProcFlags::IrqDisable)) {
|
||||
ProcessInterrupt(_state.EmulationMode ? Cpu::LegacyIrqVector : Cpu::IrqVector);
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +342,8 @@ uint8_t Cpu::GetOpCode()
|
|||
void Cpu::Idle()
|
||||
{
|
||||
#ifndef DUMMYCPU
|
||||
_prevNmiFlag = _nmiFlag;
|
||||
_prevIrqSource = _irqSource;
|
||||
_memoryManager->IncrementMasterClockValue<6>();
|
||||
#endif
|
||||
}
|
||||
|
@ -372,6 +377,8 @@ uint8_t Cpu::Read(uint32_t addr, MemoryOperationType type)
|
|||
LogRead(addr, value);
|
||||
return value;
|
||||
#else
|
||||
_prevNmiFlag = _nmiFlag;
|
||||
_prevIrqSource = _irqSource;
|
||||
return _memoryManager->Read(addr, type);
|
||||
#endif
|
||||
}
|
||||
|
@ -415,6 +422,8 @@ void Cpu::Write(uint32_t addr, uint8_t value, MemoryOperationType type)
|
|||
#ifdef DUMMYCPU
|
||||
LogWrite(addr, value);
|
||||
#else
|
||||
_prevNmiFlag = _nmiFlag;
|
||||
_prevIrqSource = _irqSource;
|
||||
_memoryManager->Write(addr, value, type);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -38,7 +38,9 @@ private:
|
|||
CpuState _state;
|
||||
uint32_t _operand;
|
||||
bool _nmiFlag;
|
||||
bool _prevNmiFlag;
|
||||
uint8_t _irqSource;
|
||||
uint8_t _prevIrqSource;
|
||||
|
||||
uint32_t GetProgramAddress(uint16_t addr);
|
||||
uint32_t GetDataAddress(uint16_t addr);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
void MemoryManager::Initialize(shared_ptr<Console> console)
|
||||
{
|
||||
_lastMasterClock = 0;
|
||||
_cyclesToRun = 0;
|
||||
_masterClock = 0;
|
||||
_console = console;
|
||||
_regs = console->GetInternalRegisters().get();
|
||||
|
@ -102,7 +102,7 @@ void MemoryManager::GenerateMasterClockTable()
|
|||
uint8_t page = (i & 0xFF);
|
||||
if(page <= 0x1F) {
|
||||
//Slow
|
||||
_masterClockTable[j][i] = 6;
|
||||
_masterClockTable[j][i] = 8;
|
||||
} else if(page >= 0x20 && page <= 0x3F) {
|
||||
//Fast
|
||||
_masterClockTable[j][i] = 6;
|
||||
|
@ -127,20 +127,26 @@ void MemoryManager::GenerateMasterClockTable()
|
|||
|
||||
void MemoryManager::IncrementMasterClock(uint32_t addr)
|
||||
{
|
||||
_previousSpeed = _masterClockTable[(uint8_t)_regs->IsFastRomEnabled()][addr >> 8];
|
||||
_masterClock += _previousSpeed;
|
||||
while(_lastMasterClock < _masterClock - 3) {
|
||||
_ppu->Exec();
|
||||
_lastMasterClock += 4;
|
||||
}
|
||||
IncrementMasterClockValue(_masterClockTable[(uint8_t)_regs->IsFastRomEnabled()][addr >> 8]);
|
||||
}
|
||||
|
||||
void MemoryManager::IncrementMasterClockValue(uint16_t value)
|
||||
{
|
||||
_masterClock += value;
|
||||
while(_lastMasterClock < _masterClock - 3) {
|
||||
_cyclesToRun += value;
|
||||
|
||||
if(_cyclesToRun >= 12) {
|
||||
_cyclesToRun -= 12;
|
||||
_ppu->Exec();
|
||||
_ppu->Exec();
|
||||
_ppu->Exec();
|
||||
} else if(_cyclesToRun >= 8) {
|
||||
_cyclesToRun -= 8;
|
||||
_ppu->Exec();
|
||||
_ppu->Exec();
|
||||
} else if(_cyclesToRun >= 4) {
|
||||
_cyclesToRun -= 4;
|
||||
_ppu->Exec();
|
||||
_lastMasterClock += 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,17 +23,16 @@ private:
|
|||
shared_ptr<RegisterHandlerA> _registerHandlerA;
|
||||
shared_ptr<RegisterHandlerB> _registerHandlerB;
|
||||
|
||||
InternalRegisters* _regs;
|
||||
InternalRegisters *_regs;
|
||||
shared_ptr<Ppu> _ppu;
|
||||
|
||||
IMemoryHandler* _handlers[0x100 * 0x10];
|
||||
vector<unique_ptr<RamHandler>> _workRamHandlers;
|
||||
|
||||
uint8_t * _workRam;
|
||||
uint8_t *_workRam;
|
||||
|
||||
uint64_t _masterClock;
|
||||
uint8_t _previousSpeed;
|
||||
uint64_t _lastMasterClock;
|
||||
uint64_t _cyclesToRun;
|
||||
uint8_t _masterClockTable[2][0x10000];
|
||||
|
||||
public:
|
||||
|
@ -67,8 +66,9 @@ template<uint16_t value>
|
|||
void MemoryManager::IncrementMasterClockValue()
|
||||
{
|
||||
_masterClock += value;
|
||||
while(_lastMasterClock < _masterClock - 3) {
|
||||
_cyclesToRun += value;
|
||||
while(_cyclesToRun >= 4) {
|
||||
_cyclesToRun -= 4;
|
||||
_ppu->Exec();
|
||||
_lastMasterClock += 4;
|
||||
}
|
||||
}
|
56
Core/Ppu.cpp
56
Core/Ppu.cpp
|
@ -71,9 +71,19 @@ PpuState Ppu::GetState()
|
|||
|
||||
void Ppu::Exec()
|
||||
{
|
||||
//"normally dots 323 and 327 are 6 master cycles instead of 4."
|
||||
//Add 1 extra dot to compensate (0-340 instead of 0-339)
|
||||
//TODO fix this properly
|
||||
if(_cycle == 340) {
|
||||
_cycle = -1;
|
||||
_scanline++;
|
||||
|
||||
_drawStartX = 0;
|
||||
_drawEndX = 0;
|
||||
_pixelsDrawn = 0;
|
||||
_subPixelsDrawn = 0;
|
||||
memset(_rowPixelFlags, 0, sizeof(_rowPixelFlags));
|
||||
memset(_subScreenFilled, 0, sizeof(_subScreenFilled));
|
||||
|
||||
if(_scanline == (_overscanMode ? 240 : 225)) {
|
||||
//Reset OAM address at the start of vblank?
|
||||
|
@ -93,8 +103,9 @@ void Ppu::Exec()
|
|||
}
|
||||
} else if(_scanline == 240 && _frameCount & 0x01) {
|
||||
//Skip 1 tick every other frame
|
||||
//TODO : some modes don't skip this?
|
||||
_cycle++;
|
||||
} else if(_scanline == 261) {
|
||||
} else if(_scanline == 262) {
|
||||
_regs->SetNmiFlag(false);
|
||||
_scanline = 0;
|
||||
_rangeOver = false;
|
||||
|
@ -109,6 +120,7 @@ void Ppu::Exec()
|
|||
if(_regs->IsVerticalIrqEnabled() && !_regs->IsHorizontalIrqEnabled() && _scanline == _regs->GetVerticalTimer()) {
|
||||
//An IRQ will occur sometime just after the V Counter reaches the value set in $4209/$420A.
|
||||
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
|
||||
_irqDelay = 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,18 +129,24 @@ void Ppu::Exec()
|
|||
|
||||
if(_regs->IsHorizontalIrqEnabled() && _cycle == _regs->GetHorizontalTimer() && (!_regs->IsVerticalIrqEnabled() || _scanline == _regs->GetVerticalTimer())) {
|
||||
//An IRQ will occur sometime just after the H Counter reaches the value set in $4207/$4208.
|
||||
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
|
||||
_irqDelay = 4;
|
||||
}
|
||||
|
||||
if(_irqDelay > 0) {
|
||||
_irqDelay--;
|
||||
if(_irqDelay == 0) {
|
||||
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
|
||||
}
|
||||
}
|
||||
|
||||
if(_cycle == 278 && _scanline <= (_overscanMode ? 239 : 224)) {
|
||||
if(_scanline != 0) {
|
||||
RenderScanline();
|
||||
} else {
|
||||
EvaluateNextLineSprites();
|
||||
}
|
||||
EvaluateNextLineSprites();
|
||||
_console->GetDmaController()->ProcessHdmaChannels();
|
||||
} else if(_cycle == 134) {
|
||||
//TODO Approximation
|
||||
//TODO Approximation (DRAM refresh timing is not exact)
|
||||
_console->GetMemoryManager()->IncrementMasterClockValue<40>();
|
||||
}
|
||||
}
|
||||
|
@ -380,10 +398,7 @@ void Ppu::RenderMode7()
|
|||
|
||||
void Ppu::RenderScanline()
|
||||
{
|
||||
_pixelsDrawn = 0;
|
||||
_subPixelsDrawn = 0;
|
||||
memset(_rowPixelFlags, 0, sizeof(_rowPixelFlags));
|
||||
memset(_subScreenFilled, 0, sizeof(_subScreenFilled));
|
||||
_drawEndX = std::min(_cycle - 22, 255);
|
||||
|
||||
if(_forcedVblank) {
|
||||
RenderBgColor<true>();
|
||||
|
@ -437,9 +452,8 @@ void Ppu::RenderScanline()
|
|||
ApplyColorMath();
|
||||
ApplyBrightness<true>();
|
||||
ApplyHiResMode();
|
||||
|
||||
//Process sprites for next scanline
|
||||
EvaluateNextLineSprites();
|
||||
|
||||
_drawStartX = _drawEndX + 1;
|
||||
}
|
||||
|
||||
template<bool forMainScreen>
|
||||
|
@ -450,7 +464,7 @@ void Ppu::RenderBgColor()
|
|||
}
|
||||
|
||||
uint16_t bgColor = _cgram[0] | (_cgram[1] << 8);
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x <= _drawEndX; x++) {
|
||||
if(forMainScreen) {
|
||||
if(!_rowPixelFlags[x]) {
|
||||
uint8_t pixelFlags = PixelFlags::Filled | ((_colorMathEnabled & 0x20) ? PixelFlags::AllowColorMath : 0);
|
||||
|
@ -484,7 +498,7 @@ void Ppu::RenderSprites()
|
|||
}
|
||||
|
||||
if(forMainScreen) {
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x <= _drawEndX; x++) {
|
||||
if(!_rowPixelFlags[x] && _spritePriority[x] == priority) {
|
||||
if(activeWindowCount && ProcessMaskWindow<Ppu::SpriteLayerIndex>(activeWindowCount, x)) {
|
||||
//This pixel was masked
|
||||
|
@ -496,7 +510,7 @@ void Ppu::RenderSprites()
|
|||
}
|
||||
}
|
||||
} else {
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x < _drawEndX; x++) {
|
||||
if(!_subScreenFilled[x] && _spritePriority[x] == priority) {
|
||||
if(activeWindowCount && ProcessMaskWindow<Ppu::SpriteLayerIndex>(activeWindowCount, x)) {
|
||||
//This pixel was masked
|
||||
|
@ -562,7 +576,7 @@ void Ppu::RenderTilemap()
|
|||
/* The tilemap address to read the tile data from */
|
||||
uint16_t addr;
|
||||
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x <= _drawEndX; x++) {
|
||||
if(hiResMode) {
|
||||
realX = (x << 1) + (forMainScreen ? 1 : 0);
|
||||
} else {
|
||||
|
@ -791,7 +805,7 @@ void Ppu::RenderTilemapMode7()
|
|||
|
||||
uint8_t pixelFlags = PixelFlags::Filled | (((_colorMathEnabled >> layerIndex) & 0x01) ? PixelFlags::AllowColorMath : 0);
|
||||
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x <= _drawEndX; x++) {
|
||||
uint16_t realX = _mode7.HorizontalMirroring ? (255 - x) : x;
|
||||
|
||||
if(forMainScreen) {
|
||||
|
@ -884,7 +898,7 @@ void Ppu::ApplyColorMath()
|
|||
|
||||
uint8_t activeWindowCount = (uint8_t)_window[0].ActiveLayers[Ppu::ColorWindowIndex] + (uint8_t)_window[1].ActiveLayers[Ppu::ColorWindowIndex];
|
||||
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x < _drawEndX; x++) {
|
||||
if(_rowPixelFlags[x] & PixelFlags::AllowColorMath) {
|
||||
uint8_t halfShift = _colorMathHalveResult ? 1 : 0;
|
||||
uint16_t &mainPixel = _mainScreenBuffer[x];
|
||||
|
@ -966,7 +980,7 @@ template<bool forMainScreen>
|
|||
void Ppu::ApplyBrightness()
|
||||
{
|
||||
if(_screenBrightness != 15) {
|
||||
for(int x = 0; x < 256; x++) {
|
||||
for(int x = _drawStartX; x < _drawEndX; x++) {
|
||||
uint16_t &pixel = (forMainScreen ? _mainScreenBuffer : _subScreenBuffer)[x];
|
||||
uint16_t r = (pixel & 0x1F) * _screenBrightness / 15;
|
||||
uint16_t g = ((pixel >> 5) & 0x1F) * _screenBrightness / 15;
|
||||
|
@ -1222,6 +1236,10 @@ uint8_t Ppu::Read(uint16_t addr)
|
|||
|
||||
void Ppu::Write(uint32_t addr, uint8_t value)
|
||||
{
|
||||
if(_scanline < (_overscanMode ? 239 : 224) && _scanline > 0 && _cycle >= 22 && _cycle <= 277) {
|
||||
RenderScanline();
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2100:
|
||||
_forcedVblank = (value & 0x80) != 0;
|
||||
|
|
|
@ -51,6 +51,10 @@ private:
|
|||
uint16_t _cycle = 0;
|
||||
uint16_t _scanline = 0;
|
||||
uint32_t _frameCount = 0;
|
||||
|
||||
uint8_t _drawStartX = 0;
|
||||
uint8_t _drawEndX = 0;
|
||||
uint8_t _irqDelay = 0;
|
||||
|
||||
uint8_t _bgMode = 0;
|
||||
bool _mode1Bg3Priority = false;
|
||||
|
|
Loading…
Add table
Reference in a new issue