@@ -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
817810template <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
15141536template <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