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"
2015-07-21 23:05:27 -04:00
# include "EmulationSettings.h"
2015-08-30 21:04:21 -04:00
# include "VideoDecoder.h"
2014-06-14 11:27:55 -04:00
2014-06-25 17:30:35 -04:00
PPU * PPU : : Instance = nullptr ;
2014-06-19 17:06:00 -04:00
2014-06-15 21:45:36 -04:00
PPU : : PPU ( MemoryManager * memoryManager )
2014-06-14 11:27:55 -04:00
{
2014-06-25 17:30:35 -04:00
PPU : : Instance = this ;
2014-06-15 21:45:36 -04:00
_memoryManager = memoryManager ;
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 ] ;
2014-06-23 13:52:53 -04:00
2016-01-01 12:33:43 -05:00
memset ( _paletteRAM , 0x0F , 0x20 ) ;
memset ( _spriteRAM , 0xFF , 0x100 ) ;
memset ( _secondarySpriteRAM , 0xFF , 0x20 ) ;
2014-06-23 13:52:53 -04:00
Reset ( ) ;
2014-06-14 18:20:56 -04:00
}
PPU : : ~ PPU ( )
{
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-01-02 20:36:22 -05:00
_openBus = 0 ;
memset ( _openBusDecayStamp , 0 , sizeof ( _openBusDecayStamp ) ) ;
2016-01-02 18:02:56 -05:00
_skipTick = false ;
2014-06-23 13:52:53 -04:00
_state = { } ;
_flags = { } ;
_statusFlags = { } ;
_scanline = 0 ;
2016-01-02 13:52:55 -05:00
_cycle = 0 ;
2016-01-02 20:36:22 -05:00
_frameCount = 1 ;
2014-06-23 13:52:53 -04:00
_memoryReadBuffer = 0 ;
}
2015-07-21 23:05:27 -04:00
void PPU : : SetNesModel ( NesModel model )
{
_nesModel = model ;
_vblankEnd = ( model = = NesModel : : NTSC ? 260 : 311 ) ;
}
2015-07-01 23:17:14 -04:00
PPUDebugState PPU : : GetState ( )
2014-06-14 18:20:56 -04:00
{
2015-07-01 23:17:14 -04:00
PPUDebugState state ;
state . ControlFlags = _flags ;
state . StatusFlags = _statusFlags ;
state . State = _state ;
state . Cycle = _cycle ;
state . Scanline = _scanline ;
return state ;
2014-06-14 11:27:55 -04:00
}
2014-06-22 00:37:45 -04:00
void PPU : : UpdateVideoRamAddr ( )
{
if ( _scanline > = 239 | | ! IsRenderingEnabled ( ) ) {
_state . VideoRamAddr + = _flags . VerticalWrite ? 32 : 1 ;
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"
_memoryManager - > ReadVRAM ( _state . VideoRamAddr ) ;
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 ;
}
_openBus = openBus & 0xFF ;
}
}
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 ;
break ;
2014-06-14 11:27:55 -04:00
case PPURegisters : : SpriteData :
2015-12-29 13:23:38 -05:00
if ( _scanline < = 240 & & IsRenderingEnabled ( ) & & ( _cycle > = 257 | | _cycle < = 64 ) ) {
if ( _cycle > = 257 & & _cycle < = 320 ) {
//Set OAM copy buffer to its proper value. This is done here for performance.
//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 ] ;
}
2016-01-02 20:36:22 -05:00
returnValue = _oamCopybuffer ;
2015-12-29 13:23:38 -05:00
} else {
2016-01-02 20:36:22 -05:00
returnValue = _spriteRAM [ _state . SpriteRamAddr ] ;
2015-12-29 13:23:38 -05:00
}
2016-01-02 20:36:22 -05:00
openBusMask = 0x00 ;
break ;
2014-06-14 11:27:55 -04:00
case PPURegisters : : VideoMemoryData :
2014-06-17 18:16:49 -04:00
returnValue = _memoryReadBuffer ;
2015-08-17 19:32:10 -04:00
_memoryReadBuffer = _memoryManager - > ReadVRAM ( _state . VideoRamAddr , MemoryOperationType : : Read ) ;
2014-06-17 18:16:49 -04:00
if ( _state . VideoRamAddr > = 0x3F00 ) {
2016-01-02 20:36:22 -05:00
returnValue = ReadPaletteRAM ( _state . VideoRamAddr ) | ( _openBus & 0xC0 ) ;
openBusMask = 0xC0 ;
} else {
openBusMask = 0x00 ;
2014-06-17 18:16:49 -04:00
}
2014-06-20 22:31:48 -04:00
2014-06-22 00:37:45 -04:00
UpdateVideoRamAddr ( ) ;
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 :
2014-06-22 00:37:45 -04:00
SetControlRegister ( value ) ;
2014-06-14 18:20:56 -04:00
break ;
2014-06-22 00:37:45 -04:00
case PPURegisters : : Mask :
SetMaskRegister ( value ) ;
2014-06-14 18:20:56 -04:00
break ;
case PPURegisters : : SpriteAddr :
_state . SpriteRamAddr = value ;
break ;
case PPURegisters : : SpriteData :
2015-12-28 20:07:27 -05:00
if ( _spriteDmaCounter > 0 ) {
2015-12-28 23:20:38 -05:00
_spriteRAM [ _spriteDmaAddr & 0xFF ] = value ;
2015-12-28 20:07:27 -05:00
_spriteDmaAddr + + ;
_spriteDmaCounter - - ;
2015-07-25 17:05:56 -04:00
} else {
2015-12-28 20:07:27 -05:00
if ( _scanline > = 240 | | ! IsRenderingEnabled ( ) ) {
2015-12-29 13:23:38 -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
_spriteRAM [ _state . SpriteRamAddr ] = value ;
_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 + = 4 ;
}
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 ) ;
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 ;
2014-06-17 18:16:49 -04:00
_state . VideoRamAddr = _state . TmpVideoRamAddr ;
2014-06-25 17:30:35 -04:00
//Trigger memory read when setting the vram address - needed by MMC3 IRQ counter
2014-06-26 16:41:07 -04:00
//"4) Should be clocked when A12 changes to 1 via $2006 write"
2014-06-25 17:30:35 -04:00
_memoryManager - > ReadVRAM ( _state . VideoRamAddr ) ;
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 :
2014-06-20 22:31:48 -04:00
if ( _state . VideoRamAddr > = 0x3F00 ) {
WritePaletteRAM ( _state . VideoRamAddr , value ) ;
} else {
_memoryManager - > WriteVRAM ( _state . VideoRamAddr , value ) ;
}
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 :
2015-12-28 20:07:27 -05:00
//_spriteDmaAddr & _spriteDmaCounter are probably not the correct solution for this
//Missing something else in the PPU/CPU DMA logic maybe?
_spriteDmaCounter = 0x100 ;
_spriteDmaAddr = _state . SpriteRamAddr ;
CPU : : RunDMATransfer ( _spriteRAM , 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 ;
}
2015-07-22 22:08:28 -04:00
return ( _paletteRAM [ addr ] & _paletteRamMask ) ;
2014-06-20 22:31:48 -04:00
}
void PPU : : WritePaletteRAM ( uint16_t addr , uint8_t value )
{
addr & = 0x1F ;
if ( addr = = 0x10 | | addr = = 0x14 | | addr = = 0x18 | | addr = = 0x1C ) {
addr & = ~ 0x10 ;
}
_paletteRAM [ addr ] = value ;
}
2014-06-17 18:16:49 -04:00
bool PPU : : IsRenderingEnabled ( )
{
return _flags . BackgroundEnabled | | _flags . SpritesEnabled ;
}
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 ;
2014-06-27 12:18:07 -04:00
2015-07-24 19:20:54 -04:00
if ( ! originalVBlank & & _flags . VBlank & & _statusFlags . VerticalBlank & & ( _scanline ! = - 1 | | _cycle ! = 0 ) ) {
2014-06-21 23:54:32 -04:00
CPU : : SetNMIFlag ( ) ;
2016-01-02 13:52:55 -05:00
}
if ( _scanline = = 241 & & _cycle < 3 & & ! _flags . VBlank ) {
2014-06-27 12:18:07 -04:00
CPU : : 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
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 ;
2015-07-22 22:08:28 -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"
_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 ;
} else {
//"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 ) |
( ( uint8_t ) _statusFlags . Sprite0Hit < < 6 ) |
2014-06-15 09:35:17 -04:00
( ( uint8_t ) _statusFlags . VerticalBlank < < 7 ) ;
_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."
_statusFlags . VerticalBlank = false ;
CPU : : 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
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 ) ;
}
2014-06-19 19:58:15 -04:00
void PPU : : LoadTileInfo ( )
{
2014-07-26 20:21:36 -04:00
if ( IsRenderingEnabled ( ) ) {
2014-06-26 16:41:07 -04:00
_previousTile = _currentTile ;
_currentTile = _nextTile ;
2014-06-19 19:58:15 -04:00
2014-06-26 16:41:07 -04:00
uint16_t tileIndex = _memoryManager - > ReadVRAM ( GetNameTableAddr ( ) ) ;
uint16_t tileAddr = ( tileIndex < < 4 ) | ( _state . VideoRamAddr > > 12 ) | _flags . BackgroundPatternAddr ;
uint16_t shift = ( ( _state . VideoRamAddr > > 4 ) & 0x04 ) | ( _state . VideoRamAddr & 0x02 ) ;
_nextTile . PaletteOffset = ( ( _memoryManager - > ReadVRAM ( GetAttributeAddr ( ) ) > > shift ) & 0x03 ) < < 2 ;
_nextTile . LowByte = _memoryManager - > ReadVRAM ( tileAddr ) ;
_nextTile . HighByte = _memoryManager - > ReadVRAM ( tileAddr + 8 ) ;
2015-08-14 21:50:14 -04:00
_nextTile . TileAddr = tileAddr ;
_nextTile . OffsetY = _state . VideoRamAddr > > 12 ;
2014-06-26 16:41:07 -04:00
}
2014-06-19 19:58:15 -04:00
}
2015-12-29 13:23:38 -05:00
void PPU : : LoadSpriteTileInfo ( )
2014-06-20 21:48:55 -04:00
{
2015-12-29 13:23:38 -05:00
uint32_t spriteAddr = _spriteIndex * 4 ;
uint8_t spriteY = _secondarySpriteRAM [ spriteAddr ] ;
uint8_t tileIndex = _secondarySpriteRAM [ spriteAddr + 1 ] ;
uint8_t attributes = _secondarySpriteRAM [ spriteAddr + 2 ] ;
uint8_t spriteX = _secondarySpriteRAM [ spriteAddr + 3 ] ;
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
2015-12-29 13:23:38 -05:00
if ( _spriteIndex < _spriteCount & & spriteY < 240 ) {
_spriteTiles [ _spriteIndex ] . BackgroundPriority = backgroundPriority ;
_spriteTiles [ _spriteIndex ] . HorizontalMirror = horizontalMirror ;
_spriteTiles [ _spriteIndex ] . VerticalMirror = verticalMirror ;
_spriteTiles [ _spriteIndex ] . PaletteOffset = ( ( attributes & 0x03 ) < < 2 ) | 0x10 ;
_spriteTiles [ _spriteIndex ] . LowByte = _memoryManager - > ReadVRAM ( tileAddr ) ;
_spriteTiles [ _spriteIndex ] . HighByte = _memoryManager - > ReadVRAM ( tileAddr + 8 ) ;
_spriteTiles [ _spriteIndex ] . TileAddr = tileAddr ;
_spriteTiles [ _spriteIndex ] . OffsetY = lineOffset ;
_spriteTiles [ _spriteIndex ] . SpriteX = spriteX ;
} else {
//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 ;
}
2015-12-29 13:23:38 -05:00
_memoryManager - > ReadVRAM ( tileAddr ) ;
_memoryManager - > 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
}
2014-06-19 19:58:15 -04:00
void PPU : : LoadNextTile ( )
{
_state . LowBitShift | = _nextTile . LowByte ;
_state . HighBitShift | = _nextTile . HighByte ;
}
void PPU : : InitializeShiftRegisters ( )
{
_state . LowBitShift = ( _currentTile . LowByte < < 8 ) | _nextTile . LowByte ;
_state . HighBitShift = ( _currentTile . HighByte < < 8 ) | _nextTile . HighByte ;
}
void PPU : : ShiftTileRegisters ( )
{
_state . LowBitShift < < = 1 ;
_state . HighBitShift < < = 1 ;
}
2015-08-14 21:50:14 -04:00
uint32_t PPU : : GetPixelColor ( uint32_t & paletteOffset )
2014-06-19 19:58:15 -04:00
{
2015-08-14 21:50:14 -04:00
uint8_t offset = _state . XScroll ;
uint32_t backgroundColor = 0 ;
if ( ( _cycle > 8 | | _flags . BackgroundMask ) & & _flags . BackgroundEnabled ) {
//BackgroundMask = false: Hide background in leftmost 8 pixels of screen
backgroundColor = ( ( ( _state . LowBitShift < < offset ) & 0x8000 ) > > 15 ) | ( ( ( _state . HighBitShift < < offset ) & 0x8000 ) > > 14 ) ;
}
2014-06-21 20:18:05 -04:00
2015-08-14 21:50:14 -04:00
if ( ( _cycle > 8 | | _flags . SpriteMask ) & & _flags . SpritesEnabled ) {
//SpriteMask = true: Hide sprites in leftmost 8 pixels of screen
for ( uint8_t i = 0 ; i < _spriteCount ; i + + ) {
int32_t shift = - ( ( int32_t ) _spriteTiles [ i ] . SpriteX - ( int32_t ) _cycle + 1 ) ;
if ( shift > = 0 & & shift < 8 ) {
_lastSprite = & _spriteTiles [ i ] ;
uint32_t spriteColor ;
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 ;
}
if ( spriteColor ! = 0 ) {
//First sprite without a 00 color, use it.
if ( i = = 0 & & backgroundColor ! = 0 & & _sprite0Visible & & _cycle ! = 256 & & _flags . BackgroundEnabled ) {
//"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"
_statusFlags . Sprite0Hit = true ;
2014-06-28 22:52:28 -04:00
}
2015-07-05 08:47:34 -04:00
2015-08-14 21:50:14 -04:00
if ( backgroundColor = = 0 | | ! _spriteTiles [ i ] . BackgroundPriority ) {
//Check sprite priority
paletteOffset = _lastSprite - > PaletteOffset ;
return 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
}
}
2015-08-14 21:50:14 -04:00
}
paletteOffset = ( ( offset + ( ( _cycle - 1 ) & 0x07 ) < 8 ) ? _previousTile : _currentTile ) . PaletteOffset ;
return backgroundColor ;
}
void PPU : : DrawPixel ( )
{
//This is called 3.7 million times per second - needs to be as fast as possible.
2015-08-30 21:04:21 -04:00
uint16_t & pixel = _currentOutputBuffer [ ( _scanline < < 8 ) + _cycle - 1 ] ;
2015-08-14 21:50:14 -04:00
if ( IsRenderingEnabled ( ) | | ( ( _state . VideoRamAddr & 0x3F00 ) ! = 0x3F00 ) ) {
uint32_t paletteOffset ;
uint32_t color = GetPixelColor ( paletteOffset ) ;
if ( color = = 0 ) {
pixel = ReadPaletteRAM ( 0x3F00 ) | _intensifyColorBits ;
} else {
pixel = ReadPaletteRAM ( 0x3F00 + paletteOffset + color ) | _intensifyColorBits ;
}
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."
pixel = ReadPaletteRAM ( _state . VideoRamAddr ) | _intensifyColorBits ;
2014-06-20 21:48:55 -04:00
}
2014-06-19 19:58:15 -04:00
}
2014-06-20 21:48:55 -04:00
void PPU : : ProcessPreVBlankScanline ( )
2014-06-17 18:16:49 -04:00
{
2014-06-20 21:48:55 -04:00
//For pre-render scanline & all visible scanlines
2014-06-17 18:16:49 -04:00
if ( IsRenderingEnabled ( ) ) {
2014-06-20 21:48:55 -04:00
//Update video ram address according to scrolling logic
2015-07-21 18:18:20 -04:00
if ( ( _cycle > 0 & & _cycle < 256 & & ( _cycle & 0x07 ) = = 0 ) | | _cycle = = 328 | | _cycle = = 336 ) {
2015-07-04 22:21:14 -04:00
IncHorizontalScrolling ( ) ;
} else if ( _cycle = = 256 ) {
2014-06-20 21:48:55 -04:00
IncVerticalScrolling ( ) ;
} else if ( _cycle = = 257 ) {
//copy horizontal scrolling value from t
_state . VideoRamAddr = ( _state . VideoRamAddr & ~ 0x041F ) | ( _state . TmpVideoRamAddr & 0x041F ) ;
}
}
if ( _cycle > = 257 & & _cycle < = 320 ) {
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 ;
if ( ( _cycle - 261 ) % 8 = = 0 ) {
//Cycle 261, 269, etc. This is an approximation (each tile is actually loaded in 8 steps (e.g from 257 to 264))
LoadSpriteTileInfo ( ) ;
} else if ( _cycle = = 257 ) {
_spriteIndex = 0 ;
}
}
} else if ( _cycle = = 321 & & IsRenderingEnabled ( ) ) {
_oamCopybuffer = _secondarySpriteRAM [ 0 ] ;
2014-06-17 18:16:49 -04:00
}
2014-06-20 21:48:55 -04:00
}
void PPU : : ProcessPrerenderScanline ( )
{
ProcessPreVBlankScanline ( ) ;
2014-06-17 18:16:49 -04:00
2015-07-25 17:08:04 -04:00
if ( _cycle = = 0 & & _state . SpriteRamAddr > = 0x08 ) {
//"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"
memcpy ( _spriteRAM , _spriteRAM + ( _state . SpriteRamAddr & 0xF8 ) , 8 ) ;
}
2015-07-24 19:20:54 -04:00
if ( _cycle = = 1 ) {
2014-06-17 18:16:49 -04:00
_statusFlags . SpriteOverflow = false ;
_statusFlags . Sprite0Hit = false ;
_statusFlags . VerticalBlank = false ;
2014-06-26 16:41:07 -04:00
}
2015-07-21 18:18:20 -04:00
if ( ( ( _cycle - 1 ) & 0x07 ) = = 0 & & _cycle < 250 ) {
2014-06-26 16:41:07 -04:00
LoadTileInfo ( ) ;
2014-06-17 18:16:49 -04:00
} else if ( _cycle > = 280 & & _cycle < = 304 ) {
if ( IsRenderingEnabled ( ) ) {
//copy vertical scrolling value from t
2014-06-22 00:37:45 -04:00
_state . VideoRamAddr = ( _state . VideoRamAddr & ~ 0x7BE0 ) | ( _state . TmpVideoRamAddr & 0x7BE0 ) ;
2014-06-17 18:16:49 -04:00
}
2016-01-02 18:02:56 -05:00
} else if ( _nesModel = = NesModel : : NTSC & & _cycle = = 338 & & IsRenderingEnabled ( ) & & ( _frameCount & 0x01 ) ) {
//Check for the cycle skip in the else if block below
//If, at cycle 338 in the prerender scanline, rendering is enabled, we skip a tick (cycle 340) on the current frame
_skipTick = true ;
} else if ( _cycle = = 339 & & _skipTick ) {
2015-07-21 23:05:27 -04:00
//This behavior is NTSC-specific - PAL frames are always the same number of cycles
2014-06-27 12:18:07 -04:00
//"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340)
2014-06-21 23:54:32 -04:00
_cycle = - 1 ;
2014-06-18 22:54:23 -04:00
_scanline = 0 ;
2016-01-02 18:02:56 -05:00
_skipTick = false ;
2014-06-18 22:54:23 -04:00
} else if ( _cycle = = 321 | | _cycle = = 329 ) {
LoadTileInfo ( ) ;
2014-06-19 19:58:15 -04:00
if ( _cycle = = 329 ) {
InitializeShiftRegisters ( ) ;
}
2014-06-18 22:54:23 -04:00
}
}
2014-06-17 18:16:49 -04:00
void PPU : : ProcessVisibleScanline ( )
{
2014-06-20 21:48:55 -04:00
if ( _cycle > 0 & & _cycle < = 256 ) {
2015-07-21 18:18:20 -04:00
if ( ( ( _cycle - 1 ) & 0x07 ) = = 0 ) {
2014-06-20 21:48:55 -04:00
//Cycle 1, 9, 17, etc.
if ( _cycle ! = 1 ) {
LoadNextTile ( ) ;
}
LoadTileInfo ( ) ;
}
DrawPixel ( ) ;
2015-07-04 22:21:14 -04:00
ShiftTileRegisters ( ) ;
2014-06-21 09:00:10 -04:00
if ( IsRenderingEnabled ( ) ) {
CopyOAMData ( ) ;
}
2014-06-18 23:59:10 -04:00
} else if ( _cycle = = 321 | | _cycle = = 329 ) {
LoadTileInfo ( ) ;
2014-06-19 19:58:15 -04:00
if ( _cycle = = 329 ) {
InitializeShiftRegisters ( ) ;
}
2014-06-17 18:16:49 -04:00
}
2014-06-18 22:54:23 -04:00
2014-06-20 21:48:55 -04:00
ProcessPreVBlankScanline ( ) ;
}
2014-06-18 23:59:10 -04:00
2014-06-20 21:48:55 -04:00
void PPU : : CopyOAMData ( )
{
2014-06-22 01:54:35 -04:00
if ( _cycle < 65 ) {
//Clear secondary OAM at between cycle 0 and 64
2015-12-29 13:23:38 -05:00
_oamCopybuffer = 0xFF ;
_secondarySpriteRAM [ _cycle > > 1 ] = _oamCopybuffer ;
2014-06-22 01:54:35 -04:00
} else {
2014-06-20 21:48:55 -04:00
if ( _cycle = = 65 ) {
2014-06-22 16:11:28 -04:00
_overflowCounter = 0 ;
2014-06-21 12:42:40 -04:00
_sprite0Added = false ;
2014-07-01 18:05:54 -04:00
_writeOAMData = false ;
2014-06-20 21:48:55 -04:00
_secondaryOAMAddr = 0 ;
2014-06-21 12:42:40 -04:00
} else if ( _cycle = = 256 ) {
_sprite0Visible = _sprite0Added ;
2014-06-22 13:00:31 -04:00
_spriteCount = ( _secondaryOAMAddr > > 2 ) ;
2014-06-17 18:16:49 -04:00
}
2014-06-22 01:54:35 -04:00
if ( _cycle & 0x01 ) {
//Read a byte from the primary OAM on odd cycles
2014-07-01 18:05:54 -04:00
_oamCopybuffer = _spriteRAM [ _state . SpriteRamAddr & 0xFF ] ;
2014-06-22 01:54:35 -04:00
_state . SpriteRamAddr + + ;
} else {
2015-07-21 18:18:20 -04:00
if ( ! _writeOAMData & & _scanline > = _oamCopybuffer & & _scanline < _oamCopybuffer + ( _flags . LargeSprites ? 16 : 8 ) & & _state . SpriteRamAddr < 0x100 ) {
2014-07-01 18:05:54 -04:00
_writeOAMData = true ;
2014-06-22 01:54:35 -04:00
}
2014-06-20 21:48:55 -04:00
2014-06-22 01:54:35 -04:00
if ( _secondaryOAMAddr < 0x20 ) {
//Copy 1 byte to secondary OAM
2014-07-01 18:05:54 -04:00
_secondarySpriteRAM [ _secondaryOAMAddr ] = _oamCopybuffer ;
2014-06-20 21:48:55 -04:00
2014-07-01 18:05:54 -04:00
if ( _writeOAMData ) {
2014-06-22 01:54:35 -04:00
_secondaryOAMAddr + + ;
2014-06-20 21:48:55 -04:00
2014-06-22 01:54:35 -04:00
if ( _state . SpriteRamAddr = = 0x01 ) {
_sprite0Added = true ;
}
2014-06-21 12:42:40 -04:00
2014-06-22 01:54:35 -04:00
if ( ( _secondaryOAMAddr & 0x03 ) = = 0 ) {
//Done copying
2014-07-01 18:05:54 -04:00
_writeOAMData = false ;
2014-06-20 21:48:55 -04:00
}
} else {
2014-06-22 01:54:35 -04:00
_state . SpriteRamAddr + = 3 ;
}
} else {
2014-06-22 16:11:28 -04:00
//8 sprites have been found, check next sprite for overflow + emulate PPU bug
//Based on: http://forums.nesdev.com/viewtopic.php?p=85431#p85431
//Behavior matches: http://forums.nesdev.com/viewtopic.php?p=1387#p1387
2014-06-22 01:54:35 -04:00
if ( ! _statusFlags . SpriteOverflow ) {
2014-07-01 18:05:54 -04:00
if ( _writeOAMData ) {
2014-06-22 01:54:35 -04:00
//Sprite is visible, consider this to be an overflow
_statusFlags . SpriteOverflow = true ;
2014-06-22 16:11:28 -04:00
_overflowCounter = 3 ;
} else if ( ( _state . SpriteRamAddr & 0x3 ) ! = 0 ) {
//Sprite isn't on this scanline, trigger sprite evaluation bug
2014-06-22 01:54:35 -04:00
_state . SpriteRamAddr + = 4 ;
2014-06-21 09:00:10 -04:00
}
2014-06-22 16:11:28 -04:00
} else {
if ( _overflowCounter ! = 0 ) {
_overflowCounter - - ;
if ( _overflowCounter = = 0 ) {
_state . SpriteRamAddr = ( _state . SpriteRamAddr + 3 ) & 0x0FFC ;
}
} else {
_state . SpriteRamAddr = ( _state . SpriteRamAddr + 4 ) & 0x0FFC ;
}
2014-06-20 21:48:55 -04:00
}
}
2014-06-20 22:31:48 -04:00
}
2014-06-18 22:54:23 -04:00
}
}
2015-08-14 21:50:14 -04:00
void PPU : : SendFrame ( )
{
2015-12-26 17:11:00 -05:00
MessageManager : : SendNotification ( ConsoleNotificationType : : PpuFrameDone , _currentOutputBuffer ) ;
2015-08-30 21:04:21 -04:00
if ( VideoDecoder : : GetInstance ( ) - > UpdateFrame ( _currentOutputBuffer ) ) {
_currentOutputBuffer = ( _currentOutputBuffer = = _outputBuffers [ 0 ] ) ? _outputBuffers [ 1 ] : _outputBuffers [ 0 ] ;
2015-08-14 21:50:14 -04:00
}
}
2014-06-17 18:16:49 -04:00
void PPU : : BeginVBlank ( )
{
2016-01-02 13:52:55 -05:00
if ( _cycle = = 0 ) {
2015-08-14 21:50:14 -04:00
SendFrame ( ) ;
2016-01-02 13:52:55 -05:00
_statusFlags . VerticalBlank = true ;
if ( _flags . VBlank ) {
CPU : : SetNMIFlag ( ) ;
2014-06-17 18:16:49 -04:00
}
}
}
void PPU : : EndVBlank ( )
{
if ( _cycle = = 340 ) {
_frameCount + + ;
}
}
2015-07-04 22:21:14 -04:00
void PPU : : Exec ( )
2014-06-14 18:20:56 -04:00
{
2015-07-24 19:20:54 -04:00
if ( _cycle = = 340 ) {
_cycle = - 1 ;
if ( _scanline + + = = _vblankEnd ) {
_scanline = - 1 ;
}
}
_cycle + + ;
2015-07-04 22:21:14 -04:00
if ( _scanline ! = - 1 & & _scanline < 240 ) {
ProcessVisibleScanline ( ) ;
} else if ( _scanline = = - 1 ) {
ProcessPrerenderScanline ( ) ;
} else if ( _scanline = = 241 ) {
BeginVBlank ( ) ;
2015-07-21 23:05:27 -04:00
} else if ( _scanline = = _vblankEnd ) {
2015-07-04 22:21:14 -04:00
EndVBlank ( ) ;
2014-06-27 12:18:07 -04:00
}
2015-07-04 22:21:14 -04:00
}
void PPU : : ExecStatic ( )
{
PPU : : Instance - > Exec ( ) ;
PPU : : Instance - > Exec ( ) ;
PPU : : Instance - > Exec ( ) ;
2015-07-21 23:05:27 -04:00
if ( PPU : : Instance - > _nesModel = = NesModel : : PAL & & CPU : : GetCycleCount ( ) % 5 = = 0 ) {
//PAL PPU runs 3.2 clocks for every CPU clock, so we need to run an extra clock every 5 CPU clocks
PPU : : Instance - > Exec ( ) ;
}
2014-06-25 21:52:37 -04:00
}
void PPU : : StreamState ( bool saving )
{
Stream < uint8_t > ( _state . Control ) ;
Stream < uint8_t > ( _state . Mask ) ;
Stream < uint8_t > ( _state . Status ) ;
Stream < uint32_t > ( _state . SpriteRamAddr ) ;
Stream < uint16_t > ( _state . VideoRamAddr ) ;
Stream < uint8_t > ( _state . XScroll ) ;
Stream < uint16_t > ( _state . TmpVideoRamAddr ) ;
Stream < bool > ( _state . WriteToggle ) ;
Stream < uint16_t > ( _state . HighBitShift ) ;
Stream < uint16_t > ( _state . LowBitShift ) ;
2014-07-01 18:05:54 -04:00
Stream < bool > ( _flags . VerticalWrite ) ;
Stream < uint16_t > ( _flags . SpritePatternAddr ) ;
Stream < uint16_t > ( _flags . BackgroundPatternAddr ) ;
Stream < bool > ( _flags . LargeSprites ) ;
Stream < bool > ( _flags . VBlank ) ;
Stream < bool > ( _flags . Grayscale ) ;
Stream < bool > ( _flags . BackgroundMask ) ;
Stream < bool > ( _flags . SpriteMask ) ;
Stream < bool > ( _flags . BackgroundEnabled ) ;
Stream < bool > ( _flags . SpritesEnabled ) ;
Stream < bool > ( _flags . IntensifyRed ) ;
Stream < bool > ( _flags . IntensifyGreen ) ;
Stream < bool > ( _flags . IntensifyBlue ) ;
2015-07-22 22:08:28 -04:00
Stream < uint8_t > ( _paletteRamMask ) ;
Stream < uint16_t > ( _intensifyColorBits ) ;
2014-07-01 18:05:54 -04:00
Stream < bool > ( _statusFlags . SpriteOverflow ) ;
Stream < bool > ( _statusFlags . Sprite0Hit ) ;
Stream < bool > ( _statusFlags . VerticalBlank ) ;
2014-06-25 21:52:37 -04:00
Stream < int32_t > ( _scanline ) ;
Stream < uint32_t > ( _cycle ) ;
2016-01-02 20:36:22 -05:00
Stream < uint32_t > ( _frameCount ) ;
2014-06-25 21:52:37 -04:00
Stream < uint8_t > ( _memoryReadBuffer ) ;
2015-08-05 21:43:53 -04:00
StreamArray < uint8_t > ( _paletteRAM , 0x20 ) ;
2014-06-25 21:52:37 -04:00
StreamArray < uint8_t > ( _spriteRAM , 0x100 ) ;
StreamArray < uint8_t > ( _secondarySpriteRAM , 0x20 ) ;
Stream < uint8_t > ( _currentTile . LowByte ) ;
Stream < uint8_t > ( _currentTile . HighByte ) ;
Stream < uint32_t > ( _currentTile . PaletteOffset ) ;
Stream < uint8_t > ( _nextTile . LowByte ) ;
Stream < uint8_t > ( _nextTile . HighByte ) ;
Stream < uint32_t > ( _nextTile . PaletteOffset ) ;
Stream < uint8_t > ( _previousTile . LowByte ) ;
Stream < uint8_t > ( _previousTile . HighByte ) ;
Stream < uint32_t > ( _previousTile . PaletteOffset ) ;
for ( int i = 0 ; i < 8 ; i + + ) {
2015-08-14 21:50:14 -04:00
Stream < uint8_t > ( _spriteTiles [ i ] . SpriteX ) ;
2014-06-25 21:52:37 -04:00
Stream < uint8_t > ( _spriteTiles [ i ] . LowByte ) ;
Stream < uint8_t > ( _spriteTiles [ i ] . HighByte ) ;
Stream < uint32_t > ( _spriteTiles [ i ] . PaletteOffset ) ;
Stream < bool > ( _spriteTiles [ i ] . HorizontalMirror ) ;
Stream < bool > ( _spriteTiles [ i ] . BackgroundPriority ) ;
}
2015-12-29 13:23:38 -05:00
Stream < uint32_t > ( _spriteIndex ) ;
2014-06-25 21:52:37 -04:00
Stream < uint32_t > ( _spriteCount ) ;
Stream < uint32_t > ( _secondaryOAMAddr ) ;
Stream < bool > ( _sprite0Visible ) ;
2014-07-01 18:05:54 -04:00
Stream < uint8_t > ( _oamCopybuffer ) ;
Stream < bool > ( _writeOAMData ) ;
Stream < uint32_t > ( _overflowCounter ) ;
Stream < bool > ( _sprite0Added ) ;
2015-07-21 23:05:27 -04:00
Stream < NesModel > ( _nesModel ) ;
2015-12-28 20:07:27 -05:00
Stream < uint16_t > ( _spriteDmaAddr ) ;
Stream < uint16_t > ( _spriteDmaCounter ) ;
2016-01-02 18:02:56 -05:00
Stream < bool > ( _skipTick ) ;
2016-01-02 20:36:22 -05:00
Stream < uint8_t > ( _openBus ) ;
StreamArray < int32_t > ( _openBusDecayStamp , 8 ) ;
2015-07-21 23:05:27 -04:00
if ( ! saving ) {
SetNesModel ( _nesModel ) ;
}
2014-06-14 11:27:55 -04:00
}