GB: PPU accuracy improvements

Passes all moooneye-gb tests
This commit is contained in:
Sour 2020-05-30 23:23:35 -04:00
parent a9c456b75a
commit 2ccef88375
12 changed files with 297 additions and 125 deletions

View file

@ -353,22 +353,30 @@ void GbCpu::ExecOpCode(uint8_t opCode)
}
void GbCpu::IncCycleCount()
{
_memoryManager->Exec();
_memoryManager->Exec();
}
void GbCpu::HalfCycle()
{
_memoryManager->Exec();
}
uint8_t GbCpu::ReadOpCode()
{
IncCycleCount();
HalfCycle();
uint8_t value = _memoryManager->Read(_state.PC, MemoryOperationType::ExecOpCode);
HalfCycle();
_state.PC++;
return value;
}
uint8_t GbCpu::ReadCode()
{
IncCycleCount();
HalfCycle();
uint8_t value = _memoryManager->Read(_state.PC, MemoryOperationType::ExecOperand);
HalfCycle();
_state.PC++;
return value;
}
@ -382,14 +390,17 @@ uint16_t GbCpu::ReadCodeWord()
uint8_t GbCpu::Read(uint16_t addr)
{
IncCycleCount();
return _memoryManager->Read(addr, MemoryOperationType::Read);
HalfCycle();
uint8_t value = _memoryManager->Read(addr, MemoryOperationType::Read);
HalfCycle();
return value;
}
void GbCpu::Write(uint16_t addr, uint8_t value)
{
IncCycleCount();
HalfCycle();
_memoryManager->Write(addr, value);
HalfCycle();
}
bool GbCpu::CheckFlag(uint8_t flag)

View file

@ -30,6 +30,7 @@ public:
void ExecOpCode(uint8_t opCode);
__forceinline void IncCycleCount();
__forceinline void HalfCycle();
__forceinline uint8_t ReadOpCode();
__forceinline uint8_t ReadCode();
__forceinline uint16_t ReadCodeWord();

View file

