2014-06-14 11:27:55 -04:00
# include "stdafx.h"
# include "PPU.h"
2014-06-14 18:20:56 -04:00
# include "CPU.h"
2017-04-29 21:39:57 -04:00
# include "APU.h"
2015-07-21 23:05:27 -04:00
# include "EmulationSettings.h"
2015-08-30 21:04:21 -04:00
# include "VideoDecoder.h"
2016-01-09 13:15:43 -05:00
# include "Debugger.h"
2017-02-23 20:01:48 -05:00
# include "BaseMapper.h"
2017-04-28 19:54:58 -04:00
# include "RewindManager.h"
2017-11-19 23:08:23 -05:00
# include "ControlManager.h"
2018-07-01 15:21:05 -04:00
# include "MemoryManager.h"
# include "Console.h"
2018-07-02 14:49:19 -04:00
# include "NotificationManager.h"
2014-06-14 11:27:55 -04:00
2018-07-01 15:21:05 -04:00
PPU : : PPU ( shared_ptr < Console > console )
2014-06-14 11:27:55 -04:00
{
2018-07-01 15:21:05 -04:00
_console = console ;
2014-06-25 17:30:35 -04:00
2015-08-30 21:04:21 -04:00
_outputBuffers [ 0 ] = new uint16_t [ 256 * 240 ] ;
_outputBuffers [ 1 ] = new uint16_t [ 256 * 240 ] ;
_currentOutputBuffer = _outputBuffers [ 0 ] ;
2017-02-25 14:20:30 -05:00
memset ( _outputBuffers [ 0 ] , 0 , 256 * 240 * sizeof ( uint16_t ) ) ;
memset ( _outputBuffers [ 1 ] , 0 , 256 * 240 * sizeof ( uint16_t ) ) ;
2014-06-23 13:52:53 -04:00
2016-01-11 19:19:06 -05:00
uint8_t paletteRamBootValues [ 0x20 ] { 0x09 , 0x01 , 0x00 , 0x01 , 0x00 , 0x02 , 0x02 , 0x0D , 0x08 , 0x10 , 0x08 , 0x24 , 0x00 , 0x00 , 0x04 , 0x2C ,
2017-04-08 14:13:10 -04:00
0x09 , 0x01 , 0x34 , 0x03 , 0x00 , 0x04 , 0x00 , 0x14 , 0x08 , 0x3A , 0x00 , 0x02 , 0x00 , 0x20 , 0x2C , 0x08 } ;
2016-01-11 19:19:06 -05:00
memcpy ( _paletteRAM , paletteRamBootValues , sizeof ( _paletteRAM ) ) ;
2017-04-08 14:13:10 -04:00
2017-02-23 20:01:48 -05:00
BaseMapper : : InitializeRam ( _spriteRAM , 0x100 ) ;
BaseMapper : : InitializeRam ( _secondarySpriteRAM , 0x20 ) ;
2016-01-01 12:33:43 -05:00
2014-06-23 13:52:53 -04:00
Reset ( ) ;
2014-06-14 18:20:56 -04:00
}
2017-04-08 14:13:10 -04:00
PPU : : ~ PPU ( )
2014-06-14 18:20:56 -04:00
{
2015-08-30 21:04:21 -04:00
delete [ ] _outputBuffers [ 0 ] ;
delete [ ] _outputBuffers [ 1 ] ;
2014-06-14 18:20:56 -04:00
}
2014-06-23 13:52:53 -04:00
void PPU : : Reset ( )
{
2016-06-12 18:11:31 -04:00
_cyclesNeeded = 0 ;
2017-12-04 00:09:11 -05:00
_needStateUpdate = false ;
2016-02-14 13:57:47 -05:00
_prevRenderingEnabled = false ;
2016-01-11 17:18:01 -05:00
_renderingEnabled = false ;
2016-01-03 17:20:18 -05:00
_ignoreVramRead = 0 ;
2016-01-02 20:36:22 -05:00
_openBus = 0 ;
memset ( _openBusDecayStamp , 0 , sizeof ( _openBusDecayStamp ) ) ;
2014-06-23 13:52:53 -04:00
_state = { } ;
_flags = { } ;
_statusFlags = { } ;
2017-03-31 22:14:16 -04:00
_previousTile = { } ;
_currentTile = { } ;
_nextTile = { } ;
2018-06-02 22:12:00 -04:00
_ppuBusAddress = 0 ;
2016-12-16 07:13:33 -05:00
_intensifyColorBits = 0 ;
2017-04-20 21:58:35 -04:00
_paletteRamMask = 0x3F ;
_lastUpdatedPixel = - 1 ;
2016-12-16 07:13:33 -05:00
_lastSprite = nullptr ;
_oamCopybuffer = 0 ;
_spriteInRange = false ;
_sprite0Added = false ;
_spriteAddrH = 0 ;
_spriteAddrL = 0 ;
_oamCopyDone = false ;
_renderingEnabled = false ;
_prevRenderingEnabled = false ;
_cyclesNeeded = 0.0 ;
2017-04-21 21:29:39 -04:00
memset ( _hasSprite , 0 , sizeof ( _hasSprite ) ) ;
2018-06-13 17:48:52 -04:00
memset ( _spriteTiles , 0 , sizeof ( _spriteTiles ) ) ;
2016-12-16 07:13:33 -05:00
_spriteCount = 0 ;
_secondaryOAMAddr = 0 ;
_sprite0Visible = false ;
_overflowSpriteAddr = 0 ;
_spriteIndex = 0 ;
_openBus = 0 ;
memset ( _openBusDecayStamp , 0 , sizeof ( _openBusDecayStamp ) ) ;
_ignoreVramRead = 0 ;
2017-04-21 21:29:39 -04:00
//First execution will be cycle 0, scanline 0
_scanline = - 1 ;
_cycle = 340 ;
2016-01-02 20:36:22 -05:00
_frameCount = 1 ;
2014-06-23 13:52:53 -04:00
_memoryReadBuffer = 0 ;
2017-03-10 21:29:48 -05:00
_overflowBugCounter = 0 ;
2017-03-10 22:21:14 -05:00
_updateVramAddrDelay = 0 ;
_updateVramAddr = 0 ;
2017-04-08 14:13:10 -04:00
memset ( _oamDecayCycles , 0 , sizeof ( _oamDecayCycles ) ) ;
_enableOamDecay = EmulationSettings : : CheckFlag ( EmulationFlags : : EnableOamDecay ) ;
2016-10-23 13:29:37 -04:00
UpdateMinimumDrawCycles ( ) ;
2014-06-23 13:52:53 -04:00
}
2015-07-21 23:05:27 -04:00
void PPU : : SetNesModel ( NesModel model )
{
_nesModel = model ;
2016-06-21 18:58:22 -04:00
switch ( _nesModel ) {
2018-06-25 15:56:05 -04:00
case NesModel : : Auto :
//Should never be Auto
break ;
2016-06-21 18:58:22 -04:00
case NesModel : : NTSC :
_nmiScanline = 241 ;
_vblankEnd = 260 ;
2017-04-29 21:39:57 -04:00
_standardNmiScanline = 241 ;
_standardVblankEnd = 260 ;
2016-06-21 18:58:22 -04:00
break ;
2016-01-30 19:33:32 -05:00
case NesModel : : PAL :
2016-06-21 18:58:22 -04:00
_nmiScanline = 241 ;
_vblankEnd = 310 ;
2017-04-29 21:39:57 -04:00
_standardNmiScanline = 241 ;
_standardVblankEnd = 310 ;
2016-06-21 18:58:22 -04:00
break ;
case NesModel : : Dendy :
_nmiScanline = 291 ;
_vblankEnd = 310 ;
2017-04-29 21:39:57 -04:00
_standardNmiScanline = 291 ;
_standardVblankEnd = 310 ;
2016-06-21 18:58:22 -04:00
break ;
2016-01-30 19:33:32 -05:00
}
2016-06-21 18:58:22 -04:00
_nmiScanline + = EmulationSettings : : GetPpuExtraScanlinesBeforeNmi ( ) ;
2017-07-17 19:35:16 -04:00
_palSpriteEvalScanline = _nmiScanline + 24 ;
2017-05-14 09:40:28 -04:00
_standardVblankEnd + = EmulationSettings : : GetPpuExtraScanlinesBeforeNmi ( ) ;
2016-06-21 18:58:22 -04:00
_vblankEnd + = EmulationSettings : : GetPpuExtraScanlinesAfterNmi ( ) + EmulationSettings : : GetPpuExtraScanlinesBeforeNmi ( ) ;
2015-07-21 23:05:27 -04:00
}
2018-02-18 22:41:50 -05:00
void PPU : : GetState ( PPUDebugState & state )
2014-06-14 18:20:56 -04:00
{
2015-07-01 23:17:14 -04:00
state . ControlFlags = _flags ;
state . StatusFlags = _statusFlags ;
state . State = _state ;
state . Cycle = _cycle ;
state . Scanline = _scanline ;
2016-09-03 21:52:59 -04:00
state . FrameCount = _frameCount ;
2018-02-18 22:41:50 -05:00
state . NmiScanline = _nmiScanline ;
2018-02-19 23:23:26 -05:00
state . ScanlineCount = _vblankEnd + 2 ;
state . SafeOamScanline = _nesModel = = NesModel : : NTSC ? _nmiScanline + 19 : _palSpriteEvalScanline ;
2014-06-14 11:27:55 -04:00
}
2018-02-18 22:41:50 -05:00
void PPU : : SetState ( PPUDebugState & state )
2016-11-26 17:48:11 -05:00
{
_flags = state . ControlFlags ;
_statusFlags = state . StatusFlags ;
_state = state . State ;
_cycle = state . Cycle ;
_scanline = state . Scanline ;
_frameCount = state . FrameCount ;
2017-08-05 14:55:07 -04:00
UpdateMinimumDrawCycles ( ) ;
_paletteRamMask = _flags . Grayscale ? 0x30 : 0x3F ;
if ( _nesModel = = NesModel : : NTSC ) {
_intensifyColorBits = ( _flags . IntensifyGreen ? 0x40 : 0x00 ) | ( _flags . IntensifyRed ? 0x80 : 0x00 ) | ( _flags . IntensifyBlue ? 0x100 : 0x00 ) ;
} else if ( _nesModel = = NesModel : : PAL | | _nesModel = = NesModel : : Dendy ) {
//"Note that on the Dendy and PAL NES, the green and red bits swap meaning."
_intensifyColorBits = ( _flags . IntensifyRed ? 0x40 : 0x00 ) | ( _flags . IntensifyGreen ? 0x80 : 0x00 ) | ( _flags . IntensifyBlue ? 0x100 : 0x00 ) ;
}
2016-11-26 17:48:11 -05:00
}
2014-06-22 00:37:45 -04:00
void PPU : : UpdateVideoRamAddr ( )
{
2016-07-10 09:08:31 -04:00
if ( _scanline > = 240 | | ! IsRenderingEnabled ( ) ) {
2018-06-02 22:12:00 -04:00
_state . VideoRamAddr = ( _state . VideoRamAddr + ( _flags . VerticalWrite ? 32 : 1 ) ) & 0x7FFF ;
2017-04-08 14:13:10 -04:00
2014-06-26 16:41:07 -04:00
//Trigger memory read when setting the vram address - needed by MMC3 IRQ counter
//"Should be clocked when A12 changes to 1 via $2007 read/write"
2018-06-02 22:12:00 -04:00
SetBusAddress ( _state . VideoRamAddr & 0x3FFF ) ;
2014-06-22 00:37:45 -04:00
} else {
//"During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled), "
//it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously"
IncHorizontalScrolling ( ) ;
IncVerticalScrolling ( ) ;
}
}
2016-01-02 20:36:22 -05:00
void PPU : : SetOpenBus ( uint8_t mask , uint8_t value )
{
//Decay expired bits, set new bits and update stamps on each individual bit
if ( mask = = 0xFF ) {
//Shortcut when mask is 0xFF - all bits are set to the value and stamps updated
_openBus = value ;
for ( int i = 0 ; i < 8 ; i + + ) {
_openBusDecayStamp [ i ] = _frameCount ;
}
} else {
uint16_t openBus = ( _openBus < < 8 ) ;
for ( int i = 0 ; i < 8 ; i + + ) {
openBus > > = 1 ;
if ( mask & 0x01 ) {
if ( value & 0x01 ) {
openBus | = 0x80 ;
} else {
openBus & = 0xFF7F ;
}
_openBusDecayStamp [ i ] = _frameCount ;
} else if ( _frameCount - _openBusDecayStamp [ i ] > 30 ) {
openBus & = 0xFF7F ;
}
value > > = 1 ;
mask > > = 1 ;
}
2017-03-31 22:14:16 -04:00
_openBus = ( uint8_t ) openBus ;
2016-01-02 20:36:22 -05:00
}
}
uint8_t PPU : : ApplyOpenBus ( uint8_t mask , uint8_t value )
{
SetOpenBus ( ~ mask , value ) ;
return value | ( _openBus & mask ) ;
}
2014-06-15 21:45:36 -04:00
uint8_t PPU : : ReadRAM ( uint16_t addr )
2014-06-14 11:27:55 -04:00
{
2016-01-02 20:36:22 -05:00
uint8_t openBusMask = 0xFF ;
uint8_t returnValue = 0 ;
2014-06-14 11:27:55 -04:00
switch ( GetRegisterID ( addr ) ) {
case PPURegisters : : Status :
2014-06-17 18:16:49 -04:00
_state . WriteToggle = false ;
_flags . IntensifyBlue = false ;
2014-06-14 18:20:56 -04:00
UpdateStatusFlag ( ) ;
2016-01-02 20:36:22 -05:00
returnValue = _state . Status ;
openBusMask = 0x1F ;
2016-04-30 20:08:53 -04:00
switch ( EmulationSettings : : GetPpuModel ( ) ) {
case PpuModel : : Ppu2C05A : openBusMask = 0x00 ; returnValue | = 0x1B ; break ;
case PpuModel : : Ppu2C05B : openBusMask = 0x00 ; returnValue | = 0x3D ; break ;
case PpuModel : : Ppu2C05C : openBusMask = 0x00 ; returnValue | = 0x1C ; break ;
case PpuModel : : Ppu2C05D : openBusMask = 0x00 ; returnValue | = 0x1B ; break ;
case PpuModel : : Ppu2C05E : openBusMask = 0x00 ; break ;
2018-06-25 15:56:05 -04:00
default : break ;
2016-04-30 20:08:53 -04:00
}
2016-01-02 20:36:22 -05:00
break ;
2014-06-14 11:27:55 -04:00
case PPURegisters : : SpriteData :
2017-02-22 20:41:58 -05:00
if ( ! EmulationSettings : : CheckFlag ( EmulationFlags : : DisablePpu2004Reads ) ) {
2017-03-10 21:29:48 -05:00
if ( _scanline < = 239 & & IsRenderingEnabled ( ) ) {
//While the screen is begin drawn
2017-02-22 20:41:58 -05:00
if ( _cycle > = 257 & & _cycle < = 320 ) {
2017-03-10 21:29:48 -05:00
//If we're doing sprite rendering, set OAM copy buffer to its proper value. This is done here for performance.
2017-02-22 20:41:58 -05:00
//It's faster to only do this here when it's needed, rather than splitting LoadSpriteTileInfo() into an 8-step process
uint8_t step = ( ( _cycle - 257 ) % 8 ) > 3 ? 3 : ( ( _cycle - 257 ) % 8 ) ;
_secondaryOAMAddr = ( _cycle - 257 ) / 8 * 4 + step ;
_oamCopybuffer = _secondarySpriteRAM [ _secondaryOAMAddr ] ;
}
2017-03-10 21:29:48 -05:00
//Return the value that PPU is currently using for sprite evaluation/rendering
2017-02-22 20:41:58 -05:00
returnValue = _oamCopybuffer ;
} else {
2017-04-08 14:13:10 -04:00
returnValue = ReadSpriteRam ( _state . SpriteRamAddr ) ;
2015-12-29 13:23:38 -05:00
}
2017-02-22 20:41:58 -05:00
openBusMask = 0x00 ;
2015-12-29 13:23:38 -05:00
}
2016-01-02 20:36:22 -05:00
break ;
2014-06-14 11:27:55 -04:00
case PPURegisters : : VideoMemoryData :
2016-01-03 17:20:18 -05:00
if ( _ignoreVramRead ) {
//2 reads to $2007 in quick succession (2 consecutive CPU cycles) causes the 2nd read to be ignored (normally depends on PPU/CPU timing, but this is the simplest solution)
//Return open bus in this case? (which will match the last value read)
openBusMask = 0xFF ;
2016-01-02 20:36:22 -05:00
} else {
2016-01-03 17:20:18 -05:00
returnValue = _memoryReadBuffer ;
2018-06-02 22:12:00 -04:00
_memoryReadBuffer = ReadVram ( _ppuBusAddress & 0x3FFF , MemoryOperationType : : Read ) ;
2014-06-20 22:31:48 -04:00
2017-02-24 21:43:42 -05:00
if ( ( _state . VideoRamAddr & 0x3FFF ) > = 0x3F00 & & ! EmulationSettings : : CheckFlag ( EmulationFlags : : DisablePaletteRead ) ) {
2016-01-03 17:20:18 -05:00
returnValue = ReadPaletteRAM ( _state . VideoRamAddr ) | ( _openBus & 0xC0 ) ;
2018-07-01 15:21:05 -04:00
_console - > DebugProcessVramReadOperation ( MemoryOperationType : : Read , _state . VideoRamAddr & 0x3FFF , returnValue ) ;
2016-01-03 17:20:18 -05:00
openBusMask = 0xC0 ;
} else {
openBusMask = 0x00 ;
}
UpdateVideoRamAddr ( ) ;
2017-12-04 00:09:11 -05:00
_ignoreVramRead = 6 ;
_needStateUpdate = true ;
2017-04-08 14:13:10 -04:00
}
2016-01-02 20:36:22 -05:00
break ;
2014-06-17 18:16:49 -04:00
default :
break ;
2014-06-14 11:27:55 -04:00
}
2016-01-02 20:36:22 -05:00
return ApplyOpenBus ( openBusMask , returnValue ) ;
2014-06-14 11:27:55 -04:00
}
2014-06-15 21:45:36 -04:00
void PPU : : WriteRAM ( uint16_t addr , uint8_t value )
2014-06-14 11:27:55 -04:00
{
2016-01-02 20:36:22 -05:00
if ( addr ! = 0x4014 ) {
SetOpenBus ( 0xFF , value ) ;
}
2014-06-14 18:20:56 -04:00
switch ( GetRegisterID ( addr ) ) {
case PPURegisters : : Control :
2016-04-30 20:08:53 -04:00
if ( EmulationSettings : : GetPpuModel ( ) > = PpuModel : : Ppu2C05A & & EmulationSettings : : GetPpuModel ( ) < = PpuModel : : Ppu2C05E ) {
SetMaskRegister ( value ) ;
} else {
2017-04-08 14:13:10 -04:00
SetControlRegister ( value ) ;
2016-04-30 20:08:53 -04:00
}
2014-06-14 18:20:56 -04:00
break ;
2014-06-22 00:37:45 -04:00
case PPURegisters : : Mask :
2016-04-30 20:08:53 -04:00
if ( EmulationSettings : : GetPpuModel ( ) > = PpuModel : : Ppu2C05A & & EmulationSettings : : GetPpuModel ( ) < = PpuModel : : Ppu2C05E ) {
SetControlRegister ( value ) ;
2017-04-08 14:13:10 -04:00
} else {
2016-04-30 20:08:53 -04:00
SetMaskRegister ( value ) ;
}
2014-06-14 18:20:56 -04:00
break ;
case PPURegisters : : SpriteAddr :
_state . SpriteRamAddr = value ;
break ;
case PPURegisters : : SpriteData :
2017-04-07 19:24:34 -04:00
if ( ( _scanline > = 240 & & ( _nesModel ! = NesModel : : PAL | | _scanline < _palSpriteEvalScanline ) ) | | ! IsRenderingEnabled ( ) ) {
2017-02-23 20:00:52 -05:00
if ( ( _state . SpriteRamAddr & 0x03 ) = = 0x02 ) {
//"The three unimplemented bits of each sprite's byte 2 do not exist in the PPU and always read back as 0 on PPU revisions that allow reading PPU OAM through OAMDATA ($2004)"
value & = 0xE3 ;
2015-12-28 20:07:27 -05:00
}
2017-04-08 14:13:10 -04:00
WriteSpriteRam ( _state . SpriteRamAddr , value ) ;
2017-02-23 20:00:52 -05:00
_state . SpriteRamAddr = ( _state . SpriteRamAddr + 1 ) & 0xFF ;
} else {
//"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,
//but do perform a glitchy increment of OAMADDR, bumping only the high 6 bits"
_state . SpriteRamAddr = ( _state . SpriteRamAddr + 4 ) & 0xFF ;
2015-07-25 17:05:56 -04:00
}
2014-06-14 18:20:56 -04:00
break ;
case PPURegisters : : ScrollOffsets :
2014-06-20 21:48:55 -04:00
if ( _state . WriteToggle ) {
2014-06-18 22:54:23 -04:00
_state . TmpVideoRamAddr = ( _state . TmpVideoRamAddr & ~ 0x73E0 ) | ( ( value & 0xF8 ) < < 2 ) | ( ( value & 0x07 ) < < 12 ) ;
2014-06-17 18:16:49 -04:00
} else {
_state . XScroll = value & 0x07 ;
2014-06-18 22:54:23 -04:00
_state . TmpVideoRamAddr = ( _state . TmpVideoRamAddr & ~ 0x001F ) | ( value > > 3 ) ;
2017-09-28 20:05:00 -04:00
//Update debugger overlay X scroll only
2018-07-01 15:21:05 -04:00
_console - > DebugSetLastFramePpuScroll ( _state . TmpVideoRamAddr , _state . XScroll , false ) ;
2014-06-17 18:16:49 -04:00
}
_state . WriteToggle = ! _state . WriteToggle ;
2014-06-14 18:20:56 -04:00
break ;
case PPURegisters : : VideoMemoryAddr :
2014-06-17 18:16:49 -04:00
if ( _state . WriteToggle ) {
2014-06-18 22:54:23 -04:00
_state . TmpVideoRamAddr = ( _state . TmpVideoRamAddr & ~ 0x00FF ) | value ;
2016-05-31 17:12:43 -04:00
2017-03-10 22:21:14 -05:00
//Video RAM update is apparently delayed by 2-3 PPU cycles (based on Visual NES findings)
//A 3-cycle delay causes issues with the scanline test.
2017-12-04 00:09:11 -05:00
_needStateUpdate = true ;
2017-03-10 22:21:14 -05:00
_updateVramAddrDelay = 2 ;
_updateVramAddr = _state . TmpVideoRamAddr ;
2018-07-01 15:21:05 -04:00
_console - > DebugSetLastFramePpuScroll ( _updateVramAddr , _state . XScroll , false ) ;
2014-06-14 18:20:56 -04:00
} else {
2014-06-18 22:54:23 -04:00
_state . TmpVideoRamAddr = ( _state . TmpVideoRamAddr & ~ 0xFF00 ) | ( ( value & 0x3F ) < < 8 ) ;
2014-06-14 18:20:56 -04:00
}
2014-06-17 18:16:49 -04:00
_state . WriteToggle = ! _state . WriteToggle ;
2014-06-14 18:20:56 -04:00
break ;
case PPURegisters : : VideoMemoryData :
2016-01-13 00:06:01 -05:00
if ( ( _state . VideoRamAddr & 0x3FFF ) > = 0x3F00 ) {
2014-06-20 22:31:48 -04:00
WritePaletteRAM ( _state . VideoRamAddr , value ) ;
2018-07-01 15:21:05 -04:00
_console - > DebugProcessVramWriteOperation ( _state . VideoRamAddr & 0x3FFF , value ) ;
2014-06-20 22:31:48 -04:00
} else {
2018-07-01 15:21:05 -04:00
_console - > GetMapper ( ) - > WriteVRAM ( _ppuBusAddress & 0x3FFF , value ) ;
2014-06-20 22:31:48 -04:00
}
2014-06-22 00:37:45 -04:00
UpdateVideoRamAddr ( ) ;
2014-06-14 18:20:56 -04:00
break ;
2014-06-20 21:48:55 -04:00
case PPURegisters : : SpriteDMA :
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > RunDMATransfer ( value ) ;
2014-06-20 21:48:55 -04:00
break ;
2015-08-28 21:01:18 -04:00
default :
break ;
2014-06-14 18:20:56 -04:00
}
}
2014-06-14 11:27:55 -04:00
2014-06-20 22:31:48 -04:00
uint8_t PPU : : ReadPaletteRAM ( uint16_t addr )
{
addr & = 0x1F ;
if ( addr = = 0x10 | | addr = = 0x14 | | addr = = 0x18 | | addr = = 0x1C ) {
addr & = ~ 0x10 ;
}
2017-03-31 22:14:16 -04:00
return _paletteRAM [ addr ] ;
2014-06-20 22:31:48 -04:00
}
void PPU : : WritePaletteRAM ( uint16_t addr , uint8_t value )
{
addr & = 0x1F ;
2017-04-20 21:58:35 -04:00
value & = 0x3F ;
2017-03-31 22:14:16 -04:00
if ( addr = = 0x00 | | addr = = 0x10 ) {
_paletteRAM [ 0x00 ] = value ;
_paletteRAM [ 0x10 ] = value ;
} else if ( addr = = 0x04 | | addr = = 0x14 ) {
_paletteRAM [ 0x04 ] = value ;
_paletteRAM [ 0x14 ] = value ;
} else if ( addr = = 0x08 | | addr = = 0x18 ) {
_paletteRAM [ 0x08 ] = value ;
_paletteRAM [ 0x18 ] = value ;
} else if ( addr = = 0x0C | | addr = = 0x1C ) {
_paletteRAM [ 0x0C ] = value ;
_paletteRAM [ 0x1C ] = value ;
} else {
_paletteRAM [ addr ] = value ;
2014-06-20 22:31:48 -04:00
}
}
2014-06-17 18:16:49 -04:00
bool PPU : : IsRenderingEnabled ( )
{
2016-01-11 17:18:01 -05:00
return _renderingEnabled ;
2014-06-17 18:16:49 -04:00
}
2014-06-22 00:37:45 -04:00
void PPU : : SetControlRegister ( uint8_t value )
2014-06-14 18:20:56 -04:00
{
2014-06-22 00:37:45 -04:00
_state . Control = value ;
2014-06-14 18:20:56 -04:00
uint8_t nameTable = ( _state . Control & 0x03 ) ;
2014-06-21 23:54:32 -04:00
_state . TmpVideoRamAddr = ( _state . TmpVideoRamAddr & ~ 0x0C00 ) | ( nameTable < < 10 ) ;
2014-06-17 18:16:49 -04:00
2014-06-14 18:20:56 -04:00
_flags . VerticalWrite = ( _state . Control & 0x04 ) = = 0x04 ;
_flags . SpritePatternAddr = ( ( _state . Control & 0x08 ) = = 0x08 ) ? 0x1000 : 0x0000 ;
_flags . BackgroundPatternAddr = ( ( _state . Control & 0x10 ) = = 0x10 ) ? 0x1000 : 0x0000 ;
_flags . LargeSprites = ( _state . Control & 0x20 ) = = 0x20 ;
2014-06-21 23:54:32 -04:00
2014-06-27 12:18:07 -04:00
//"By toggling NMI_output ($2000 bit 7) during vertical blank without reading $2002, a program can cause /NMI to be pulled low multiple times, causing multiple NMIs to be generated."
2014-06-21 23:54:32 -04:00
bool originalVBlank = _flags . VBlank ;
2014-06-14 18:20:56 -04:00
_flags . VBlank = ( _state . Control & 0x80 ) = = 0x80 ;
2017-04-08 14:13:10 -04:00
2015-07-24 19:20:54 -04:00
if ( ! originalVBlank & & _flags . VBlank & & _statusFlags . VerticalBlank & & ( _scanline ! = - 1 | | _cycle ! = 0 ) ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > SetNmiFlag ( ) ;
2016-01-02 13:52:55 -05:00
}
if ( _scanline = = 241 & & _cycle < 3 & & ! _flags . VBlank ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > ClearNmiFlag ( ) ;
2014-06-21 23:54:32 -04:00
}
2014-06-22 00:37:45 -04:00
}
2014-06-14 18:20:56 -04:00
2016-10-23 13:29:37 -04:00
void PPU : : UpdateMinimumDrawCycles ( )
{
_minimumDrawBgCycle = _flags . BackgroundEnabled ? ( ( _flags . BackgroundMask | | EmulationSettings : : CheckFlag ( EmulationFlags : : ForceBackgroundFirstColumn ) ) ? 0 : 8 ) : 300 ;
_minimumDrawSpriteCycle = _flags . SpritesEnabled ? ( ( _flags . SpriteMask | | EmulationSettings : : CheckFlag ( EmulationFlags : : ForceSpritesFirstColumn ) ) ? 0 : 8 ) : 300 ;
_minimumDrawSpriteStandardCycle = _flags . SpritesEnabled ? ( _flags . SpriteMask ? 0 : 8 ) : 300 ;
}
2014-06-22 00:37:45 -04:00
void PPU : : SetMaskRegister ( uint8_t value )
{
_state . Mask = value ;
_flags . Grayscale = ( _state . Mask & 0x01 ) = = 0x01 ;
_flags . BackgroundMask = ( _state . Mask & 0x02 ) = = 0x02 ;
_flags . SpriteMask = ( _state . Mask & 0x04 ) = = 0x04 ;
_flags . BackgroundEnabled = ( _state . Mask & 0x08 ) = = 0x08 ;
_flags . SpritesEnabled = ( _state . Mask & 0x10 ) = = 0x10 ;
_flags . IntensifyBlue = ( _state . Mask & 0x80 ) = = 0x80 ;
2016-10-23 13:29:37 -04:00
2017-12-04 00:09:11 -05:00
if ( _renderingEnabled ! = ( _flags . BackgroundEnabled | _flags . SpritesEnabled ) ) {
_needStateUpdate = true ;
}
2016-10-23 13:29:37 -04:00
UpdateMinimumDrawCycles ( ) ;
2017-04-20 21:58:35 -04:00
UpdateGrayscaleAndIntensifyBits ( ) ;
2017-04-08 14:13:10 -04:00
//"Bit 0 controls a greyscale mode, which causes the palette to use only the colors from the grey column: $00, $10, $20, $30. This is implemented as a bitwise AND with $30 on any value read from PPU $3F00-$3FFF"
2015-07-22 22:08:28 -04:00
_paletteRamMask = _flags . Grayscale ? 0x30 : 0x3F ;
if ( _nesModel = = NesModel : : NTSC ) {
_flags . IntensifyRed = ( _state . Mask & 0x20 ) = = 0x20 ;
_flags . IntensifyGreen = ( _state . Mask & 0x40 ) = = 0x40 ;
_intensifyColorBits = ( value & 0xE0 ) < < 1 ;
2016-01-30 19:33:32 -05:00
} else if ( _nesModel = = NesModel : : PAL | | _nesModel = = NesModel : : Dendy ) {
2015-07-22 22:08:28 -04:00
//"Note that on the Dendy and PAL NES, the green and red bits swap meaning."
_flags . IntensifyRed = ( _state . Mask & 0x40 ) = = 0x40 ;
_flags . IntensifyGreen = ( _state . Mask & 0x20 ) = = 0x20 ;
_intensifyColorBits = ( _flags . IntensifyRed ? 0x40 : 0x00 ) | ( _flags . IntensifyGreen ? 0x80 : 0x00 ) | ( _flags . IntensifyBlue ? 0x100 : 0x00 ) ;
}
2014-06-14 18:20:56 -04:00
}
void PPU : : UpdateStatusFlag ( )
{
_state . Status = ( ( uint8_t ) _statusFlags . SpriteOverflow < < 5 ) |
2017-04-08 14:13:10 -04:00
( ( uint8_t ) _statusFlags . Sprite0Hit < < 6 ) |
( ( uint8_t ) _statusFlags . VerticalBlank < < 7 ) ;
2014-06-15 09:35:17 -04:00
_statusFlags . VerticalBlank = false ;
2014-06-21 00:37:27 -04:00
2016-01-02 13:52:55 -05:00
if ( _scanline = = 241 & & _cycle < 3 ) {
//"Reading on the same PPU clock or one later reads it as set, clears it, and suppresses the NMI for that frame."
2017-04-08 14:13:10 -04:00
_statusFlags . VerticalBlank = false ;
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > ClearNmiFlag ( ) ;
2014-06-27 12:18:07 -04:00
2016-01-02 13:52:55 -05:00
if ( _cycle = = 0 ) {
//"Reading one PPU clock before reads it as clear and never sets the flag or generates NMI for that frame. "
_state . Status = ( ( uint8_t ) _statusFlags . SpriteOverflow < < 5 ) | ( ( uint8_t ) _statusFlags . Sprite0Hit < < 6 ) ;
2014-06-27 12:18:07 -04:00
}
2014-06-21 00:37:27 -04:00
}
2014-06-14 18:20:56 -04:00
}
2014-06-17 18:16:49 -04:00
//Taken from http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling#Wrapping_around
void PPU : : IncVerticalScrolling ( )
{
uint16_t addr = _state . VideoRamAddr ;
if ( ( addr & 0x7000 ) ! = 0x7000 ) {
// if fine Y < 7
addr + = 0x1000 ; // increment fine Y
} else {
// fine Y = 0
addr & = ~ 0x7000 ;
int y = ( addr & 0x03E0 ) > > 5 ; // let y = coarse Y
if ( y = = 29 ) {
y = 0 ; // coarse Y = 0
addr ^ = 0x0800 ; // switch vertical nametable
} else if ( y = = 31 ) {
y = 0 ; // coarse Y = 0, nametable not switched
} else {
2014-06-22 00:00:56 -04:00
y + + ; // increment coarse Y
2014-06-17 18:16:49 -04:00
}
addr = ( addr & ~ 0x03E0 ) | ( y < < 5 ) ; // put coarse Y back into v
}
_state . VideoRamAddr = addr ;
}
//Taken from http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling#Wrapping_around
void PPU : : IncHorizontalScrolling ( )
{
//Increase coarse X scrolling value.
uint16_t addr = _state . VideoRamAddr ;
if ( ( addr & 0x001F ) = = 31 ) {
2014-06-22 00:00:56 -04:00
//When the value is 31, wrap around to 0 and switch nametable
2017-04-08 14:13:10 -04:00
addr = ( addr & ~ 0x001F ) ^ 0x0400 ;
2014-06-17 18:16:49 -04:00
} else {
2014-06-22 00:00:56 -04:00
addr + + ;
2014-06-17 18:16:49 -04:00
}
_state . VideoRamAddr = addr ;
}
2015-07-01 23:17:14 -04:00
//Taken from http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling#Tile_and_attribute_fetching
2014-06-19 19:58:15 -04:00
uint16_t PPU : : GetNameTableAddr ( )
2014-06-17 18:16:49 -04:00
{
return 0x2000 | ( _state . VideoRamAddr & 0x0FFF ) ;
}
2015-07-01 23:17:14 -04:00
//Taken from http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling#Tile_and_attribute_fetching
2014-06-17 18:16:49 -04:00
uint16_t PPU : : GetAttributeAddr ( )
{
return 0x23C0 | ( _state . VideoRamAddr & 0x0C00 ) | ( ( _state . VideoRamAddr > > 4 ) & 0x38 ) | ( ( _state . VideoRamAddr > > 2 ) & 0x07 ) ;
}
2018-06-02 22:12:00 -04:00
void PPU : : SetBusAddress ( uint16_t addr )
{
_ppuBusAddress = addr ;
2018-07-01 15:21:05 -04:00
_console - > GetMapper ( ) - > NotifyVRAMAddressChange ( addr ) ;
2018-06-02 22:12:00 -04:00
}
uint8_t PPU : : ReadVram ( uint16_t addr , MemoryOperationType type )
{
SetBusAddress ( addr ) ;
2018-07-01 15:21:05 -04:00
return _console - > GetMapper ( ) - > ReadVRAM ( addr , type ) ;
2018-06-02 22:12:00 -04:00
}
void PPU : : WriteVram ( uint16_t addr , uint8_t value )
{
SetBusAddress ( addr ) ;
2018-07-01 15:21:05 -04:00
_console - > GetMapper ( ) - > WriteVRAM ( addr , value ) ;
2018-06-02 22:12:00 -04:00
}
2016-01-03 19:39:41 -05:00
void PPU : : LoadTileInfo ( )
2014-06-19 19:58:15 -04:00
{
2014-07-26 20:21:36 -04:00
if ( IsRenderingEnabled ( ) ) {
2016-01-03 19:39:41 -05:00
switch ( ( _cycle - 1 ) & 0x07 ) {
2017-03-31 22:14:16 -04:00
case 0 : {
2016-01-03 19:39:41 -05:00
_previousTile = _currentTile ;
_currentTile = _nextTile ;
2017-04-21 21:29:39 -04:00
_state . LowBitShift | = _nextTile . LowByte ;
_state . HighBitShift | = _nextTile . HighByte ;
2016-01-03 19:39:41 -05:00
2018-06-02 22:12:00 -04:00
uint8_t tileIndex = ReadVram ( GetNameTableAddr ( ) ) ;
2016-01-03 19:39:41 -05:00
_nextTile . TileAddr = ( tileIndex < < 4 ) | ( _state . VideoRamAddr > > 12 ) | _flags . BackgroundPatternAddr ;
_nextTile . OffsetY = _state . VideoRamAddr > > 12 ;
break ;
2017-03-31 22:14:16 -04:00
}
2016-01-03 19:39:41 -05:00
2017-03-31 22:14:16 -04:00
case 2 : {
uint8_t shift = ( ( _state . VideoRamAddr > > 4 ) & 0x04 ) | ( _state . VideoRamAddr & 0x02 ) ;
2018-06-02 22:12:00 -04:00
_nextTile . PaletteOffset = ( ( ReadVram ( GetAttributeAddr ( ) ) > > shift ) & 0x03 ) < < 2 ;
2016-01-03 19:39:41 -05:00
break ;
2017-03-31 22:14:16 -04:00
}
2016-01-03 19:39:41 -05:00
case 3 :
2018-06-02 22:12:00 -04:00
_nextTile . LowByte = ReadVram ( _nextTile . TileAddr ) ;
2018-07-01 15:21:05 -04:00
_nextTile . AbsoluteTileAddr = _console - > GetMapper ( ) - > ToAbsoluteChrAddress ( _nextTile . TileAddr ) ;
2016-01-03 19:39:41 -05:00
break ;
case 5 :
2018-06-02 22:12:00 -04:00
_nextTile . HighByte = ReadVram ( _nextTile . TileAddr + 8 ) ;
2016-01-03 19:39:41 -05:00
break ;
2017-04-07 19:24:34 -04:00
}
2014-06-26 16:41:07 -04:00
}
2014-06-19 19:58:15 -04:00
}
2016-01-14 22:20:50 -05:00
void PPU : : LoadSprite ( uint8_t spriteY , uint8_t tileIndex , uint8_t attributes , uint8_t spriteX , bool extraSprite )
2014-06-20 21:48:55 -04:00
{
2015-12-29 13:23:38 -05:00
bool backgroundPriority = ( attributes & 0x20 ) = = 0x20 ;
bool horizontalMirror = ( attributes & 0x40 ) = = 0x40 ;
bool verticalMirror = ( attributes & 0x80 ) = = 0x80 ;
uint16_t tileAddr ;
uint8_t lineOffset ;
if ( verticalMirror ) {
lineOffset = ( _flags . LargeSprites ? 15 : 7 ) - ( _scanline - spriteY ) ;
} else {
lineOffset = _scanline - spriteY ;
}
if ( _flags . LargeSprites ) {
tileAddr = ( ( ( tileIndex & 0x01 ) ? 0x1000 : 0x0000 ) | ( ( tileIndex & ~ 0x01 ) < < 4 ) ) + ( lineOffset > = 8 ? lineOffset + 8 : lineOffset ) ;
} else {
tileAddr = ( ( tileIndex < < 4 ) | _flags . SpritePatternAddr ) + lineOffset ;
}
2014-06-25 17:30:35 -04:00
2017-04-08 14:13:10 -04:00
bool fetchLastSprite = true ;
2016-01-14 22:20:50 -05:00
if ( ( _spriteIndex < _spriteCount | | extraSprite ) & & spriteY < 240 ) {
2017-03-31 22:14:16 -04:00
SpriteInfo & info = _spriteTiles [ _spriteIndex ] ;
info . BackgroundPriority = backgroundPriority ;
info . HorizontalMirror = horizontalMirror ;
info . VerticalMirror = verticalMirror ;
info . PaletteOffset = ( ( attributes & 0x03 ) < < 2 ) | 0x10 ;
2016-01-14 22:20:50 -05:00
if ( extraSprite ) {
2016-11-13 22:46:55 -05:00
//Use DebugReadVRAM for extra sprites to prevent side-effects.
2018-07-01 15:21:05 -04:00
info . LowByte = _console - > GetMapper ( ) - > DebugReadVRAM ( tileAddr ) ;
info . HighByte = _console - > GetMapper ( ) - > DebugReadVRAM ( tileAddr + 8 ) ;
2016-01-14 22:20:50 -05:00
} else {
2016-11-13 22:46:55 -05:00
fetchLastSprite = false ;
2018-06-02 22:12:00 -04:00
info . LowByte = ReadVram ( tileAddr ) ;
info . HighByte = ReadVram ( tileAddr + 8 ) ;
2016-01-14 22:20:50 -05:00
}
2017-03-31 22:14:16 -04:00
info . TileAddr = tileAddr ;
2018-07-01 15:21:05 -04:00
info . AbsoluteTileAddr = _console - > GetMapper ( ) - > ToAbsoluteChrAddress ( tileAddr ) ;
2017-03-31 22:14:16 -04:00
info . OffsetY = lineOffset ;
info . SpriteX = spriteX ;
2017-04-08 14:13:10 -04:00
2017-04-07 19:24:34 -04:00
if ( _scanline > = 0 ) {
//Sprites read on prerender scanline are not shown on scanline 0
for ( int i = 0 ; i < 8 & & spriteX + i + 1 < 257 ; i + + ) {
_hasSprite [ spriteX + i + 1 ] = true ;
}
2017-04-02 17:41:24 -04:00
}
2017-04-08 14:13:10 -04:00
}
2016-11-13 22:46:55 -05:00
if ( fetchLastSprite ) {
2015-12-29 13:23:38 -05:00
//Fetches to sprite 0xFF for remaining sprites/hidden - used by MMC3 IRQ counter
lineOffset = 0 ;
tileIndex = 0xFF ;
2014-06-25 17:33:25 -04:00
if ( _flags . LargeSprites ) {
tileAddr = ( ( ( tileIndex & 0x01 ) ? 0x1000 : 0x0000 ) | ( ( tileIndex & ~ 0x01 ) < < 4 ) ) + ( lineOffset > = 8 ? lineOffset + 8 : lineOffset ) ;
} else {
tileAddr = ( ( tileIndex < < 4 ) | _flags . SpritePatternAddr ) + lineOffset ;
}
2018-06-02 22:12:00 -04:00
ReadVram ( tileAddr ) ;
ReadVram ( tileAddr + 8 ) ;
2014-06-20 21:48:55 -04:00
}
2015-12-29 13:23:38 -05:00
_spriteIndex + + ;
2014-06-20 21:48:55 -04:00
}
2016-01-14 22:20:50 -05:00
void PPU : : LoadExtraSprites ( )
{
if ( _spriteCount = = 8 & & EmulationSettings : : CheckFlag ( EmulationFlags : : RemoveSpriteLimit ) ) {
2017-06-08 20:00:51 -04:00
bool loadExtraSprites = true ;
if ( EmulationSettings : : CheckFlag ( EmulationFlags : : AdaptiveSpriteLimit ) ) {
uint16_t lastPosition = 0xFFFF ;
uint8_t identicalSpriteCount = 0 ;
uint8_t maxIdenticalSpriteCount = 0 ;
for ( int i = 0 ; i < 64 ; i + + ) {
uint8_t y = _spriteRAM [ i < < 2 ] ;
if ( _scanline > = y & & _scanline < y + ( _flags . LargeSprites ? 16 : 8 ) ) {
uint8_t x = _spriteRAM [ ( i < < 2 ) + 3 ] ;
uint16_t position = ( y < < 8 ) | x ;
if ( lastPosition ! = position ) {
if ( identicalSpriteCount > maxIdenticalSpriteCount ) {
maxIdenticalSpriteCount = identicalSpriteCount ;
}
lastPosition = position ;
identicalSpriteCount = 1 ;
} else {
identicalSpriteCount + + ;
}
}
}
loadExtraSprites = identicalSpriteCount < 8 & & maxIdenticalSpriteCount < 8 ;
}
if ( loadExtraSprites ) {
for ( uint32_t i = _overflowSpriteAddr ; i < 0x100 ; i + = 4 ) {
uint8_t spriteY = _spriteRAM [ i ] ;
if ( _scanline > = spriteY & & _scanline < spriteY + ( _flags . LargeSprites ? 16 : 8 ) ) {
LoadSprite ( spriteY , _spriteRAM [ i + 1 ] , _spriteRAM [ i + 2 ] , _spriteRAM [ i + 3 ] , true ) ;
_spriteCount + + ;
}
2016-01-14 22:20:50 -05:00
}
}
}
}
void PPU : : LoadSpriteTileInfo ( )
{
uint8_t * spriteAddr = _secondarySpriteRAM + _spriteIndex * 4 ;
LoadSprite ( * spriteAddr , * ( spriteAddr + 1 ) , * ( spriteAddr + 2 ) , * ( spriteAddr + 3 ) , false ) ;
2016-11-13 22:46:55 -05:00
if ( _cycle = = 316 ) {
LoadExtraSprites ( ) ;
}
2016-01-14 22:20:50 -05:00
}
2014-06-19 19:58:15 -04:00
void PPU : : ShiftTileRegisters ( )
{
_state . LowBitShift < < = 1 ;
_state . HighBitShift < < = 1 ;
}
2017-04-21 21:29:39 -04:00
uint8_t PPU : : GetPixelColor ( )
2014-06-19 19:58:15 -04:00
{
2015-08-14 21:50:14 -04:00
uint8_t offset = _state . XScroll ;
2017-04-21 21:29:39 -04:00
uint8_t backgroundColor = 0 ;
uint8_t spriteBgColor = 0 ;
2017-04-08 14:13:10 -04:00
2016-10-23 13:29:37 -04:00
if ( _cycle > _minimumDrawBgCycle ) {
2015-08-14 21:50:14 -04:00
//BackgroundMask = false: Hide background in leftmost 8 pixels of screen
2016-09-11 11:03:26 -04:00
spriteBgColor = ( ( ( _state . LowBitShift < < offset ) & 0x8000 ) > > 15 ) | ( ( ( _state . HighBitShift < < offset ) & 0x8000 ) > > 14 ) ;
if ( EmulationSettings : : GetBackgroundEnabled ( ) ) {
backgroundColor = spriteBgColor ;
}
2015-08-14 21:50:14 -04:00
}
2014-06-21 20:18:05 -04:00
2017-04-02 17:41:24 -04:00
if ( _hasSprite [ _cycle ] & & _cycle > _minimumDrawSpriteCycle ) {
2015-08-14 21:50:14 -04:00
//SpriteMask = true: Hide sprites in leftmost 8 pixels of screen
for ( uint8_t i = 0 ; i < _spriteCount ; i + + ) {
2017-04-02 17:41:24 -04:00
int32_t shift = ( int32_t ) _cycle - _spriteTiles [ i ] . SpriteX - 1 ;
2015-08-14 21:50:14 -04:00
if ( shift > = 0 & & shift < 8 ) {
_lastSprite = & _spriteTiles [ i ] ;
2017-04-21 21:29:39 -04:00
uint8_t spriteColor ;
2015-08-14 21:50:14 -04:00
if ( _spriteTiles [ i ] . HorizontalMirror ) {
spriteColor = ( ( _lastSprite - > LowByte > > shift ) & 0x01 ) | ( ( _lastSprite - > HighByte > > shift ) & 0x01 ) < < 1 ;
} else {
spriteColor = ( ( _lastSprite - > LowByte < < shift ) & 0x80 ) > > 7 | ( ( _lastSprite - > HighByte < < shift ) & 0x80 ) > > 6 ;
}
2017-04-08 14:13:10 -04:00
2015-08-14 21:50:14 -04:00
if ( spriteColor ! = 0 ) {
//First sprite without a 00 color, use it.
2017-09-28 20:13:21 -04:00
if ( i = = 0 & & spriteBgColor ! = 0 & & _sprite0Visible & & _cycle ! = 256 & & _flags . BackgroundEnabled & & ! _statusFlags . Sprite0Hit & & _cycle > _minimumDrawSpriteStandardCycle ) {
2015-08-14 21:50:14 -04:00
//"The hit condition is basically sprite zero is in range AND the first sprite output unit is outputting a non-zero pixel AND the background drawing unit is outputting a non-zero pixel."
//"Sprite zero hits do not register at x=255" (cycle 256)
//"... provided that background and sprite rendering are both enabled"
//"Should always miss when Y >= 239"
2016-05-18 20:46:03 -04:00
_statusFlags . Sprite0Hit = true ;
2018-02-18 22:41:50 -05:00
2018-07-01 15:21:05 -04:00
_console - > DebugProcessEvent ( EventType : : SpriteZeroHit ) ;
2014-06-28 22:52:28 -04:00
}
2015-07-05 08:47:34 -04:00
2016-09-11 11:03:26 -04:00
if ( EmulationSettings : : GetSpritesEnabled ( ) & & ( backgroundColor = = 0 | | ! _spriteTiles [ i ] . BackgroundPriority ) ) {
2015-08-14 21:50:14 -04:00
//Check sprite priority
2017-03-31 22:14:16 -04:00
return _lastSprite - > PaletteOffset + spriteColor ;
2015-07-22 22:08:28 -04:00
}
2015-08-14 21:50:14 -04:00
break ;
2014-06-21 20:18:05 -04:00
}
2014-06-20 21:48:55 -04:00
}
}
2017-04-08 14:13:10 -04:00
}
2017-03-31 22:14:16 -04:00
return ( ( offset + ( ( _cycle - 1 ) & 0x07 ) < 8 ) ? _previousTile : _currentTile ) . PaletteOffset + backgroundColor ;
2015-08-14 21:50:14 -04:00
}
void PPU : : DrawPixel ( )
{
//This is called 3.7 million times per second - needs to be as fast as possible.
if ( IsRenderingEnabled ( ) | | ( ( _state . VideoRamAddr & 0x3F00 ) ! = 0x3F00 ) ) {
2017-03-31 22:14:16 -04:00
uint32_t color = GetPixelColor ( ) ;
2017-04-20 21:58:35 -04:00
_currentOutputBuffer [ ( _scanline < < 8 ) + _cycle - 1 ] = _paletteRAM [ color & 0x03 ? color : 0 ] ;
2015-07-22 22:08:28 -04:00
} else {
//"If the current VRAM address points in the range $3F00-$3FFF during forced blanking, the color indicated by this palette location will be shown on screen instead of the backdrop color."
2017-04-20 21:58:35 -04:00
_currentOutputBuffer [ ( _scanline < < 8 ) + _cycle - 1 ] = _paletteRAM [ _state . VideoRamAddr & 0x1F ] ;
}
}
void PPU : : UpdateGrayscaleAndIntensifyBits ( )
{
2017-09-04 08:31:54 -04:00
if ( _scanline < 0 | | _scanline > _nmiScanline ) {
2017-04-20 21:58:35 -04:00
return ;
}
int pixelNumber ;
if ( _scanline > = 240 ) {
pixelNumber = 61439 ;
} else if ( _cycle < 3 ) {
pixelNumber = ( _scanline < < 8 ) - 1 ;
} else if ( _cycle < = 258 ) {
pixelNumber = ( _scanline < < 8 ) + _cycle - 3 ;
} else {
pixelNumber = ( _scanline < < 8 ) + 255 ;
}
if ( _paletteRamMask = = 0x3F & & _intensifyColorBits = = 0 ) {
//Nothing to do (most common case)
_lastUpdatedPixel = pixelNumber ;
return ;
}
if ( _lastUpdatedPixel < pixelNumber ) {
uint16_t * out = _currentOutputBuffer + _lastUpdatedPixel + 1 ;
while ( _lastUpdatedPixel < pixelNumber ) {
* out = ( * out & _paletteRamMask ) | _intensifyColorBits ;
out + + ;
_lastUpdatedPixel + + ;
}
2014-06-20 21:48:55 -04:00
}
2014-06-19 19:58:15 -04:00
}
2017-03-31 22:14:16 -04:00
void PPU : : ProcessScanline ( )
2014-06-17 18:16:49 -04:00
{
2017-04-21 21:29:39 -04:00
//Only called for cycle 1+
if ( _cycle < = 256 ) {
2017-03-31 22:14:16 -04:00
LoadTileInfo ( ) ;
if ( _prevRenderingEnabled & & ( _cycle & 0x07 ) = = 0 ) {
2015-07-04 22:21:14 -04:00
IncHorizontalScrolling ( ) ;
2017-03-31 22:14:16 -04:00
if ( _cycle = = 256 ) {
IncVerticalScrolling ( ) ;
}
2014-06-20 21:48:55 -04:00
}
2017-03-31 22:14:16 -04:00
if ( _scanline > = 0 ) {
DrawPixel ( ) ;
ShiftTileRegisters ( ) ;
2017-04-07 19:24:34 -04:00
//"Secondary OAM clear and sprite evaluation do not occur on the pre-render line"
ProcessSpriteEvaluation ( ) ;
} else if ( _cycle < 9 ) {
2017-04-21 21:29:39 -04:00
//Pre-render scanline logic
2017-04-07 19:24:34 -04:00
if ( _cycle = = 1 ) {
_statusFlags . VerticalBlank = false ;
}
if ( _state . SpriteRamAddr > = 0x08 & & IsRenderingEnabled ( ) & & ! EmulationSettings : : CheckFlag ( EmulationFlags : : DisableOamAddrBug ) ) {
//This should only be done if rendering is enabled (otherwise oam_stress test fails immediately)
//"If OAMADDR is not less than eight when rendering starts, the eight bytes starting at OAMADDR & 0xF8 are copied to the first eight bytes of OAM"
2017-04-08 14:13:10 -04:00
WriteSpriteRam ( _cycle - 1 , ReadSpriteRam ( ( _state . SpriteRamAddr & 0xF8 ) + _cycle - 1 ) ) ;
2017-04-07 19:24:34 -04:00
}
2017-04-08 14:13:10 -04:00
}
2017-03-31 22:14:16 -04:00
} else if ( _cycle > = 257 & & _cycle < = 320 ) {
2016-12-16 07:13:33 -05:00
if ( _cycle = = 257 ) {
_spriteIndex = 0 ;
2017-04-02 17:41:24 -04:00
memset ( _hasSprite , 0 , sizeof ( _hasSprite ) ) ;
2017-03-31 22:14:16 -04:00
if ( _prevRenderingEnabled ) {
//copy horizontal scrolling value from t
_state . VideoRamAddr = ( _state . VideoRamAddr & ~ 0x041F ) | ( _state . TmpVideoRamAddr & 0x041F ) ;
}
2016-12-16 07:13:33 -05:00
}
2015-12-29 13:23:38 -05:00
if ( IsRenderingEnabled ( ) ) {
//"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)
_state . SpriteRamAddr = 0 ;
2016-01-03 15:35:40 -05:00
if ( ( _cycle - 260 ) % 8 = = 0 ) {
//Cycle 260, 268, etc. This is an approximation (each tile is actually loaded in 8 steps (e.g from 257 to 264))
2015-12-29 13:23:38 -05:00
LoadSpriteTileInfo ( ) ;
2016-06-02 20:35:08 -04:00
} else if ( ( _cycle - 257 ) % 8 = = 0 ) {
//Garbage NT sprite fetch (257, 265, 273, etc.) - Required for proper MC-ACC IRQs (MMC3 clone)
2018-06-02 22:12:00 -04:00
ReadVram ( GetNameTableAddr ( ) ) ;
2016-07-22 19:11:25 -04:00
} else if ( ( _cycle - 259 ) % 8 = = 0 ) {
//Garbage AT sprite fetch
2018-06-02 22:12:00 -04:00
ReadVram ( GetAttributeAddr ( ) ) ;
2015-12-29 13:23:38 -05:00
}
2014-06-17 18:16:49 -04:00
2017-03-31 22:14:16 -04:00
if ( _scanline = = - 1 & & _cycle > = 280 & & _cycle < = 304 ) {
//copy vertical scrolling value from t
_state . VideoRamAddr = ( _state . VideoRamAddr & ~ 0x7BE0 ) | ( _state . TmpVideoRamAddr & 0x7BE0 ) ;
}
2014-06-17 18:16:49 -04:00
}
2016-01-03 19:39:41 -05:00
} else if ( _cycle > = 321 & & _cycle < = 336 ) {
2017-03-31 22:14:16 -04:00
LoadTileInfo ( ) ;
2016-12-02 18:10:37 -05:00
if ( _cycle = = 321 ) {
2017-03-31 22:14:16 -04:00
if ( IsRenderingEnabled ( ) ) {
_oamCopybuffer = _secondarySpriteRAM [ 0 ] ;
}
if ( _scanline = = - 1 ) {
2018-07-01 15:21:05 -04:00
_console - > DebugSetLastFramePpuScroll ( _state . VideoRamAddr , _state . XScroll , false ) ;
2017-03-31 22:14:16 -04:00
}
} else if ( _prevRenderingEnabled & & ( _cycle = = 328 | | _cycle = = 336 ) ) {
2017-04-21 21:29:39 -04:00
_state . LowBitShift < < = 8 ;
_state . HighBitShift < < = 8 ;
2017-03-31 22:14:16 -04:00
IncHorizontalScrolling ( ) ;
2016-12-02 18:10:37 -05:00
}
2016-07-22 19:11:25 -04:00
} else if ( _cycle = = 337 | | _cycle = = 339 ) {
if ( IsRenderingEnabled ( ) ) {
2018-06-02 22:12:00 -04:00
ReadVram ( GetNameTableAddr ( ) ) ;
2016-01-03 15:35:40 -05:00
2017-03-31 22:14:16 -04:00
if ( _scanline = = - 1 & & _nesModel = = NesModel : : NTSC & & _cycle = = 339 & & ( _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 = 340 ;
}
2016-07-22 19:11:25 -04:00
}
2014-06-17 18:16:49 -04:00
}
2014-06-20 21:48:55 -04:00
}
2014-06-18 23:59:10 -04:00
2017-04-07 19:24:34 -04:00
void PPU : : ProcessSpriteEvaluation ( )
2014-06-20 21:48:55 -04:00
{
2017-04-07 19:24:34 -04:00
if ( IsRenderingEnabled ( ) | | ( _nesModel = = NesModel : : PAL & & _scanline > = _palSpriteEvalScanline ) ) {
2017-02-24 21:43:42 -05:00
if ( _cycle < 65 ) {
//Clear secondary OAM at between cycle 1 and 64
_oamCopybuffer = 0xFF ;
_secondarySpriteRAM [ ( _cycle - 1 ) > > 1 ] = 0xFF ;
2014-06-22 01:54:35 -04:00
} else {
2017-02-24 21:43:42 -05:00
if ( _cycle = = 65 ) {
_sprite0Added = false ;
_spriteInRange = false ;
_secondaryOAMAddr = 0 ;
_overflowSpriteAddr = 0 ;
2017-03-10 21:29:48 -05:00
_overflowBugCounter = 0 ;
2017-02-24 21:43:42 -05:00
_oamCopyDone = false ;
_spriteAddrH = ( _state . SpriteRamAddr > > 2 ) & 0x3F ;
_spriteAddrL = _state . SpriteRamAddr & 0x03 ;
} else if ( _cycle = = 256 ) {
_sprite0Visible = _sprite0Added ;
_spriteCount = ( _secondaryOAMAddr > > 2 ) ;
}
if ( _cycle & 0x01 ) {
//Read a byte from the primary OAM on odd cycles
2017-04-08 14:13:10 -04:00
_oamCopybuffer = ReadSpriteRam ( _state . SpriteRamAddr ) ;
2016-01-03 11:44:20 -05:00
} else {
2017-02-24 21:43:42 -05:00
if ( _oamCopyDone ) {
_spriteAddrH = ( _spriteAddrH + 1 ) & 0x3F ;
2017-03-10 21:29:48 -05:00
if ( _secondaryOAMAddr > = 0x20 ) {
//"As seen above, a side effect of the OAM write disable signal is to turn writes to the secondary OAM into reads from it."
_oamCopybuffer = _secondarySpriteRAM [ _secondaryOAMAddr & 0x1F ] ;
}
2017-02-24 21:43:42 -05:00
} else {
if ( ! _spriteInRange & & _scanline > = _oamCopybuffer & & _scanline < _oamCopybuffer + ( _flags . LargeSprites ? 16 : 8 ) ) {
_spriteInRange = true ;
}
2014-06-20 21:48:55 -04:00
2017-02-24 21:43:42 -05:00
if ( _secondaryOAMAddr < 0x20 ) {
//Copy 1 byte to secondary OAM
_secondarySpriteRAM [ _secondaryOAMAddr ] = _oamCopybuffer ;
2014-06-20 21:48:55 -04:00
2017-02-24 21:43:42 -05:00
if ( _spriteInRange ) {
_spriteAddrL + + ;
_secondaryOAMAddr + + ;
2014-06-20 21:48:55 -04:00
2017-02-24 21:43:42 -05:00
if ( _spriteAddrH = = 0 ) {
_sprite0Added = true ;
}
2014-06-21 12:42:40 -04:00
2017-02-24 21:43:42 -05:00
if ( _spriteAddrL = = 4 ) {
//Done copying all 4 bytes
_spriteInRange = false ;
_spriteAddrL = 0 ;
_spriteAddrH = ( _spriteAddrH + 1 ) & 0x3F ;
if ( _spriteAddrH = = 0 ) {
_oamCopyDone = true ;
}
}
} else {
//Nothing to copy, skip to next sprite
2016-01-03 11:44:20 -05:00
_spriteAddrH = ( _spriteAddrH + 1 ) & 0x3F ;
if ( _spriteAddrH = = 0 ) {
_oamCopyDone = true ;
}
}
} else {
2017-03-10 21:29:48 -05:00
//"As seen above, a side effect of the OAM write disable signal is to turn writes to the secondary OAM into reads from it."
_oamCopybuffer = _secondarySpriteRAM [ _secondaryOAMAddr & 0x1F ] ;
2017-02-24 21:43:42 -05:00
//8 sprites have been found, check next sprite for overflow + emulate PPU bug
if ( _overflowSpriteAddr = = 0 ) {
//Used to remove sprite limit
_overflowSpriteAddr = _spriteAddrH * 4 ;
2016-01-03 11:44:20 -05:00
}
2016-01-14 22:20:50 -05:00
2017-02-24 21:43:42 -05:00
if ( _spriteInRange ) {
//Sprite is visible, consider this to be an overflow
_statusFlags . SpriteOverflow = true ;
2017-03-10 21:29:48 -05:00
_spriteAddrL = ( _spriteAddrL + 1 ) ;
2017-02-24 21:43:42 -05:00
if ( _spriteAddrL = = 4 ) {
_spriteAddrH = ( _spriteAddrH + 1 ) & 0x3F ;
_spriteAddrL = 0 ;
}
2017-03-10 21:29:48 -05:00
if ( _overflowBugCounter = = 0 ) {
_overflowBugCounter = 3 ;
} else if ( _overflowBugCounter > 0 ) {
_overflowBugCounter - - ;
if ( _overflowBugCounter = = 0 ) {
//"After it finishes "fetching" this sprite(and setting the overflow flag), it realigns back at the beginning of this line and then continues here on the next sprite"
_oamCopyDone = true ;
_spriteAddrL = 0 ;
}
}
2017-02-24 21:43:42 -05:00
} else {
//Sprite isn't on this scanline, trigger sprite evaluation bug - increment both H & L at the same time
2016-01-03 11:44:20 -05:00
_spriteAddrH = ( _spriteAddrH + 1 ) & 0x3F ;
2017-02-24 21:43:42 -05:00
_spriteAddrL = ( _spriteAddrL + 1 ) & 0x03 ;
2016-01-03 11:44:20 -05:00
2017-02-24 21:43:42 -05:00
if ( _spriteAddrH = = 0 ) {
_oamCopyDone = true ;
}
2016-01-03 11:44:20 -05:00
}
2014-06-22 16:11:28 -04:00
}
2014-06-20 21:48:55 -04:00
}
2017-03-10 21:29:48 -05:00
_state . SpriteRamAddr = ( _spriteAddrL & 0x03 ) | ( _spriteAddrH < < 2 ) ;
2014-06-20 21:48:55 -04:00
}
2014-06-20 22:31:48 -04:00
}
2014-06-18 22:54:23 -04:00
}
}
2017-04-08 14:13:10 -04:00
uint8_t PPU : : ReadSpriteRam ( uint8_t addr )
{
if ( ! _enableOamDecay ) {
return _spriteRAM [ addr ] ;
} else {
2018-07-01 15:21:05 -04:00
int32_t cycle = _console - > GetCpu ( ) - > GetCycleCount ( ) ;
2017-04-08 14:13:10 -04:00
if ( _oamDecayCycles [ addr > > 2 ] > = cycle ) {
_oamDecayCycles [ addr > > 2 ] = cycle + 3000 ;
return _spriteRAM [ addr ] ;
} else {
//If this 4-byte row hasn't been read/written to in over 3000 cpu cycles (~1.7ms), return 0xFF to simulate decay
return 0xFF ;
}
}
}
void PPU : : WriteSpriteRam ( uint8_t addr , uint8_t value )
{
_spriteRAM [ addr ] = value ;
if ( _enableOamDecay ) {
2018-07-01 15:21:05 -04:00
_oamDecayCycles [ addr > > 2 ] = _console - > GetCpu ( ) - > GetCycleCount ( ) + 3000 ;
2017-04-08 14:13:10 -04:00
}
}
2016-06-05 10:26:05 -04:00
void PPU : : DebugSendFrame ( )
{
2018-07-01 15:21:05 -04:00
_console - > GetVideoDecoder ( ) - > UpdateFrame ( _currentOutputBuffer ) ;
2016-06-05 10:26:05 -04:00
}
2018-02-16 17:36:37 -05:00
void PPU : : DebugCopyOutputBuffer ( uint16_t * target )
{
memcpy ( target , _currentOutputBuffer , PPU : : PixelCount * sizeof ( uint16_t ) ) ;
}
2015-08-14 21:50:14 -04:00
void PPU : : SendFrame ( )
{
2017-04-20 21:58:35 -04:00
UpdateGrayscaleAndIntensifyBits ( ) ;
2018-07-02 14:49:19 -04:00
_console - > GetNotificationManager ( ) - > SendNotification ( ConsoleNotificationType : : PpuFrameDone , _currentOutputBuffer ) ;
2017-04-08 14:13:10 -04:00
2018-01-04 19:03:47 -05:00
# ifdef LIBRETRO
2018-07-01 15:21:05 -04:00
_console - > GetVideoDecoder ( ) - > UpdateFrameSync ( _currentOutputBuffer ) ;
2018-01-04 19:03:47 -05:00
# else
2018-07-01 15:21:05 -04:00
if ( _console - > GetRewindManager ( ) - > IsRewinding ( ) ) {
if ( ! _console - > GetRewindManager ( ) - > IsStepBack ( ) ) {
_console - > GetVideoDecoder ( ) - > UpdateFrameSync ( _currentOutputBuffer ) ;
2017-08-01 22:49:50 -04:00
}
2017-04-28 19:54:58 -04:00
} else {
2018-02-23 12:18:12 -05:00
//If VideoDecoder isn't done with the previous frame, UpdateFrame will block until it is ready to accept a new frame.
2018-07-01 15:21:05 -04:00
_console - > GetVideoDecoder ( ) - > UpdateFrame ( _currentOutputBuffer ) ;
2017-04-28 19:54:58 -04:00
}
2017-04-08 14:13:10 -04:00
_enableOamDecay = EmulationSettings : : CheckFlag ( EmulationFlags : : EnableOamDecay ) ;
2018-01-04 19:03:47 -05:00
# endif
2015-08-14 21:50:14 -04:00
}
2014-06-17 18:16:49 -04:00
void PPU : : BeginVBlank ( )
{
2017-04-21 21:29:39 -04:00
TriggerNmi ( ) ;
2014-06-17 18:16:49 -04:00
}
2016-01-30 19:33:32 -05:00
void PPU : : TriggerNmi ( )
{
2016-06-01 17:42:52 -04:00
_statusFlags . VerticalBlank = true ;
2016-01-30 19:33:32 -05:00
if ( _flags . VBlank ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > SetNmiFlag ( ) ;
2016-01-30 19:33:32 -05:00
}
}
2017-04-29 21:39:57 -04:00
void PPU : : UpdateApuStatus ( )
{
2018-07-01 15:21:05 -04:00
APU * apu = _console - > GetApu ( ) ;
apu - > SetApuStatus ( true ) ;
2017-04-29 21:39:57 -04:00
if ( _scanline > 240 ) {
if ( _scanline > _standardVblankEnd ) {
//Disable APU for extra lines after NMI
2018-07-01 15:21:05 -04:00
apu - > SetApuStatus ( false ) ;
2017-05-14 09:40:28 -04:00
} else if ( _scanline > = _standardNmiScanline & & _scanline < _nmiScanline ) {
2017-04-29 21:39:57 -04:00
//Disable APU for extra lines before NMI
2018-07-01 15:21:05 -04:00
apu - > SetApuStatus ( false ) ;
2017-04-29 21:39:57 -04:00
}
}
}
2018-02-16 20:05:15 -05:00
void PPU : : DebugUpdateFrameBuffer ( bool toGrayscale )
{
//Clear output buffer for "Draw partial frame" feature
if ( toGrayscale ) {
for ( int i = 0 ; i < PPU : : PixelCount ; i + + ) {
2018-02-23 12:18:12 -05:00
_currentOutputBuffer [ i ] & = 0x30 ;
2018-02-16 20:05:15 -05:00
}
} else {
memset ( _currentOutputBuffer , 0 , PPU : : PixelCount * 2 ) ;
}
}
2015-07-04 22:21:14 -04:00
void PPU : : Exec ( )
2014-06-14 18:20:56 -04:00
{
2016-12-06 19:03:29 -05:00
if ( _cycle > 339 ) {
2017-04-21 21:29:39 -04:00
_cycle = 0 ;
2016-01-30 19:33:32 -05:00
if ( + + _scanline > _vblankEnd ) {
2017-04-20 21:58:35 -04:00
_lastUpdatedPixel = - 1 ;
2015-07-24 19:20:54 -04:00
_scanline = - 1 ;
2016-10-23 13:29:37 -04:00
UpdateMinimumDrawCycles ( ) ;
2015-07-24 19:20:54 -04:00
}
2017-04-21 21:29:39 -04:00
2018-07-01 15:21:05 -04:00
_console - > DebugProcessPpuCycle ( ) ;
2017-04-29 21:39:57 -04:00
UpdateApuStatus ( ) ;
2017-11-19 23:08:23 -05:00
if ( _scanline = = EmulationSettings : : GetInputPollScanline ( ) ) {
2018-07-01 15:21:05 -04:00
_console - > GetControlManager ( ) - > UpdateInputState ( ) ;
2017-11-19 23:08:23 -05:00
}
2017-04-21 21:29:39 -04:00
//Cycle = 0
if ( _scanline = = - 1 ) {
_statusFlags . SpriteOverflow = false ;
_statusFlags . Sprite0Hit = false ;
2018-02-23 12:18:12 -05:00
//Switch to alternate output buffer (VideoDecoder may still be decoding the last frame buffer)
_currentOutputBuffer = ( _currentOutputBuffer = = _outputBuffers [ 0 ] ) ? _outputBuffers [ 1 ] : _outputBuffers [ 0 ] ;
2018-01-07 10:31:37 -05:00
} else if ( _scanline = = 240 ) {
SendFrame ( ) ;
2018-03-24 09:48:45 -04:00
_frameCount + + ;
2017-04-21 21:29:39 -04:00
} else if ( _scanline = = _nmiScanline ) {
BeginVBlank ( ) ;
}
} else {
//Cycle > 0
_cycle + + ;
2018-07-01 15:21:05 -04:00
_console - > DebugProcessPpuCycle ( ) ;
2017-04-21 21:29:39 -04:00
if ( _scanline < 240 ) {
ProcessScanline ( ) ;
} else if ( _nesModel = = NesModel : : PAL & & _scanline > = _palSpriteEvalScanline ) {
//"On a PAL machine, because of its extended vertical blank, the PPU begins refreshing OAM roughly 21 scanlines after NMI[2], to prevent it
//from decaying during the longer hiatus of rendering. Additionally, it will continue to refresh during the visible portion of the screen
//even if rendering is disabled. Because of this, OAM DMA must be done near the beginning of vertical blank on PAL, and everywhere else
//it is liable to conflict with the refresh. Since the refresh can't be disabled like on the NTSC hardware, OAM decay does not occur at all on the PAL NES."
if ( _cycle < = 256 ) {
ProcessSpriteEvaluation ( ) ;
} else if ( _cycle > = 257 & & _cycle < 320 ) {
_state . SpriteRamAddr = 0 ;
}
2016-06-25 20:46:54 -04:00
}
2014-06-27 12:18:07 -04:00
}
2016-01-11 17:18:01 -05:00
2017-12-04 00:09:11 -05:00
if ( _needStateUpdate ) {
UpdateState ( ) ;
}
}
void PPU : : UpdateState ( )
{
_needStateUpdate = false ;
2016-01-11 17:18:01 -05:00
//Rendering enabled flag is apparently set with a 1 cycle delay (i.e setting it at cycle 5 will render cycle 6 like cycle 5 and then take the new settings for cycle 7)
2016-02-14 13:57:47 -05:00
_prevRenderingEnabled = _renderingEnabled ;
2017-03-31 22:14:16 -04:00
_renderingEnabled = _flags . BackgroundEnabled | _flags . SpritesEnabled ;
2017-12-04 00:09:11 -05:00
if ( _prevRenderingEnabled ! = _renderingEnabled ) {
_needStateUpdate = true ;
}
2017-03-10 22:21:14 -05:00
if ( _updateVramAddrDelay > 0 ) {
_updateVramAddrDelay - - ;
if ( _updateVramAddrDelay = = 0 ) {
_state . VideoRamAddr = _updateVramAddr ;
2018-06-02 22:12:00 -04:00
if ( _scanline > = 240 | | ! IsRenderingEnabled ( ) ) {
//Only set the VRAM address on the bus if the PPU is rendering
//More info here: https://forums.nesdev.com/viewtopic.php?p=132145#p132145
//Trigger bus address change when setting the vram address - needed by MMC3 IRQ counter
//"4) Should be clocked when A12 changes to 1 via $2006 write"
SetBusAddress ( _state . VideoRamAddr & 0x3FFF ) ;
}
2017-12-04 00:09:11 -05:00
} else {
_needStateUpdate = true ;
}
}
if ( _ignoreVramRead > 0 ) {
_ignoreVramRead - - ;
if ( _ignoreVramRead > 0 ) {
_needStateUpdate = true ;
2017-03-10 22:21:14 -05:00
}
}
2015-07-04 22:21:14 -04:00
}
2018-07-01 15:21:05 -04:00
void PPU : : ProcessCpuClock ( )
2015-07-04 22:21:14 -04:00
{
2017-03-31 22:14:16 -04:00
if ( ! EmulationSettings : : HasOverclock ( ) ) {
2018-07-01 15:21:05 -04:00
Exec ( ) ;
Exec ( ) ;
Exec ( ) ;
if ( _nesModel = = NesModel : : PAL & & _console - > GetCpu ( ) - > GetCycleCount ( ) % 5 = = 0 ) {
2016-06-12 18:11:31 -04:00
//PAL PPU runs 3.2 clocks for every CPU clock, so we need to run an extra clock every 5 CPU clocks
2018-07-01 15:21:05 -04:00
Exec ( ) ;
2016-06-12 18:11:31 -04:00
}
} else {
2018-07-01 15:21:05 -04:00
if ( _nesModel = = NesModel : : PAL ) {
2016-06-12 18:11:31 -04:00
//PAL PPU runs 3.2 clocks for every CPU clock, so we need to run an extra clock every 5 CPU clocks
2018-07-01 15:21:05 -04:00
_cyclesNeeded + = 3.2 / ( EmulationSettings : : GetOverclockRate ( ) / 100.0 ) ;
2016-06-12 18:11:31 -04:00
} else {
2018-07-01 15:21:05 -04:00
_cyclesNeeded + = 3.0 / ( EmulationSettings : : GetOverclockRate ( ) / 100.0 ) ;
2016-06-12 18:11:31 -04:00
}
2018-07-01 15:21:05 -04:00
while ( _cyclesNeeded > = 1.0 ) {
Exec ( ) ;
_cyclesNeeded - - ;
2016-06-12 18:11:31 -04:00
}
}
2014-06-25 21:52:37 -04:00
}
void PPU : : StreamState ( bool saving )
{
2016-06-02 23:56:11 -04:00
ArrayInfo < uint8_t > paletteRam = { _paletteRAM , 0x20 } ;
ArrayInfo < uint8_t > spriteRam = { _spriteRAM , 0x100 } ;
ArrayInfo < uint8_t > secondarySpriteRam = { _secondarySpriteRAM , 0x20 } ;
ArrayInfo < int32_t > openBusDecayStamp = { _openBusDecayStamp , 8 } ;
2017-04-08 14:13:10 -04:00
2017-03-24 18:33:15 -04:00
bool disablePpu2004Reads = false ;
bool disablePaletteRead = false ;
bool disableOamAddrBug = false ;
2017-02-25 15:15:44 -05:00
if ( saving ) {
disablePpu2004Reads = EmulationSettings : : CheckFlag ( EmulationFlags : : DisablePpu2004Reads ) ;
disablePaletteRead = EmulationSettings : : CheckFlag ( EmulationFlags : : DisablePaletteRead ) ;
disableOamAddrBug = EmulationSettings : : CheckFlag ( EmulationFlags : : DisableOamAddrBug ) ;
}
2017-02-23 20:00:52 -05:00
uint16_t unusedSpriteDmaAddr = 0 ;
uint16_t unusedSpriteDmaCounter = 0 ;
2017-03-10 22:21:14 -05:00
bool unusedSkipScrollIncrement = false ;
2017-02-23 20:00:52 -05:00
2016-06-02 20:20:26 -04:00
Stream ( _state . Control , _state . Mask , _state . Status , _state . SpriteRamAddr , _state . VideoRamAddr , _state . XScroll , _state . TmpVideoRamAddr , _state . WriteToggle ,
_state . HighBitShift , _state . LowBitShift , _flags . VerticalWrite , _flags . SpritePatternAddr , _flags . BackgroundPatternAddr , _flags . LargeSprites , _flags . VBlank ,
_flags . Grayscale , _flags . BackgroundMask , _flags . SpriteMask , _flags . BackgroundEnabled , _flags . SpritesEnabled , _flags . IntensifyRed , _flags . IntensifyGreen ,
_flags . IntensifyBlue , _paletteRamMask , _intensifyColorBits , _statusFlags . SpriteOverflow , _statusFlags . Sprite0Hit , _statusFlags . VerticalBlank , _scanline ,
_cycle , _frameCount , _memoryReadBuffer , _currentTile . LowByte , _currentTile . HighByte , _currentTile . PaletteOffset , _nextTile . LowByte , _nextTile . HighByte ,
_nextTile . PaletteOffset , _nextTile . TileAddr , _previousTile . LowByte , _previousTile . HighByte , _previousTile . PaletteOffset , _spriteIndex , _spriteCount ,
2017-02-23 20:00:52 -05:00
_secondaryOAMAddr , _sprite0Visible , _oamCopybuffer , _spriteInRange , _sprite0Added , _spriteAddrH , _spriteAddrL , _oamCopyDone , _nesModel , unusedSpriteDmaAddr ,
2017-03-10 22:21:14 -05:00
unusedSpriteDmaCounter , _prevRenderingEnabled , _renderingEnabled , _openBus , _ignoreVramRead , unusedSkipScrollIncrement , paletteRam , spriteRam , secondarySpriteRam ,
2018-06-02 22:12:00 -04:00
openBusDecayStamp , _cyclesNeeded , disablePpu2004Reads , disablePaletteRead , disableOamAddrBug , _overflowBugCounter , _updateVramAddr , _updateVramAddrDelay ,
_needStateUpdate , _ppuBusAddress ) ;
2014-06-25 21:52:37 -04:00
2016-01-14 22:20:50 -05:00
for ( int i = 0 ; i < 64 ; i + + ) {
2016-06-02 20:20:26 -04:00
Stream ( _spriteTiles [ i ] . SpriteX , _spriteTiles [ i ] . LowByte , _spriteTiles [ i ] . HighByte , _spriteTiles [ i ] . PaletteOffset , _spriteTiles [ i ] . HorizontalMirror , _spriteTiles [ i ] . BackgroundPriority ) ;
}
2016-05-31 17:12:43 -04:00
2015-07-21 23:05:27 -04:00
if ( ! saving ) {
2017-02-25 15:15:44 -05:00
EmulationSettings : : SetFlagState ( EmulationFlags : : DisablePpu2004Reads , disablePpu2004Reads ) ;
EmulationSettings : : SetFlagState ( EmulationFlags : : DisablePaletteRead , disablePaletteRead ) ;
EmulationSettings : : SetFlagState ( EmulationFlags : : DisableOamAddrBug , disableOamAddrBug ) ;
2015-07-21 23:05:27 -04:00
SetNesModel ( _nesModel ) ;
2016-10-23 13:29:37 -04:00
UpdateMinimumDrawCycles ( ) ;
2017-04-02 17:41:24 -04:00
2017-04-08 14:13:10 -04:00
for ( int i = 0 ; i < 0x40 ; i + + ) {
//Set max value to ensure oam decay doesn't cause issues with savestates when used
_oamDecayCycles [ i ] = 0x7FFFFFFF ;
}
2017-04-02 17:41:24 -04:00
for ( int i = 0 ; i < 257 ; i + + ) {
_hasSprite [ i ] = true ;
}
2017-04-20 21:58:35 -04:00
_lastUpdatedPixel = - 1 ;
2017-04-29 21:39:57 -04:00
UpdateApuStatus ( ) ;
2015-07-21 23:05:27 -04:00
}
2014-06-14 11:27:55 -04:00
}