Skip to content

Commit 044f7ec

Browse files
authored
Merge pull request #8 from 100thCoin/master
Various accuracy improvements
2 parents 449e2b7 + 6841fdd commit 044f7ec

3 files changed

Lines changed: 65 additions & 35 deletions

File tree

Core/NES/BaseNesPpu.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ class BaseNesPpu : public INesMemoryHandler, public ISerializable
103103
bool _needVideoRamIncrement = false;
104104
bool _allowFullPpuAccess = false;
105105

106+
uint8_t _ppuMemoryDataReadStateMachine = 0;
107+
uint8_t _ppuMemoryDataWriteStateMachine = 0;
108+
uint8_t _ppuMemoryDataWriteLatch = 0;
106109
uint8_t _memoryReadBuffer = 0;
107110
PPUStatusFlags _statusFlags = {};
108111

Core/NES/NesCpu.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ class NesCpu : public ISerializable
440440
DummyRead();
441441

442442
if(CheckPageCrossed(PC(), offset)) {
443-
DummyRead();
443+
MemoryRead(((PC() & 0xFF00) | ((PC() + offset) & 0xFF)), MemoryOperationType::DummyRead);
444444
}
445445

446446
SetPC(PC() + offset);
@@ -523,8 +523,9 @@ class NesCpu : public ISerializable
523523
}
524524

525525
void RTS() {
526-
DummyRead();
526+
MemoryRead(0x100 + SP(), MemoryOperationType::DummyRead);
527527
uint16_t addr = PopWord();
528+
SetPC(addr);
528529
DummyRead();
529530
SetPC(addr + 1);
530531
}

Core/NES/NesPpu.cpp

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,8 @@ template<class T> uint8_t NesPpu<T>::ReadRam(uint16_t addr)
378378
openBusMask = 0xFF;
379379
} else {
380380
returnValue = _memoryReadBuffer;
381-
_memoryReadBuffer = ReadVram(_ppuBusAddress & 0x3FFF, MemoryOperationType::Read);
381+
// The PPU Read Buffer is not updated until the CPU read ends, and 2 more ppu cycles have passed.
382+
_ppuMemoryDataReadStateMachine = 5;
382383

383384
if((_ppuBusAddress & 0x3FFF) >= 0x3F00 && !_console->GetNesConfig().DisablePaletteRead) {
384385
//Note: When grayscale is turned on, the read values also have the grayscale mask applied to them
@@ -395,7 +396,6 @@ template<class T> uint8_t NesPpu<T>::ReadRam(uint16_t addr)
395396

396397
_ignoreVramRead = 6;
397398
_needStateUpdate = true;
398-
_needVideoRamIncrement = true;
399399
}
400400
break;
401401

@@ -444,7 +444,7 @@ template<class T> void NesPpu<T>::WriteRam(uint16_t addr, uint8_t value)
444444
} else {
445445
//"Writes to OAMDATA during rendering (on the pre-render line and the visible lines 0-239, provided either sprite or background rendering is enabled) do not modify values in OAM,
446446
//but do perform a glitchy increment of OAMADDR, bumping only the high 6 bits"
447-
_spriteRamAddr = (_spriteRamAddr + 4) & 0xFF;
447+
_spriteRamAddr = (_spriteRamAddr + 4) & 0xFC;
448448
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesInvalidOamWrite);
449449
}
450450
break;
@@ -485,20 +485,10 @@ template<class T> void NesPpu<T>::WriteRam(uint16_t addr, uint8_t value)
485485
break;
486486

487487
case PpuRegisters::VideoMemoryData:
488-
if((_ppuBusAddress & 0x3FFF) >= 0x3F00) {
489-
WritePaletteRam(_ppuBusAddress, value);
490-
_emu->ProcessPpuWrite<CpuType::Nes>(_ppuBusAddress, value, MemoryType::NesPpuMemory);
491-
} else {
492-
if(_scanline >= 240 || !IsRenderingEnabled()) {
493-
_mapper->WriteVram(_ppuBusAddress & 0x3FFF, value);
494-
} else {
495-
//During rendering, the value written is ignored, and instead the address' LSB is used (not confirmed, based on Visual NES)
496-
_mapper->WriteVram(_ppuBusAddress & 0x3FFF, _ppuBusAddress & 0xFF);
497-
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesInvalidVramAccess);
498-
}
499-
}
488+
// The write to VRAM does not occur until the CPU write ends, and 2 more ppu cycles have passed.
489+
_ppuMemoryDataWriteStateMachine = 5;
490+
_ppuMemoryDataWriteLatch = value;
500491
_needStateUpdate = true;
501-
_needVideoRamIncrement = true;
502492
break;
503493