@ -110,16 +110,18 @@ void GbDmaController::ProcessDmaBlock()
//TODO check invalid dma sources/etc.
//4 cycles for setup
_memoryManager->Exec();
_memoryManager->Exec();
for(int i = 0; i < 16; i++) {
uint16_t dst = 0x8000 | ((_state.CgbDmaDest + i) & 0x1FFF);
//2 or 4 cycles per byte transfered (2x more cycles in high speed mode - effective speed is the same in both modes
if(_memoryManager->IsHighSpeed() || (i & 0x01)) {
//TODO: Not perfectly accurate (in "slow" mode mode both occur on the same cycle)
_memoryManager->Exec();
uint8_t value = _memoryManager->Read(_state.CgbDmaSource + i, MemoryOperationType::DmaRead);
if(_memoryManager->IsHighSpeed()) {
_memoryManager->Exec();
}
_memoryManager->Write(dst, _memoryManager->Read(_state.CgbDmaSource + i, MemoryOperationType::DmaRead));
_memoryManager->Write(dst, value);
}
//Source/Dest/Length are all modified by the DMA process and keep their last value after DMA completes

View file

@ -74,11 +74,13 @@ void GbMemoryManager::RefreshMappings()
void GbMemoryManager::Exec()
{
_state.CycleCount += 4;
_state.ApuCycleCount += _state.CgbHighSpeed ? 2 : 4;
_state.CycleCount += 2;
_state.ApuCycleCount += _state.CgbHighSpeed ? 1 : 2;
_timer->Exec();
_ppu->Exec();
_dmaController->Exec();
if((_state.CycleCount & 0x03) == 0) {
_dmaController->Exec();
}
if(_state.SerialBitCount && (_state.CycleCount & 0x1FF) == 0) {
_state.SerialData = (_state.SerialData << 1) | 0x01;

View file

@ -38,6 +38,7 @@ void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryMana
_currentEventViewerBuffer = _eventViewerBuffers[0];
_state = {};
_state.Cycle = -1;
_state.Mode = PpuMode::HBlank;
_state.CgbEnabled = _gameboy->IsCgb();
_lastFrameTime = 0;
@ -96,69 +97,40 @@ void GbPpu::Exec()
return;
}
uint8_t cyclesToRun = _memoryManager->IsHighSpeed() ? 2 : 4;
uint8_t cyclesToRun = _memoryManager->IsHighSpeed() ? 1 : 2;
for(int i = 0; i < cyclesToRun; i++) {
_state.Cycle++;
if(_state.IdleCycles > 0) {
_state.IdleCycles--;
ProcessPpuCycle();
continue;
}
ExecCycle();
}
}
void GbPpu::ExecCycle()
{
_state.Cycle++;
PpuMode oldMode = _state.IrqMode;
PpuMode oldMode = _state.Mode;
bool oldCoincidenceFlag = _state.LyCoincidenceFlag;
switch(_state.Cycle) {
case 4: {
if(_state.Scanline < 144) {
_spriteCount = 0;
ChangeMode(PpuMode::OamEvaluation);
} else if(_state.Scanline == 144) {
ChangeMode(PpuMode::VBlank);
_windowCounter = -1;
_memoryManager->RequestIrq(GbIrqSource::VerticalBlank);
SendFrame();
}
break;
if(_state.Scanline < 144) {
if(_state.Scanline == 0 && _isFirstFrame) {
ProcessFirstScanlineAfterPowerOn();
} else {
ProcessVisibleScanline();
}
case 84: {
if(_state.Scanline < 144) {
_latchWindowX = _state.WindowX;
_latchWindowY = _state.WindowY;
_latchWindowEnabled = _state.WindowEnabled;
ChangeMode(PpuMode::Drawing);
ResetRenderer();
}
break;
}
case 456: {
_state.Cycle = 0;
_state.Scanline++;
if(_state.Scanline < 144) {
ChangeMode(PpuMode::HBlank);
} else if(_state.Scanline == 154) {
_state.Scanline = 0;
ChangeMode(PpuMode::HBlank);
_console->ProcessEvent(EventType::StartFrame);
if(_console->IsDebugging()) {
_currentEventViewerBuffer = _currentEventViewerBuffer == _eventViewerBuffers[0] ? _eventViewerBuffers[1] : _eventViewerBuffers[0];
}
}
break;
}
}
} else {
ProcessVblankScanline();
}
if(_state.Mode == PpuMode::Drawing) {
if(_drawnPixels < 160) {
RunDrawCycle();
} else {
ChangeMode(PpuMode::HBlank);
RunDrawCycle();
if(_drawnPixels == 160) {
//Mode turns to hblank on the same cycle as the last pixel is output (IRQ is on next cycle)
_state.Mode = PpuMode::HBlank;
if(_state.Scanline < 143) {
//"This mode will transfer one block (16 bytes) during each H-Blank. No data is transferred during VBlank (LY = 143 153"
//"This mode will transfer one block (16 bytes) during each H-Blank. No data is transferred during VBlank (LY = 143 153)"
_dmaController->ProcessHdma();
}
}
@ -166,14 +138,161 @@ void GbPpu::ExecCycle()
RunSpriteEvaluation();
}
UpdateLyCoincidenceFlag();
if(_state.Mode != oldMode || _state.LyCoincidenceFlag != oldCoincidenceFlag) {
bool coincidenceFlag = (_state.LyCompare == _state.LyForCompare);
if(_state.IrqMode != oldMode || _state.LyCoincidenceFlag != coincidenceFlag) {
_state.LyCoincidenceFlag = coincidenceFlag;
UpdateStatIrq();
}
ProcessPpuCycle();
}
void GbPpu::ProcessVblankScanline()
{
switch(_state.Cycle) {
case 2:
if(_state.Scanline == 144) {
_state.IrqMode = PpuMode::OamEvaluation;
}
break;
case 4:
if(_state.Scanline < 153) {
_state.LyForCompare = _state.Scanline;
if(_state.Scanline == 144) {
_state.Mode = PpuMode::VBlank;
_state.IrqMode = PpuMode::VBlank;
_windowCounter = -1;
_memoryManager->RequestIrq(GbIrqSource::VerticalBlank);
SendFrame();
}
}
break;
case 6:
if(_state.Scanline == 153) {
_state.Ly = 0;
_state.LyForCompare = _state.Scanline;
}
break;
case 8:
if(_state.Scanline == 153) {
_state.LyForCompare = -1;
}
break;
case 12:
if(_state.Scanline == 153) {
_state.LyForCompare = 0;
}
_state.IdleCycles = 456 - 12 - 1;
break;
case 456:
_state.Cycle = 0;
_state.Scanline++;
if(_state.Scanline == 154) {
_state.Scanline = 0;
_console->ProcessEvent(EventType::StartFrame);
if(_console->IsDebugging()) {
_currentEventViewerBuffer = _currentEventViewerBuffer == _eventViewerBuffers[0] ? _eventViewerBuffers[1] : _eventViewerBuffers[0];
}
} else {
_state.Ly = _state.Scanline;
_state.LyForCompare = -1;
}
break;
}
}
void GbPpu::ProcessFirstScanlineAfterPowerOn()
{
switch(_state.Cycle) {
case 1:
_state.IrqMode = PpuMode::NoIrq;
break;
case 79:
_latchWindowX = _state.WindowX;
_latchWindowY = _state.WindowY;
_latchWindowEnabled = _state.WindowEnabled;
_state.Mode = PpuMode::Drawing;
_state.IrqMode = PpuMode::Drawing;
ResetRenderer();
_rendererIdle = true;
break;
case 84:
_rendererIdle = false;
break;
case 448:
_state.Cycle = 0;
_state.Scanline++;
_drawnPixels = 0;
_state.Mode = PpuMode::HBlank;
_state.IrqMode = PpuMode::HBlank;
break;
}
}
void GbPpu::ProcessVisibleScanline()
{
if(_drawnPixels == 160) {
//IRQ flag for Hblank is 1 cycle late compared to the mode register
_state.IrqMode = PpuMode::HBlank;
_drawnPixels = 0;
_state.IdleCycles = 456 - _state.Cycle - 1;
}
switch(_state.Cycle) {
case 3:
_state.Ly = _state.Scanline;
_state.LyForCompare = -1;
if(_state.Scanline > 0) {
//On scanlines 1-143, the OAM IRQ fires 1 cycle early
_state.IrqMode = PpuMode::OamEvaluation;
} else {
//On scanline 0, hblank gets set for 1 cycle here
_state.Mode = PpuMode::HBlank;
}
break;
case 4:
_spriteCount = 0;
_state.LyForCompare = _state.Scanline;
_state.Mode = PpuMode::OamEvaluation;
_state.IrqMode = PpuMode::OamEvaluation; //TODO set to noirq?
break;
case 84:
_latchWindowX = _state.WindowX;
_latchWindowY = _state.WindowY;
_latchWindowEnabled = _state.WindowEnabled;
_state.Mode = PpuMode::Drawing;
_state.IrqMode = PpuMode::Drawing;
_rendererIdle = true;
ResetRenderer();
break;
case 89:
_rendererIdle = false;
break;
case 456:
_state.Cycle = 0;
_state.Scanline++;
if(_state.Scanline == 144) {
_state.Ly = 144;
_state.LyForCompare = -1;
}
break;
}
}
void GbPpu::ProcessPpuCycle()
{
if(_console->IsDebugging()) {
@ -192,7 +311,7 @@ void GbPpu::ProcessPpuCycle()
void GbPpu::RunDrawCycle()
{
if(_state.Cycle < 89) {
if(_rendererIdle) {
//Idle cycles
_evtColor = EvtColor::RenderingIdle;
return;
@ -213,6 +332,14 @@ void GbPpu::RunDrawCycle()
return;
}
FindNextSprite();
if(_fetchSprite >= 0 && _bgFetcher.Step >= 5 && _bgFifo.Size > 0) {
_evtColor = EvtColor::RenderingOamLoad;
ClockSpriteFetcher();
FindNextSprite();
return;
}
if(_fetchSprite == -1 && _bgFifo.Size > 0) {
if(_drawnPixels >= 0) {
uint16_t outOffset = _state.Scanline * 256 + _drawnPixels;
@ -332,14 +459,6 @@ void GbPpu::FindNextSprite()
void GbPpu::ClockTileFetcher()
{
FindNextSprite();
if(_fetchSprite >= 0 && _bgFetcher.Step >= 5 && _bgFifo.Size > 0) {
_evtColor = EvtColor::RenderingOamLoad;
ClockSpriteFetcher();
FindNextSprite();
return;
}
_evtColor = EvtColor::RenderingBgLoad;
switch(_bgFetcher.Step++) {
@ -441,32 +560,14 @@ void GbPpu::PushTileToPixelFifo()
_bgFetcher.Step = 0;
}
void GbPpu::ChangeMode(PpuMode mode)
{
_state.Mode = mode;
}
void GbPpu::UpdateLyCoincidenceFlag()
{
if(_state.Scanline < 153) {
_state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline) && (_state.Cycle >= 4 || _state.Scanline == 0);
} else {
if(_state.LyCompare == 153) {
_state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline) && _state.Cycle >= 4 && _state.Cycle < 8;
} else {
_state.LyCoincidenceFlag = (_state.LyCompare == 0) && _state.Cycle >= 12;
}
}
}
void GbPpu::UpdateStatIrq()
{
bool irqFlag = (
_state.LcdEnabled &&
((_state.LyCoincidenceFlag && (_state.Status & GbPpuStatusFlags::CoincidenceIrq)) ||
(_state.Mode == PpuMode::HBlank && (_state.Status & GbPpuStatusFlags::HBlankIrq)) ||
(_state.Mode == PpuMode::OamEvaluation && (_state.Status & GbPpuStatusFlags::OamIrq)) ||
(_state.Mode == PpuMode::VBlank && ((_state.Status & GbPpuStatusFlags::VBlankIrq) || (_state.Status & GbPpuStatusFlags::OamIrq))))
(_state.IrqMode == PpuMode::HBlank && (_state.Status & GbPpuStatusFlags::HBlankIrq)) ||
(_state.IrqMode == PpuMode::OamEvaluation && (_state.Status & GbPpuStatusFlags::OamIrq)) ||
(_state.IrqMode == PpuMode::VBlank && (_state.Status & GbPpuStatusFlags::VBlankIrq)))
);
if(irqFlag && !_state.StatIrqFlag) {
@ -546,11 +647,7 @@ uint8_t GbPpu::Read(uint16_t addr)
case 0xFF42: return _state.ScrollY; //FF42 - SCY - Scroll Y (R/W)
case 0xFF43: return _state.ScrollX; //FF43 - SCX - Scroll X (R/W)
case 0xFF44:
//FF44 - LY - LCDC Y-Coordinate (R)
//On the last scanline (153), at cycle >= 4 (TODO: exact cycle varies by model) , the value 0 is returned instead of 153
return (_state.Scanline < 153 || _state.Cycle < 4) ? _state.Scanline : 0;
case 0xFF44: return _state.Ly; //FF44 - LY - LCDC Y-Coordinate (R)
case 0xFF45: return _state.LyCompare; //FF45 - LYC - LY Compare (R/W)
case 0xFF47: return _state.BgPalette; //FF47 - BGP - BG Palette Data (R/W) - Non CGB Mode Only
case 0xFF48: return _state.ObjPalette0; //FF48 - OBP0 - Object Palette 0 Data (R/W) - Non CGB Mode Only
@ -575,7 +672,10 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
//Reset LCD to top of screen when it gets turned off
_state.Cycle = 0;
_state.Scanline = 0;
ChangeMode(PpuMode::HBlank);
_state.Ly = 0;
_state.LyForCompare = -1;
_state.Mode = PpuMode::HBlank;
_state.IrqMode = PpuMode::HBlank;
//Send a blank (white) frame
_lastFrameTime = _gameboy->GetCycleCount();
@ -587,11 +687,15 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
_dmaController->ProcessHdma();
} else {
_isFirstFrame = true;
_state.Cycle = 4;
_state.Cycle = -1;
_state.Scanline = 0;
_state.Ly = 0;
_state.LyForCompare = 0;
_state.IdleCycles = 0;
ResetRenderer();
ChangeMode(PpuMode::HBlank);
UpdateLyCoincidenceFlag();
_state.Mode = PpuMode::HBlank;
_state.IrqMode = PpuMode::HBlank;
_state.LyCoincidenceFlag = _state.LyCompare == _state.LyForCompare;
UpdateStatIrq();
_console->ProcessEvent(EventType::StartFrame);
@ -619,7 +723,7 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
case 0xFF42: _state.ScrollY = value; break;
case 0xFF43: _state.ScrollX = value; break;
case 0xFF45: _state.LyCompare = value; break;
case 0xFF45: _state.LyCompare = value; _state.IdleCycles = 0; break;
case 0xFF47: _state.BgPalette = value; break;
case 0xFF48: _state.ObjPalette0 = value; break;
case 0xFF49: _state.ObjPalette1 = value; break;
@ -634,7 +738,7 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
uint8_t GbPpu::ReadVram(uint16_t addr)
{
if((int)_state.Mode <= (int)PpuMode::OamEvaluation) {
if(_state.Mode <= PpuMode::VBlank || (_state.Mode == PpuMode::OamEvaluation && _state.Cycle < 80)) {
return _vram[(_state.CgbVramBank << 13) | (addr & 0x1FFF)];
} else {
return 0xFF;
@ -643,7 +747,7 @@ uint8_t GbPpu::ReadVram(uint16_t addr)
void GbPpu::WriteVram(uint16_t addr, uint8_t value)
{
if((int)_state.Mode <= (int)PpuMode::OamEvaluation) {
if(_state.Mode <= PpuMode::OamEvaluation) {
_vram[(_state.CgbVramBank << 13) | (addr & 0x1FFF)] = value;
}
}
@ -651,10 +755,22 @@ void GbPpu::WriteVram(uint16_t addr, uint8_t value)
uint8_t GbPpu::ReadOam(uint8_t addr)
{
if(addr < 0xA0) {
if((int)_state.Mode >= (int)PpuMode::OamEvaluation || _memoryManager->IsOamDmaRunning()) {
if(_memoryManager->IsOamDmaRunning()) {
return 0xFF;
}
if(_state.Scanline == 0 && _isFirstFrame) {
if(_state.Mode == PpuMode::HBlank) {
return _oam[addr];
} else {
return 0xFF;
}
} else {
return _oam[addr];
if(_state.Mode == PpuMode::VBlank || (_state.Mode == PpuMode::HBlank && _state.Cycle != 3)) {
return _oam[addr];
} else {
return 0xFF;
}
}
}
return 0;
@ -664,10 +780,22 @@ void GbPpu::WriteOam(uint8_t addr, uint8_t value, bool forDma)
{
//During DMA or rendering/oam evaluation, ignore writes to OAM
//The DMA controller is always allowed to write to OAM (presumably the PPU can't read OAM during that time? TODO implement)
//On the DMG, there is apparently a ~4 clock gap (80 to 84) between OAM evaluation & rendering where writing is allowed?
//On the DMG, there is a 4 clock gap (80 to 83) between OAM evaluation & rendering where writing is allowed
if(addr < 0xA0) {
if(forDma || ((int)_state.Mode <= (int)PpuMode::VBlank && !_memoryManager->IsOamDmaRunning()) || (_state.Cycle >= 80 && _state.Cycle < 84)) {
if(forDma) {
_oam[addr] = value;
} else if(_memoryManager->IsOamDmaRunning()) {
return;
}
if(_state.Scanline == 0 && _isFirstFrame) {
if(_state.Mode == PpuMode::HBlank && _state.Cycle != 77 && _state.Cycle != 78) {
_oam[addr] = value;
}
} else {
if(_state.Mode <= PpuMode::VBlank || (_state.Cycle >= 80 && _state.Cycle < 84)) {
_oam[addr] = value;
}
}
}
}
@ -744,7 +872,8 @@ void GbPpu::Serialize(Serializer& s)
_state.Status, _state.FrameCount, _lastFrameTime, _state.LyCoincidenceFlag,
_state.CgbBgPalAutoInc, _state.CgbBgPalPosition,
_state.CgbObjPalAutoInc, _state.CgbObjPalPosition, _state.CgbVramBank, _state.CgbEnabled,
_latchWindowX, _latchWindowY, _latchWindowEnabled, _windowCounter, _isFirstFrame
_latchWindowX, _latchWindowY, _latchWindowEnabled, _windowCounter, _isFirstFrame, _rendererIdle,
_state.IdleCycles, _state.Ly, _state.LyForCompare, _state.IrqMode
);
s.StreamArray(_state.CgbBgPalettes, 4 * 8);

View file

@ -49,10 +49,14 @@ private:
uint8_t _spriteIndexes[10] = {};
bool _isFirstFrame = true;
bool _rendererIdle = false;
__forceinline void ProcessPpuCycle();
__forceinline void ExecCycle();
__forceinline void ProcessVblankScanline();
void ProcessFirstScanlineAfterPowerOn();
__forceinline void ProcessVisibleScanline();
__forceinline void RunDrawCycle();
__forceinline void RunSpriteEvaluation();
void ResetRenderer();
@ -62,9 +66,7 @@ private:
__forceinline void PushSpriteToPixelFifo();
__forceinline void PushTileToPixelFifo();
__forceinline void ChangeMode(PpuMode mode);
__forceinline void UpdateLyCoincidenceFlag();
__forceinline void UpdateStatIrq();
void UpdateStatIrq();
void WriteCgbPalette(uint8_t& pos, uint16_t* pal, bool autoInc, uint8_t value);

View file

@ -23,11 +23,13 @@ GbTimer::~GbTimer()
void GbTimer::Exec()
{
_state.Reloaded = false;
if(_state.NeedReload) {
ReloadCounter();
if((_state.Divider & 0x03) == 2) {
_state.Reloaded = false;
if(_state.NeedReload) {
ReloadCounter();
}
}
SetDivider(_state.Divider + 4);
SetDivider(_state.Divider + 2);
}
void GbTimer::ReloadCounter()

View file

@ -21,8 +21,6 @@ public:
GbTimer(GbMemoryManager* memoryManager, GbApu* apu);
virtual ~GbTimer();
void Reset();
void Exec();
uint8_t Read(uint16_t addr);

View file

@ -87,7 +87,8 @@ enum class PpuMode
HBlank,
VBlank,
OamEvaluation,
Drawing
Drawing,
NoIrq,
};
namespace GbPpuStatusFlags
@ -152,8 +153,13 @@ struct GbPpuState
{
uint8_t Scanline;
uint16_t Cycle;
uint16_t IdleCycles;
PpuMode Mode;
PpuMode IrqMode;
bool StatIrqFlag;
uint8_t Ly;
int16_t LyForCompare;
uint8_t LyCompare;
bool LyCoincidenceFlag;

View file

@ -187,7 +187,7 @@ namespace Mesen.GUI.Debugger
new RegEntry("", "LCD Registers", null),
new RegEntry("$FF42", "Scroll Y (SCY)", ppu.ScrollY, Format.X8),
new RegEntry("$FF43", "Scroll X (SCX)", ppu.ScrollX, Format.X8),
new RegEntry("$FF44", "Y-Coordinate (LY)", (ppu.Scanline < 153 || ppu.Cycle < 4) ? ppu.Scanline : 0, Format.X8),
new RegEntry("$FF44", "Y-Coordinate (LY)", ppu.Ly, Format.X8),
new RegEntry("$FF45", "LY Compare (LYC)", ppu.LyCompare, Format.X8),
new RegEntry("$FF47", "BG Palette (BGP)", ppu.BgPalette, Format.X8),
new RegEntry("$FF48", "OBJ Palette 0 (OBP0)", ppu.ObjPalette0, Format.X8),

View file

@ -298,8 +298,21 @@ namespace Mesen.GUI.Debugger
mnuStepOver.Click += (s, e) => { DebugApi.Step(_cpuType, 1, StepType.StepOver); };
mnuStepOut.Click += (s, e) => { DebugApi.Step(_cpuType, 1, StepType.StepOut); };
mnuRunPpuCycle.Click += (s, e) => { DebugApi.Step(_cpuType, 1, StepType.PpuStep); };
mnuRunScanline.Click += (s, e) => { DebugApi.Step(_cpuType, 341, StepType.PpuStep); };
mnuRunOneFrame.Click += (s, e) => { DebugApi.Step(_cpuType, 341 * 262, StepType.PpuStep); }; //TODO ntsc/pal
mnuRunScanline.Click += (s, e) => {
if(_cpuType == CpuType.Gameboy) {
DebugApi.Step(_cpuType, 456, StepType.PpuStep);
} else {
DebugApi.Step(_cpuType, 341, StepType.PpuStep);
}
};
mnuRunOneFrame.Click += (s, e) => {
if(_cpuType == CpuType.Gameboy) {
DebugApi.Step(_cpuType, 456*154, StepType.PpuStep);
} else {
//TODO ntsc/pal
DebugApi.Step(_cpuType, 341 * 262, StepType.PpuStep);
}
};
mnuContinue.Click += (s, e) => { DebugApi.ResumeExecution(); };
mnuBreak.Click += (s, e) => { DebugApi.Step(_cpuType, 1, StepType.Step); };

View file

@ -708,16 +708,22 @@ namespace Mesen.GUI
HBlank,
VBlank,
OamEvaluation,
Drawing
Drawing,
NoIrq
}
public struct GbPpuState
{
public byte Scanline;
public UInt16 Cycle;
public UInt16 IdleCycles;
public PpuMode Mode;
public PpuMode IrqMode;
[MarshalAs(UnmanagedType.I1)] public bool StatIrqFlag;
public byte Ly;
public Int16 LyForCompare;
public byte LyCompare;
[MarshalAs(UnmanagedType.I1)] public bool LyCoincidenceFlag;
public byte BgPalette;