504494
case PpuRegisters::SpriteDMA:
@@ -674,8 +664,10 @@ template<class T> void NesPpu<T>::LoadTileInfo()
674664
_previousTilePalette = _currentTilePalette;
675665
_currentTilePalette = _tile.PaletteOffset;
676666

677-
_lowBitShift |= _tile.LowByte;
667+
_highBitShift &= 0xFF00; // This shift register pulls in 1's, so we need to mask away the lower 8 bits before bringing in the data from the tile fetch.
678668
_highBitShift |= _tile.HighByte;
669+
_lowBitShift &= 0xFF00; // Similar to the high bit plane, it's possible there are 1's in the lower 8 bits if you toggle rendering at the right time.
670+
_lowBitShift |= _tile.LowByte;
679671

680672
uint8_t tileIndex = ReadVram(GetNameTableAddr());
681673
_tile.TileAddr = (tileIndex << 4) | (_videoRamAddr >> 12) | _control.BackgroundPatternAddr;
@@ -812,6 +804,7 @@ template<class T> void NesPpu<T>::ShiftTileRegisters()
812804
{
813805
_lowBitShift <<= 1;
814806
_highBitShift <<= 1;
807+
_highBitShift |= 1;
815808
}
816809

817810
template<class T> uint8_t NesPpu<T>::GetPixelColor()
@@ -880,8 +873,9 @@ template<class T> void NesPpu<T>::ProcessScanlineImpl()
880873

881874
if(_scanline >= 0) {
882875
((T*)this)->DrawPixel();
883-
ShiftTileRegisters();
884-
876+
if(IsRenderingEnabled()) {
877+
ShiftTileRegisters();
878+
}
885879
//"Secondary OAM clear and sprite evaluation do not occur on the pre-render line"
886880
ProcessSpriteEvaluation();
887881
} else if(_cycle < 9) {
@@ -897,14 +891,6 @@ template<class T> void NesPpu<T>::ProcessScanlineImpl()
897891
}
898892
}
899893
} else if(_cycle >= 257 && _cycle <= 320) {
900-
if(_cycle == 257) {
901-
_spriteIndex = 0;
902-
memset(_hasSprite, 0, sizeof(_hasSprite));
903-
if(_prevRenderingEnabled) {
904-
//copy horizontal scrolling value from t
905-
_videoRamAddr = (_videoRamAddr & ~0x041F) | (_tmpVideoRamAddr & 0x041F);
906-
}
907-
}
908894
if(IsRenderingEnabled()) {
909895
//"OAMADDR is set to 0 during each of ticks 257-320 (the sprite tile loading interval) of the pre-render and visible scanlines." (When rendering)
910896
_spriteRamAddr = 0;
@@ -913,8 +899,8 @@ template<class T> void NesPpu<T>::ProcessScanlineImpl()
913899
//Garbage NT sprite fetch (257, 265, 273, etc.) - Required for proper MC-ACC IRQs (MMC3 clone)
914900
case 0: ReadVram(GetNameTableAddr()); break;
915901

916-
//Garbage AT sprite fetch
917-
case 2: ReadVram(GetAttributeAddr()); break;
902+
//Garbage NT sprite fetch
903+
case 2: ReadVram(GetNameTableAddr()); break;
918904

919905
//Cycle 260, 268, etc. This is an approximation (each tile is actually loaded in 8 steps (e.g from 257 to 264))
920906
case 4: LoadSpriteTileInfo(); break;
@@ -931,6 +917,14 @@ template<class T> void NesPpu<T>::ProcessScanlineImpl()
931917
LoadExtraSprites();
932918
}
933919
}
920+
if(_cycle == 257) {
921+
_spriteIndex = 0;
922+
memset(_hasSprite, 0, sizeof(_hasSprite));
923+
if(_prevRenderingEnabled) {
924+
//copy horizontal scrolling value from t
925+
_videoRamAddr = (_videoRamAddr & ~0x041F) | (_tmpVideoRamAddr & 0x041F);
926+
}
927+
}
934928
} else if(_cycle >= 321 && _cycle <= 336) {
935929
LoadTileInfo();
936930

@@ -1023,10 +1017,9 @@ template<class T> void NesPpu<T>::ProcessSpriteEvaluation()
10231017

10241018
if(_oamCopyDone && !_settings->GetNesConfig().EnablePpuSpriteEvalBug) {
10251019
_spriteAddrH = (_spriteAddrH + 1) & 0x3F;
1026-
if(_secondaryOamAddr >= 0x20) {
1027-
//"As seen above, a side effect of the OAM write disable signal is to turn writes to the secondary OAM into reads from it."
1028-
_oamCopybuffer = _secondarySpriteRam[_secondaryOamAddr & 0x1F];
1029-
}
1020+
//"As seen above, a side effect of the OAM write disable signal is to turn writes to the secondary OAM into reads from it."
1021+
_oamCopybuffer = _secondarySpriteRam[_secondaryOamAddr & 0x1F];
1022+
10301023
} else {
10311024
if(!_spriteInRange && _scanline >= _oamCopybuffer && _scanline < _oamCopybuffer + (_control.LargeSprites ? 16 : 8)) {
10321025
_spriteInRange = !_oamCopyDone;
@@ -1509,6 +1502,35 @@ template<class T> void NesPpu<T>::UpdateState()
15091502
_needVideoRamIncrement = false;
15101503
UpdateVideoRamAddr();
15111504
}
1505+
1506+
if(_ppuMemoryDataReadStateMachine > 0) {
1507+
_ppuMemoryDataReadStateMachine--;
1508+
if(_ppuMemoryDataReadStateMachine == 0) {
1509+
_memoryReadBuffer = ReadVram(_ppuBusAddress & 0x3FFF, MemoryOperationType::Read);
1510+
_needVideoRamIncrement = true;
1511+
}
1512+
_needStateUpdate = true;
1513+
}
1514+
1515+
if(_ppuMemoryDataWriteStateMachine > 0) {
1516+
_ppuMemoryDataWriteStateMachine--;
1517+
if(_ppuMemoryDataWriteStateMachine == 0) {
1518+
if((_ppuBusAddress & 0x3FFF) >= 0x3F00) {
1519+
WritePaletteRam(_ppuBusAddress, _ppuMemoryDataWriteLatch);
1520+
_emu->ProcessPpuWrite<CpuType::Nes>(_ppuBusAddress, _ppuMemoryDataWriteLatch, MemoryType::NesPpuMemory);
1521+
} else {
1522+
if(_scanline >= 240 || !IsRenderingEnabled()) {
1523+
_mapper->WriteVram(_ppuBusAddress & 0x3FFF, _ppuMemoryDataWriteLatch);
1524+
} else {
1525+
//During rendering, the value written is ignored, and instead the address' LSB is used (not confirmed, based on Visual NES)
1526+
_mapper->WriteVram(_ppuBusAddress & 0x3FFF, _ppuBusAddress & 0xFF);
1527+
_emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesInvalidVramAccess);
1528+
}
1529+
}
1530+
_needVideoRamIncrement = true;
1531+
}
1532+
_needStateUpdate = true;
1533+
}
15121534
}
15131535

15141536
template<class T> uint32_t NesPpu<T>::GetPixelBrightness(uint8_t x, uint8_t y)
@@ -1566,6 +1588,10 @@ template<class T> void NesPpu<T>::Serialize(Serializer& s)
15661588

15671589
SV(_allowFullPpuAccess);
15681590

1591+
SV(_ppuMemoryDataReadStateMachine);
1592+
SV(_ppuMemoryDataWriteStateMachine);
1593+
SV(_ppuMemoryDataWriteLatch);
1594+
15691595
for(int i = 0; i < _spriteCount; i++) {
15701596
SVI(_spriteTiles[i].SpriteX); SVI(_spriteTiles[i].LowByte); SVI(_spriteTiles[i].HighByte); SVI(_spriteTiles[i].PaletteOffset); SVI(_spriteTiles[i].HorizontalMirror); SVI(_spriteTiles[i].BackgroundPriority);
15711597
}

0 commit comments

Comments
 (0)