Files
Taylor Lineman c5dacd8c13 Static cast ints to bytes to allow for compilation
This matches the changes on the core Nestopia repository that this project points at.

Signed-off-by: Taylor Lineman <git@actuallytaylor.com>
2025-10-08 17:46:55 -04:00

3502 lines
73 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////////////
#include <cstring>
#include "NstCpu.hpp"
#include "NstPpu.hpp"
#include "NstState.hpp"
namespace Nes
{
namespace Core
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
const byte Ppu::yuvMaps[4][0x40] =
{
{
0x35, 0x23, 0x16, 0x22, 0x1C, 0x09, 0x2D, 0x15,
0x20, 0x00, 0x27, 0x05, 0x04, 0x28, 0x08, 0x20,
0x21, 0x27, 0x07, 0x29, 0x3C, 0x32, 0x36, 0x12,
0x28, 0x2B, 0x0D, 0x08, 0x10, 0x3D, 0x24, 0x01,
0x01, 0x31, 0x33, 0x2A, 0x2C, 0x0C, 0x1B, 0x14,
0x0D, 0x07, 0x34, 0x06, 0x13, 0x02, 0x26, 0x0D,
0x0D, 0x19, 0x10, 0x0A, 0x39, 0x03, 0x37, 0x17,
0x09, 0x11, 0x1A, 0x1D, 0x38, 0x25, 0x18, 0x3A
},
{
0x0D, 0x27, 0x18, 0x39, 0x3A, 0x25, 0x1C, 0x31,
0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0x3C, 0x1A,
0x09, 0x21, 0x06, 0x10, 0x1B, 0x29, 0x08, 0x22,
0x2D, 0x24, 0x01, 0x2B, 0x32, 0x08, 0x0D, 0x03,
0x04, 0x36, 0x26, 0x33, 0x11, 0x07, 0x10, 0x02,
0x14, 0x28, 0x00, 0x09, 0x12, 0x0D, 0x28, 0x20,
0x27, 0x1D, 0x2A, 0x17, 0x0C, 0x01, 0x15, 0x19,
0x0D, 0x2C, 0x07, 0x37, 0x35, 0x05, 0x0A, 0x3D
},
{
0x14, 0x25, 0x3A, 0x10, 0x1A, 0x20, 0x31, 0x09,
0x01, 0x0D, 0x36, 0x08, 0x15, 0x10, 0x27, 0x3C,
0x22, 0x1C, 0x05, 0x12, 0x19, 0x18, 0x17, 0x1B,
0x00, 0x03, 0x0D, 0x02, 0x16, 0x06, 0x34, 0x35,
0x23, 0x09, 0x01, 0x37, 0x1D, 0x27, 0x26, 0x20,
0x29, 0x04, 0x21, 0x24, 0x11, 0x3D, 0x0D, 0x07,
0x2C, 0x08, 0x39, 0x33, 0x07, 0x2A, 0x28, 0x2D,
0x0A, 0x0D, 0x32, 0x38, 0x13, 0x2B, 0x28, 0x0C
},
{
0x18, 0x03, 0x1C, 0x28, 0x0D, 0x35, 0x01, 0x17,
0x10, 0x07, 0x2A, 0x01, 0x36, 0x37, 0x1A, 0x39,
0x25, 0x08, 0x12, 0x34, 0x0D, 0x2D, 0x06, 0x26,
0x27, 0x1B, 0x22, 0x19, 0x04, 0x0D, 0x3A, 0x21,
0x05, 0x0A, 0x07, 0x02, 0x13, 0x14, 0x00, 0x15,
0x0C, 0x10, 0x11, 0x09, 0x1D, 0x38, 0x3D, 0x24,
0x33, 0x20, 0x08, 0x16, 0x28, 0x2B, 0x20, 0x3C,
0x0D, 0x27, 0x23, 0x31, 0x29, 0x32, 0x2C, 0x09
}
};
Ppu::Tiles::Tiles()
: padding0(0), padding1(0) {}
Ppu::Oam::Oam()
: limit(buffer + STD_LINE_SPRITES*4), spriteLimit(true) {}
Ppu::Output::Output(Video::Screen::Pixel* p)
: pixels(p) {}
Ppu::TileLut::TileLut()
{
for (uint i=0; i < 0x400; ++i)
{
block[i][0] = (i & 0xC0) ? (i >> 6 & 0xC) | (i >> 6 & 0x3) : 0;
block[i][1] = (i & 0x30) ? (i >> 6 & 0xC) | (i >> 4 & 0x3) : 0;
block[i][2] = (i & 0x0C) ? (i >> 6 & 0xC) | (i >> 2 & 0x3) : 0;
block[i][3] = (i & 0x03) ? (i >> 6 & 0xC) | (i >> 0 & 0x3) : 0;
}
}
Ppu::Ppu(Cpu& c)
:
cpu (c),
output (screen.pixels),
model (PPU_RP2C02),
rgbMap (NULL),
yuvMap (NULL)
{
cycles.one = PPU_RP2C02_CC;
PowerOff();
}
void Ppu::PowerOff()
{
Reset( true, false, false );
}
void Ppu::Reset(bool hard,bool acknowledged)
{
Reset( hard, acknowledged, true );
}
void Ppu::Reset(const bool hard,const bool acknowledged,const bool map)
{
if (map)
{
for (uint i=0x2000; i < 0x4000; i += 0x8)
{
cpu.Map( i+0 ).Set( this, i != 0x3000 ? &Ppu::Peek_2xxx : &Ppu::Peek_3000, &Ppu::Poke_2000 );
cpu.Map( i+1 ).Set( this, &Ppu::Peek_2xxx, &Ppu::Poke_2001 );
cpu.Map( i+2 ).Set( this, &Ppu::Peek_2002, &Ppu::Poke_2xxx );
cpu.Map( i+3 ).Set( this, &Ppu::Peek_2xxx, &Ppu::Poke_2003 );
cpu.Map( i+4 ).Set( this, &Ppu::Peek_2004, &Ppu::Poke_2004 );
cpu.Map( i+5 ).Set( this, &Ppu::Peek_2xxx, &Ppu::Poke_2005 );
cpu.Map( i+6 ).Set( this, &Ppu::Peek_2xxx, &Ppu::Poke_2006 );
cpu.Map( i+7 ).Set( this, &Ppu::Peek_2007, &Ppu::Poke_2007 );
}
if (model == PPU_RC2C05_01 || model == PPU_RC2C05_04)
{
for (uint i=0x2002; i < 0x4000; i += 0x8)
cpu.Map( i ).Set( &Ppu::Peek_2002_RC2C05_01_04 );
}
else if (model == PPU_RC2C05_02)
{
for (uint i=0x2002; i < 0x4000; i += 0x8)
cpu.Map( i ).Set( &Ppu::Peek_2002_RC2C05_02 );
}
else if (model == PPU_RC2C05_03)
{
for (uint i=0x2002; i < 0x4000; i += 0x8)
cpu.Map( i ).Set( &Ppu::Peek_2002_RC2C05_03 );
}
else if (model == PPU_RC2C05_05)
{
for (uint i=0x2000; i < 0x4000; i += 0x8)
{
cpu.Map( i+0 ).Set( &Ppu::Poke_2001 );
cpu.Map( i+1 ).Set( &Ppu::Poke_2000 );
}
}
cpu.Map( 0x4014U ).Set( this, &Ppu::Peek_4014, &Ppu::Poke_4014 );
}
if (hard)
{
static const byte powerUpPalette[] =
{
0x09,0x01,0x00,0x01,0x00,0x02,0x02,0x0D,
0x08,0x10,0x08,0x24,0x00,0x00,0x04,0x2C,
0x09,0x01,0x34,0x03,0x00,0x04,0x00,0x14,
0x08,0x3A,0x00,0x02,0x00,0x20,0x2C,0x08
};
std::memcpy( palette.ram, powerUpPalette, Palette::SIZE );
std::memset( oam.ram, Oam::GARBAGE, Oam::SIZE );
std::memset( nameTable.ram, NameTable::GARBAGE, NameTable::SIZE );
io.latch = 0;
io.buffer = Io::BUFFER_GARBAGE;
regs.status = 0;
regs.ctrl[0] = 0;
regs.ctrl[1] = 0;
regs.frame = 0;
regs.oam = 0;
scroll.latch = 0;
scroll.xFine = 0;
scroll.toggle = 0;
scroll.address = 0;
output.burstPhase = 0;
cycles.reset = 0;
cycles.hClock = HCLOCK_BOOT;
}
else if (acknowledged)
{
io.buffer = 0;
regs.status = 0;
regs.ctrl[0] = 0;
regs.ctrl[1] = 0;
scroll.latch = 0;
scroll.xFine = 0;
scroll.toggle = 0;
cycles.reset = Cpu::CYCLE_MAX;
cycles.hClock = HCLOCK_BOOT;
std::memset( oam.ram, Oam::GARBAGE, Oam::SIZE );
}
else
{
cycles.hClock = HCLOCK_DUMMY;
cycles.reset = 0;
}
if (chr.Source().Empty())
{
chr.Source().Set( Ram::RAM, true, false, NameTable::SIZE, nameTable.ram );
chr.SwapBanks<SIZE_2K,0x0000>(0,0,0,0);
}
if (nmt.Source().Empty())
{
nmt.Source().Set( Ram::RAM, true, true, NameTable::SIZE, nameTable.ram );
nmt.SwapBanks<SIZE_2K,0x0000>(0,0);
}
chr.ResetAccessor();
nmt.ResetAccessors();
cycles.vClock = 0;
cycles.count = Cpu::CYCLE_MAX;
scanline = SCANLINE_VBLANK;
scanline_sleep = 0;
io.address = 0;
io.pattern = 0;
io.line.Unset();
tiles.pattern[0] = 0;
tiles.pattern[1] = 0;
tiles.attribute = 0;
tiles.index = 8;
tiles.mask = 0;
oam.index = 0;
oam.address = 0;
oam.latch = 0;
oam.spriteZeroInLine = false;
oam.phase = &Ppu::EvaluateSpritesPhase0;
oam.buffered = oam.buffer;
oam.visible = oam.output;
oam.mask = 0;
output.target = NULL;
hActiveHook.Unset();
hBlankHook.Unset();
UpdateStates();
screen.Clear();
}
uint Ppu::SetAddressLineHook(const Core::Io::Line& line)
{
io.line = line;
return io.address;
}
void Ppu::SetHActiveHook(const Hook& hook)
{
hActiveHook = hook;
}
void Ppu::SetHBlankHook(const Hook& hook)
{
hBlankHook = hook;
}
void Ppu::UpdateStates()
{
oam.height = (regs.ctrl[0] >> 2 & 8) + 8;
tiles.show[0] = (regs.ctrl[1] & Regs::CTRL1_BG_ENABLED) ? 0xFF : 0x00;
tiles.show[1] = (regs.ctrl[1] & Regs::CTRL1_BG_ENABLED_NO_CLIP) == Regs::CTRL1_BG_ENABLED_NO_CLIP ? 0xFF : 0x00;
oam.show[0] = (regs.ctrl[1] & Regs::CTRL1_SP_ENABLED) ? 0xFF : 0x00;
oam.show[1] = (regs.ctrl[1] & Regs::CTRL1_SP_ENABLED_NO_CLIP) == Regs::CTRL1_SP_ENABLED_NO_CLIP ? 0xFF : 0x00;
UpdatePalette();
}
void Ppu::UpdatePalette()
{
for (uint i=0, c=Coloring(), e=Emphasis(); i < Palette::SIZE; ++i)
output.palette[i] = ((rgbMap ? rgbMap[palette.ram[i] & uint(Palette::COLOR)] : palette.ram[i]) & c) | e;
}
void Ppu::SaveState(State::Saver& state,const dword baseChunk) const
{
state.Begin( baseChunk );
{
const byte data[11] =
{
static_cast<byte>(regs.ctrl[0]),
static_cast<byte>(regs.ctrl[1]),
static_cast<byte>(regs.status),
static_cast<byte>(scroll.address & 0xFF),
static_cast<byte>(scroll.address >> 8),
static_cast<byte>(scroll.latch & 0xFF),
static_cast<byte>(scroll.latch >> 8),
static_cast<byte>(scroll.xFine | scroll.toggle << 3),
static_cast<byte>(regs.oam),
static_cast<byte>(io.buffer),
static_cast<byte>(io.latch)
};
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
state.Begin( AsciiId<'P','A','L'>::V ).Compress( palette.ram ).End();
state.Begin( AsciiId<'O','A','M'>::V ).Compress( oam.ram ).End();
state.Begin( AsciiId<'N','M','T'>::V ).Compress( nameTable.ram ).End();
if (model == PPU_RP2C02)
state.Begin( AsciiId<'F','R','M'>::V ).Write8( (regs.frame & Regs::FRAME_ODD) == 0 ).End();
if (cycles.hClock == HCLOCK_BOOT)
state.Begin( AsciiId<'P','O','W'>::V ).Write8( 0x0 ).End();
state.End();
}
void Ppu::LoadState(State::Loader& state)
{
cycles.hClock = HCLOCK_DUMMY;
regs.frame = 0;
output.burstPhase = 0;
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<11> data( state );
regs.ctrl[0] = data[0];
regs.ctrl[1] = data[1];
regs.status = data[2] & Regs::STATUS_BITS;
scroll.address = data[3] | (data[4] << 8 & 0x7F00);
scroll.latch = data[5] | (data[6] << 8 & 0x7F00);
scroll.xFine = data[7] & 0x7;
scroll.toggle = data[7] >> 3 & 0x1;
regs.oam = data[8];
io.buffer = data[9];
io.latch = data[10];
break;
}
case AsciiId<'P','A','L'>::V:
state.Uncompress( palette.ram );
break;
case AsciiId<'O','A','M'>::V:
state.Uncompress( oam.ram );
break;
case AsciiId<'N','M','T'>::V:
state.Uncompress( nameTable.ram );
break;
case AsciiId<'F','R','M'>::V:
if (model == PPU_RP2C02)
regs.frame = (state.Read8() & 0x1) ? 0 : Regs::FRAME_ODD;
break;
case AsciiId<'P','O','W'>::V:
cycles.hClock = HCLOCK_BOOT;
break;
}
state.End();
}
output.bgColor = palette.ram[0] & uint(Palette::COLOR);
UpdateStates();
}
void Ppu::EnableCpuSynchronization()
{
cpu.AddHook( Hook(this,&Ppu::Hook_Sync) );
}
void Ppu::ChrMem::ResetAccessor()
{
accessor.Set( this, &ChrMem::Access_Pattern );
}
void Ppu::NmtMem::ResetAccessors()
{
accessors[0].Set( this, &NmtMem::Access_Name_2000 );
accessors[1].Set( this, &NmtMem::Access_Name_2400 );
accessors[2].Set( this, &NmtMem::Access_Name_2800 );
accessors[3].Set( this, &NmtMem::Access_Name_2C00 );
}
void Ppu::SetModel(const PpuModel m,const bool yuvConversion)
{
if (model != m)
{
model = m;
regs.frame = 0;
output.burstPhase = 0;
switch (model)
{
case PPU_RP2C07: cycles.one = PPU_RP2C07_CC; break;
case PPU_DENDY: cycles.one = PPU_DENDY_CC; break;
default: cycles.one = PPU_RP2C02_CC; break;
}
}
const byte* const map =
(
model == PPU_RP2C04_0001 ? yuvMaps[0] :
model == PPU_RP2C04_0002 ? yuvMaps[1] :
model == PPU_RP2C04_0003 ? yuvMaps[2] :
model == PPU_RP2C04_0004 ? yuvMaps[3] :
NULL
);
const byte* const tmp[2] =
{
yuvConversion ? NULL : map,
yuvConversion ? map : NULL
};
if (yuvMap != tmp[0] || rgbMap != tmp[1])
{
yuvMap = tmp[0];
rgbMap = tmp[1];
UpdatePalette();
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_FORCE_INLINE Cycle Ppu::GetCycles() const
{
return (cycles.vClock + cycles.hClock) * cycles.one;
}
NST_FORCE_INLINE Cycle Ppu::GetLocalCycles(Cycle clock) const
{
NST_COMPILE_ASSERT( PPU_DENDY_CC == PPU_RP2C02_CC || PPU_DENDY_CC == PPU_RP2C07_CC );
return cycles.one == PPU_RP2C02_CC ? clock / PPU_RP2C02_CC : (clock+PPU_RP2C07_CC-1) / PPU_RP2C07_CC;
}
void Ppu::BeginFrame(bool frameLock)
{
NST_ASSERT
(
(scanline == SCANLINE_VBLANK) &&
(cycles.hClock == HCLOCK_BOOT || cycles.hClock == HCLOCK_DUMMY) &&
(cpu.GetModel() == CPU_RP2A07) == (model == PPU_RP2C07) &&
(cpu.GetModel() == CPU_DENDY) == (model == PPU_DENDY)
);
oam.limit = oam.buffer + ((oam.spriteLimit || frameLock) ? Oam::STD_LINE_SPRITES*4 : Oam::MAX_LINE_SPRITES*4);
output.target = output.pixels;
Cycle frame;
switch (model)
{
case PPU_RP2C02:
regs.frame ^= Regs::FRAME_ODD;
default:
if (cycles.hClock == HCLOCK_DUMMY)
{
cycles.vClock = PPU_RP2C02_HVINT / PPU_RP2C02_CC - HCLOCK_DUMMY;
cycles.count = PPU_RP2C02_HVINT;
frame = PPU_RP2C02_HVSYNC_0;
}
else
{
cycles.vClock = PPU_RP2C02_HVSYNCBOOT / PPU_RP2C02_CC - HCLOCK_BOOT;
cycles.count = PPU_RP2C02_HVSYNCBOOT;
frame = PPU_RP2C02_HVSYNCBOOT;
}
break;
case PPU_RP2C07:
if (cycles.hClock == HCLOCK_DUMMY)
{
cycles.vClock = PPU_RP2C07_HVINT / PPU_RP2C07_CC - HCLOCK_DUMMY;
cycles.count = PPU_RP2C07_HVINT;
frame = PPU_RP2C07_HVSYNC;
}
else
{
cycles.vClock = PPU_RP2C07_HVSYNCBOOT / PPU_RP2C07_CC - HCLOCK_BOOT;
cycles.count = PPU_RP2C07_HVSYNCBOOT;
frame = PPU_RP2C07_HVSYNCBOOT;
}
break;
case PPU_DENDY:
if (cycles.hClock == HCLOCK_DUMMY)
{
cycles.vClock = PPU_DENDY_HVINT / PPU_DENDY_CC - HCLOCK_DUMMY;
cycles.count = PPU_DENDY_HVINT;
frame = PPU_DENDY_HVSYNC;
}
else
{
cycles.vClock = PPU_DENDY_HVSYNCBOOT / PPU_DENDY_CC - HCLOCK_BOOT;
cycles.count = PPU_DENDY_HVSYNCBOOT;
frame = PPU_DENDY_HVSYNCBOOT;
}
break;
}
cpu.SetFrameCycles( frame );
}
NES_HOOK(Ppu,Sync)
{
const Cycle elapsed = cpu.GetCycles();
if (cycles.count < elapsed)
{
cycles.count = GetLocalCycles( elapsed ) - cycles.vClock;
Run();
}
}
void Ppu::EndFrame()
{
if (cycles.count != Cpu::CYCLE_MAX)
{
cycles.count = Cpu::CYCLE_MAX;
Run();
}
}
void Ppu::Update(Cycle dataSetup,const uint readAddress)
{
dataSetup += cpu.Update( readAddress );
if (cycles.count < dataSetup)
{
cycles.count = GetLocalCycles( dataSetup ) - cycles.vClock;
Run();
}
}
void Ppu::SetMirroring(const byte (&banks)[4])
{
Update( cycles.one );
NST_ASSERT( banks[0] < 4 && banks[1] < 4 && banks[2] < 4 && banks[3] < 4 );
nmt.SwapBanks<SIZE_1K,0x0000>( banks[0], banks[1], banks[2], banks[3] );
}
void Ppu::SetMirroring(NmtMirroring mirroring)
{
Update( cycles.one );
nmt.SwapBanks<SIZE_1K,0x0000>
(
uint(mirroring) >> 0 & 0x1U,
uint(mirroring) >> 1 & 0x1U,
uint(mirroring) >> 2 & 0x1U,
uint(mirroring) >> 3 & 0x1U
);
}
NES_ACCESSOR(Ppu::ChrMem,Pattern)
{
return Peek( address );
}
NES_ACCESSOR(Ppu::NmtMem,Name_2000)
{
return (*this)[0][address];
}
NES_ACCESSOR(Ppu::NmtMem,Name_2400)
{
return (*this)[1][address];
}
NES_ACCESSOR(Ppu::NmtMem,Name_2800)
{
return (*this)[2][address];
}
NES_ACCESSOR(Ppu::NmtMem,Name_2C00)
{
return (*this)[3][address];
}
NST_FORCE_INLINE uint Ppu::Chr::FetchPattern(uint address) const
{
return accessor.Fetch( address & 0x1FFF );
}
NST_FORCE_INLINE uint Ppu::Nmt::FetchName(uint address) const
{
return accessors[address >> 10 & 0x3].Fetch( address & 0x3FF );
}
NST_FORCE_INLINE uint Ppu::Nmt::FetchAttribute(uint address) const
{
return accessors[address >> 10 & 0x3].Fetch( 0x3C0 | (address & 0x03F) );
}
NST_FORCE_INLINE void Ppu::UpdateAddressLine(uint address)
{
NST_ASSERT( address <= 0x3FFF );
io.address = address;
if (io.line)
io.line.Toggle( io.address, GetCycles() );
}
NST_FORCE_INLINE void Ppu::UpdateScrollAddressLine()
{
if (io.line)
io.line.Toggle( scroll.address & 0x3FFF, cpu.GetCycles() );
}
NST_FORCE_INLINE void Ppu::UpdateVramAddress()
{
if ((scanline != SCANLINE_VBLANK ) && (regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED))
{
scroll.ClockX();
scroll.ClockY();
}
else
{
scroll.address = (scroll.address + ((regs.ctrl[0] & Regs::CTRL0_INC32) ? 32 : 1)) & 0x7FFF;
}
}
NST_FORCE_INLINE void Ppu::OpenName()
{
UpdateAddressLine( 0x2000 | (scroll.address & 0x0FFF) );
}
NST_FORCE_INLINE void Ppu::FetchName()
{
io.pattern = nmt.FetchName( io.address ) << 4 | scroll.address >> 12 | (regs.ctrl[0] << 8 & 0x1000);
}
NST_FORCE_INLINE void Ppu::OpenAttribute()
{
UpdateAddressLine( 0x23C0 | (scroll.address & 0x0C00) | (scroll.address >> 4 & 0x0038) | (scroll.address >> 2 & 0x0007) );
}
NST_FORCE_INLINE void Ppu::FetchAttribute()
{
tiles.attribute = nmt.FetchAttribute( io.address ) >> ((scroll.address & 0x2) | (scroll.address >> 4 & 0x4));
}
NST_FORCE_INLINE void Ppu::OpenPattern(uint address)
{
UpdateAddressLine( address );
}
NST_FORCE_INLINE uint Ppu::FetchSpPattern() const
{
return chr.FetchPattern( io.address );
}
NST_FORCE_INLINE void Ppu::FetchBgPattern0()
{
const uint pattern = chr.FetchPattern( io.address );
tiles.pattern[1] = pattern >> 0 & 0x55;
tiles.pattern[0] = pattern >> 1 & 0x55;
}
NST_FORCE_INLINE void Ppu::FetchBgPattern1()
{
const uint pattern = chr.FetchPattern( io.address );
tiles.pattern[0] |= pattern << 0 & 0xAA;
tiles.pattern[1] |= pattern << 1 & 0xAA;
}
uint Ppu::GetPixelCycles() const
{
return (scanline+1)-1U < 240 ? scanline * 256 + NST_MIN(cycles.hClock,255) : ~0U;
}
NST_FORCE_INLINE bool Ppu::IsDead() const
{
return scanline == SCANLINE_VBLANK || !(regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED);
}
NST_FORCE_INLINE uint Ppu::Coloring() const
{
return (regs.ctrl[1] & Regs::CTRL1_MONOCHROME) ? Palette::MONO : Palette::COLOR;
}
NST_FORCE_INLINE uint Ppu::Emphasis() const
{
return (regs.ctrl[1] & Regs::CTRL1_EMPHASIS) << 1;
}
NST_FORCE_INLINE void Ppu::UpdateDecay(byte mask)
{
Cycle curCyc = cpu.GetCycles();
for (uint i = 0; i < 8; ++i)
{
if (mask & (1 << i))
decay.timestamp[i] = curCyc;
}
}
NES_POKE_D(Ppu,2000)
{
Update( cycles.one );
NST_VERIFY( cpu.GetCycles() >= cycles.reset || !data );
if (cpu.GetCycles() >= cycles.reset)
{
scroll.latch = (scroll.latch & 0x73FF) | (data & 0x03) << 10;
oam.height = (data >> 2 & 8) + 8;
io.latch = data;
UpdateDecay(0xFF);
data = regs.ctrl[0] ;
regs.ctrl[0] = io.latch;
if ((regs.ctrl[0] & regs.status & Regs::CTRL0_NMI) > data)
{
const Cycle clock = cpu.GetCycles() + cycles.one;
if (clock < GetHVIntClock())
cpu.DoNMI( clock );
}
}
}
NES_POKE_D(Ppu,2001)
{
Update( cycles.one );
NST_VERIFY( cpu.GetCycles() >= cycles.reset || !data );
if (cpu.GetCycles() >= cycles.reset)
{
if ((regs.ctrl[1] ^ data) & (Regs::CTRL1_BG_ENABLED_NO_CLIP|Regs::CTRL1_SP_ENABLED_NO_CLIP))
{
tiles.show[0] = (data & Regs::CTRL1_BG_ENABLED) ? 0xFF : 0x00;
tiles.show[1] = (data & Regs::CTRL1_BG_ENABLED_NO_CLIP) == Regs::CTRL1_BG_ENABLED_NO_CLIP ? 0xFF : 0x00;
oam.show[0] = (data & Regs::CTRL1_SP_ENABLED) ? 0xFF : 0x00;
oam.show[1] = (data & Regs::CTRL1_SP_ENABLED_NO_CLIP) == Regs::CTRL1_SP_ENABLED_NO_CLIP ? 0xFF : 0x00;
const uint pos = (cycles.hClock - 8) >= (256-16);
tiles.mask = tiles.show[pos];
oam.mask = oam.show[pos];
if ((regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED) && !(data & Regs::CTRL1_BG_SP_ENABLED))
UpdateScrollAddressLine();
}
io.latch = data;
UpdateDecay(0xFF);
data = (regs.ctrl[1] ^ data) & (Regs::CTRL1_EMPHASIS|Regs::CTRL1_MONOCHROME);
regs.ctrl[1] = io.latch;
if (data)
{
const uint ce[] = { Coloring(), Emphasis() };
const byte* const NST_RESTRICT map = rgbMap;
if (!map)
{
for (uint i=0; i < Palette::SIZE; ++i)
output.palette[i] = (palette.ram[i] & ce[0]) | ce[1];
}
else
{
for (uint i=0; i < Palette::SIZE; ++i)
output.palette[i] = (map[palette.ram[i] & Palette::COLOR] & ce[0]) | ce[1];
}
}
}
}
NES_PEEK_A(Ppu,2002)
{
Update( cycles.one, address );
byte mask = 0xE0;
uint status = regs.status & 0xFF;
regs.status &= (Regs::STATUS_VBLANK^0xFFU);
scroll.toggle = 0;
io.latch = (io.latch & Regs::STATUS_LATCH) | status;
UpdateDecay(mask);
Cycle curCyc = cpu.GetCycles();
for (uint i = 0; i < 5; ++i)
{
if ((curCyc - decay.timestamp[i]) < 24576)
mask |= (1 << i);
}
return io.latch & mask;
}
NES_PEEK_A(Ppu,2002_RC2C05_01_04)
{
return (NES_DO_PEEK(2002,address) & 0xC0) | 0x1B;
}
NES_PEEK_A(Ppu,2002_RC2C05_02)
{
return (NES_DO_PEEK(2002,address) & 0xC0) | 0x3D;
}
NES_PEEK_A(Ppu,2002_RC2C05_03)
{
return (NES_DO_PEEK(2002,address) & 0xC0) | 0x1C;
}
NES_POKE_D(Ppu,2003)
{
Update( cycles.one );
regs.oam = data;
io.latch = data;
UpdateDecay(0xFF);
}
NES_POKE_D(Ppu,2004)
{
Update( cycles.one );
NST_ASSERT( regs.oam < Oam::SIZE );
NST_VERIFY( IsDead() );
io.latch = data;
UpdateDecay(0xFF);
if (IsDead())
{
if ((regs.oam & 0x03) == 0x02)
data &= 0xE3;
}
else
{
data = 0xFF;
}
byte* const NST_RESTRICT value = oam.ram + regs.oam;
regs.oam = (regs.oam + 1) & 0xFF;
*value = data;
}
NES_PEEK(Ppu,2004)
{
NST_ASSERT( regs.oam <= 0xFF );
if (!(regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED) || cpu.GetCycles() - (cpu.GetFrameCycles() - (341 * 241) * cycles.one) >= (341 * 240) * cycles.one)
{
io.latch = oam.ram[regs.oam];
UpdateDecay(0xFF);
}
else
{
Update( cycles.one );
io.latch = oam.latch;
UpdateDecay(0xFF);
}
return io.latch;
}
NES_POKE_D(Ppu,2005)
{
Update( cycles.one );
NST_VERIFY( cpu.GetCycles() >= cycles.reset || !data );
if (cpu.GetCycles() >= cycles.reset)
{
io.latch = data;
UpdateDecay(0xFF);
if (scroll.toggle ^= 1)
{
scroll.latch = (scroll.latch & 0x7FE0) | (data >> 3);
scroll.xFine = data & 0x7;
}
else
{
scroll.latch = (scroll.latch & 0x0C1F) | ((data << 2 | data << 12) & 0x73E0);
}
}
}
NES_POKE_D(Ppu,2006)
{
Update( cycles.one );
NST_VERIFY( cpu.GetCycles() >= cycles.reset || !data );
if (cpu.GetCycles() >= cycles.reset)
{
io.latch = data;
UpdateDecay(0xFF);
if (scroll.toggle ^= 1)
{
scroll.latch = (scroll.latch & 0x00FF) | (data & 0x3F) << 8;
}
else
{
scroll.latch = (scroll.latch & 0x7F00) | data;
scroll.address = scroll.latch;
UpdateScrollAddressLine();
}
}
}
NES_POKE_D(Ppu,2007)
{
Update( cycles.one * 4 );
uint address = scroll.address;
UpdateVramAddress();
if (!(regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED) || (scanline == SCANLINE_VBLANK))
UpdateAddressLine(scroll.address & 0x3fff);
else
return;
io.latch = data;
UpdateDecay(0xFF);
if ((address & 0x3F00) == 0x3F00)
{
address &= 0x1F;
const uint final = ((!rgbMap ? data : rgbMap[data & Palette::COLOR]) & Coloring()) | Emphasis();
palette.ram[address] = data;
output.palette[address] = final;
if (!(address & 0x3))
{
palette.ram[address ^ 0x10] = data;
output.palette[address ^ 0x10] = final;
}
output.bgColor = palette.ram[0] & uint(Palette::COLOR);
}
else
{
address &= 0x3FFF;
if (address >= 0x2000)
nmt.Poke( address & 0xFFF, data );
else
chr.Poke( address, data );
}
}
NES_PEEK_A(Ppu,2007)
{
byte mask = 0xFF;
byte cache = io.latch;
Update( cycles.one, address );
Cycle curCyc = cpu.GetCycles();
Cycle delta = curCyc - decay.rd2007;
decay.rd2007 = curCyc;
bool fastread = (delta <= 12);
address = scroll.address & 0x3FFF;
UpdateVramAddress();
if (!(regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED) || (scanline == SCANLINE_VBLANK))
UpdateAddressLine(scroll.address & 0x3fff);
if ((address & 0x3F00) == 0x3F00) // Palette
{
io.latch = (io.latch & 0xC0) | palette.ram[address & 0x1F] & Coloring();
mask = 0x3F;
}
else // Non-Palette
{
io.latch = io.buffer;
}
UpdateDecay(mask);
io.buffer = (address >= 0x2000 ? nmt.FetchName( address ) : chr.FetchPattern( address ));
if (fastread)
io.latch = cache;
return io.latch;
}
NES_POKE_D(Ppu,2xxx)
{
io.latch = data;
UpdateDecay(0xFF);
}
NES_PEEK(Ppu,2xxx)
{
if ((cpu.GetCycles() - decay.timestamp[0]) > 24576)
return 0;
return io.latch;
}
NES_PEEK(Ppu,3000)
{
Update( cycles.one );
return io.latch;
}
NES_POKE_D(Ppu,4014)
{
if (cpu.IsOddCycle())
cpu.StealCycles( cpu.GetClock() );
Update( cycles.one );
cpu.StealCycles( cpu.GetClock() );
NST_ASSERT( regs.oam < 0x100 );
data <<= 8;
if ((regs.oam == 0x00 && data < 0x2000) && (!(regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED) || cpu.GetCycles() <= GetHVIntClock() - cpu.GetClock() * 512))
{
cpu.StealCycles( cpu.GetClock() * 512 );
const byte* const NST_RESTRICT cpuRam = cpu.GetRam() + (data & (Cpu::RAM_SIZE-1));
byte* const NST_RESTRICT oamRam = oam.ram;
for (uint i=0x00; i < 0x100; i += 0x4)
{
oamRam[i+0x0] = cpuRam[i+0x0];
oamRam[i+0x1] = cpuRam[i+0x1];
oamRam[i+0x2] = cpuRam[i+0x2] & 0xE3U;
oamRam[i+0x3] = cpuRam[i+0x3];
}
io.latch = oamRam[0xFF];
UpdateDecay(0xFF);
}
else do
{
io.latch = cpu.Peek( data++ );
UpdateDecay(0xFF);
cpu.StealCycles( cpu.GetClock() );
Update( cycles.one );
cpu.StealCycles( cpu.GetClock() );
NST_VERIFY( IsDead() );
if (IsDead())
{
if ((regs.oam & 0x03) == 0x02)
io.latch &= 0xE3;
}
else
{
io.latch = 0xFF;
}
byte* const NST_RESTRICT out = oam.ram + regs.oam;
regs.oam = (regs.oam + 1) & 0xFF;
*out = io.latch;
}
while (data & 0xFF);
}
NES_PEEK(Ppu,4014)
{
return 0x40;
}
NST_FORCE_INLINE void Ppu::Scroll::ClockX()
{
if ((address & X_TILE) != X_TILE)
address++;
else
address ^= (X_TILE|NAME_LOW);
}
NST_SINGLE_CALL void Ppu::Scroll::ResetX()
{
address = (address & ((X_TILE|NAME_LOW) ^ 0x7FFFU)) | (latch & (X_TILE|NAME_LOW));
}
NST_SINGLE_CALL void Ppu::Scroll::ClockY()
{
if ((address & Y_FINE) != (7U << 12))
{
address += (1U << 12);
}
else switch (address & Y_TILE)
{
default: address = (address & (Y_FINE ^ 0x7FFFU)) + (1U << 5); break;
case (29U << 5): address ^= NAME_HIGH;
case (31U << 5): address &= (Y_FINE|Y_TILE) ^ 0x7FFFU; break;
}
}
NST_SINGLE_CALL void Ppu::PreLoadTiles()
{
const byte* const NST_RESTRICT src[] =
{
tileLut.block[tiles.pattern[0] | (tiles.attribute & 0x3U) << 8],
tileLut.block[tiles.pattern[1] | (tiles.attribute & 0x3U) << 8]
};
NST_ASSERT( tiles.index == 8 );
byte* const NST_RESTRICT dst = tiles.pixels;
dst[0] = src[0][0];
dst[1] = src[1][0];
dst[2] = src[0][1];
dst[3] = src[1][1];
dst[4] = src[0][2];
dst[5] = src[1][2];
dst[6] = src[0][3];
dst[7] = src[1][3];
}
NST_SINGLE_CALL void Ppu::LoadTiles()
{
const byte* const NST_RESTRICT src[] =
{
tileLut.block[tiles.pattern[0] | (tiles.attribute & 0x3U) << 8],
tileLut.block[tiles.pattern[1] | (tiles.attribute & 0x3U) << 8]
};
NST_ASSERT( tiles.index == 0 || tiles.index == 8 );
byte* const NST_RESTRICT dst = tiles.pixels + tiles.index;
tiles.index ^= 8U;
dst[0] = src[0][0];
dst[1] = src[1][0];
dst[2] = src[0][1];
dst[3] = src[1][1];
dst[4] = src[0][2];
dst[5] = src[1][2];
dst[6] = src[0][3];
dst[7] = src[1][3];
}
NST_FORCE_INLINE void Ppu::EvaluateSpritesEven()
{
if (cycles.hClock >= 64)
oam.latch = oam.ram[oam.address];
}
NST_FORCE_INLINE void Ppu::EvaluateSpritesOdd()
{
(*this.*oam.phase)();
}
void Ppu::EvaluateSpritesPhase0()
{
}
void Ppu::EvaluateSpritesPhase1()
{
oam.index++;
if (scanline - oam.latch >= oam.height)
{
if (oam.index != 64)
{
oam.address = (oam.index != 2 ? oam.address + 4 : 8);
}
else
{
oam.address = 0;
oam.phase = &Ppu::EvaluateSpritesPhase9;
}
}
else
{
oam.address++;
oam.phase = &Ppu::EvaluateSpritesPhase2;
oam.buffered[0] = oam.latch;
}
}
void Ppu::EvaluateSpritesPhase2()
{
oam.address++;
oam.phase = &Ppu::EvaluateSpritesPhase3;
oam.buffered[1] = oam.latch;
}
void Ppu::EvaluateSpritesPhase3()
{
oam.address++;
oam.phase = &Ppu::EvaluateSpritesPhase4;
oam.buffered[2] = oam.latch;
}
void Ppu::EvaluateSpritesPhase4()
{
oam.buffered[3] = oam.latch;
oam.buffered += 4;
if (oam.index != 64)
{
oam.phase = (oam.buffered != oam.limit ? &Ppu::EvaluateSpritesPhase1 : &Ppu::EvaluateSpritesPhase5);
if (oam.index != 2)
{
oam.address++;
if (oam.index == 1)
oam.spriteZeroInLine = true;
}
else
{
oam.address = 8;
}
}
else
{
oam.address = 0;
oam.phase = &Ppu::EvaluateSpritesPhase9;
}
}
void Ppu::EvaluateSpritesPhase5()
{
if (scanline - oam.latch >= oam.height)
{
oam.address = ((oam.address + 4) & 0xFC) + ((oam.address + 1) & 0x03);
if (oam.address <= 5)
{
oam.phase = &Ppu::EvaluateSpritesPhase9;
oam.address &= 0xFC;
}
}
else
{
oam.phase = &Ppu::EvaluateSpritesPhase6;
oam.address = (oam.address + 1) & 0xFF;
regs.status |= Regs::STATUS_SP_OVERFLOW;
}
}
void Ppu::EvaluateSpritesPhase6()
{
oam.phase = &Ppu::EvaluateSpritesPhase7;
oam.address = (oam.address + 1) & 0xFF;
}
void Ppu::EvaluateSpritesPhase7()
{
oam.phase = &Ppu::EvaluateSpritesPhase8;
oam.address = (oam.address + 1) & 0xFF;
}
void Ppu::EvaluateSpritesPhase8()
{
oam.phase = &Ppu::EvaluateSpritesPhase9;
oam.address = (oam.address + 1) & 0xFF;
if ((oam.address & 0x3) == 0x3)
oam.address++;
oam.address &= 0xFC;
}
void Ppu::EvaluateSpritesPhase9()
{
oam.address = (oam.address + 4) & 0xFF;
}
NST_FORCE_INLINE uint Ppu::OpenSprite() const
{
return (regs.ctrl[0] & (Regs::CTRL0_SP_OFFSET|Regs::CTRL0_SP8X16)) ? 0x1FF0 : 0x0FF0;
}
NST_FORCE_INLINE uint Ppu::OpenSprite(const byte* const NST_RESTRICT buffer) const
{
uint address;
const uint comparitor = (uint(scanline) - buffer[0]) ^ ((buffer[2] & uint(Oam::Y_FLIP)) ? 0xF : 0x0);
if (regs.ctrl[0] & Regs::CTRL0_SP8X16)
{
address =
(
((buffer[1] & uint(Oam::TILE_LSB)) << 12) |
((buffer[1] & (Oam::TILE_LSB ^ 0xFFU)) << 4) |
((comparitor & Oam::RANGE_MSB) << 1)
);
}
else
{
address = (regs.ctrl[0] & Regs::CTRL0_SP_OFFSET) << 9 | buffer[1] << 4;
}
return address | (comparitor & Oam::XFINE);
}
NST_FORCE_INLINE void Ppu::LoadSprite(const uint pattern0,const uint pattern1,const byte* const NST_RESTRICT buffer)
{
if (pattern0 | pattern1)
{
uint a = (buffer[2] & uint(Oam::X_FLIP)) ? 7 : 0;
uint p =
(
(pattern0 >> 1 & 0x0055) | (pattern1 << 0 & 0x00AA) |
(pattern0 << 8 & 0x5500) | (pattern1 << 9 & 0xAA00)
);
Oam::Output* const NST_RESTRICT entry = oam.visible++;
entry->pixels[( a^=6 )] = ( p ) & 0x3;
entry->pixels[( a^=2 )] = ( p >>= 2 ) & 0x3;
entry->pixels[( a^=6 )] = ( p >>= 2 ) & 0x3;
entry->pixels[( a^=2 )] = ( p >>= 2 ) & 0x3;
entry->pixels[( a^=7 )] = ( p >>= 2 ) & 0x3;
entry->pixels[( a^=2 )] = ( p >>= 2 ) & 0x3;
entry->pixels[( a^=6 )] = ( p >>= 2 ) & 0x3;
entry->pixels[( a^=2 )] = ( p >>= 2 );
const uint attribute = buffer[2];
entry->x = buffer[3];
entry->palette = Palette::SPRITE_OFFSET + ((attribute & Oam::COLOR) << 2);
entry->behind = (attribute & Oam::BEHIND) ? 0x3 : 0x0;
entry->zero = (buffer == oam.buffer && oam.spriteZeroInLine) ? 0x3 : 0x0;
}
}
void Ppu::LoadExtendedSprites()
{
const byte* NST_RESTRICT buffer = oam.buffer + (8*4);
NST_ASSERT( buffer < oam.buffered );
do
{
const uint address = OpenSprite( buffer );
const uint patterns[2] =
{
chr.FetchPattern( address | 0x0 ),
chr.FetchPattern( address | 0x8 )
};
LoadSprite( patterns[0], patterns[1], buffer );
buffer += 4;
}
while (buffer != oam.buffered);
}
NST_FORCE_INLINE void Ppu::RenderPixel()
{
uint clock;
uint pixel = tiles.pixels[((clock=cycles.hClock++) + scroll.xFine) & 15] & tiles.mask;
for (const Oam::Output* NST_RESTRICT sprite=oam.output, *const end=oam.visible; sprite != end; ++sprite)
{
uint x = clock - sprite->x;
if (x > 7)
continue;
x = sprite->pixels[x] & oam.mask;
if (x)
{
if (pixel & sprite->zero)
regs.status |= Regs::STATUS_SP_ZERO_HIT;
if (!(pixel & sprite->behind))
pixel = sprite->palette + x;
break;
}
}
Video::Screen::Pixel* const NST_RESTRICT target = output.target++;
*target = output.palette[pixel];
}
NST_SINGLE_CALL void Ppu::RenderPixel255()
{
cycles.hClock = 256;
uint pixel = tiles.pixels[(255 + scroll.xFine) & 15] & tiles.mask;
for (const Oam::Output* NST_RESTRICT sprite=oam.output, *const end=oam.visible; sprite != end; ++sprite)
{
uint x = 255U - sprite->x;
if (x > 7)
continue;
x = sprite->pixels[x] & oam.mask;
if (x)
{
if (!(pixel & sprite->behind))
pixel = sprite->palette + x;
break;
}
}
Video::Screen::Pixel* const NST_RESTRICT target = output.target++;
*target = output.palette[pixel];
}
NST_NO_INLINE void Ppu::Run()
{
NST_VERIFY( cycles.count != cycles.hClock );
if (scanline_sleep) // Extra lines between VBLANK and NMI in Dendy mode
{
switch (cycles.hClock)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
case 20:
case 21:
case 22:
case 23:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
case 38:
case 39:
case 40:
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
case 47:
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
case 58:
case 59:
case 60:
case 61:
case 62:
case 63:
case 64:
case 65:
case 66:
case 67:
case 68:
case 69:
case 70:
case 71:
case 72:
case 73:
case 74:
case 75:
case 76:
case 77:
case 78:
case 79:
case 80:
case 81:
case 82:
case 83:
case 84:
case 85:
case 86:
case 87:
case 88:
case 89:
case 90:
case 91:
case 92:
case 93:
case 94:
case 95:
case 96:
case 97:
case 98:
case 99:
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
case 108:
case 109:
case 110:
case 111:
case 112:
case 113:
case 114:
case 115:
case 116:
case 117:
case 118:
case 119:
case 120:
case 121:
case 122:
case 123:
case 124:
case 125:
case 126:
case 127:
case 128:
case 129:
case 130:
case 131:
case 132:
case 133:
case 134:
case 135:
case 136:
case 137:
case 138:
case 139:
case 140:
case 141:
case 142:
case 143:
case 144:
case 145:
case 146:
case 147:
case 148:
case 149:
case 150:
case 151:
case 152:
case 153:
case 154:
case 155:
case 156:
case 157:
case 158:
case 159:
case 160:
case 161:
case 162:
case 163:
case 164:
case 165:
case 166:
case 167:
case 168:
case 169:
case 170:
case 171:
case 172:
case 173:
case 174:
case 175:
case 176:
case 177:
case 178:
case 179:
case 180:
case 181:
case 182:
case 183:
case 184:
case 185:
case 186:
case 187:
case 188:
case 189:
case 190:
case 191:
case 192:
case 193:
case 194:
case 195:
case 196:
case 197:
case 198:
case 199:
case 200:
case 201:
case 202:
case 203:
case 204:
case 205:
case 206:
case 207:
case 208:
case 209:
case 210:
case 211:
case 212:
case 213:
case 214:
case 215:
case 216:
case 217:
case 218:
case 219:
case 220:
case 221:
case 222:
case 223:
case 224:
case 225:
case 226:
case 227:
case 228:
case 229:
case 230:
case 231:
case 232:
case 233:
case 234:
case 235:
case 236:
case 237:
case 238:
case 239:
case 240:
case 241:
case 242:
case 243:
case 244:
case 245:
case 246:
case 247:
case 248:
case 249:
case 250:
case 251:
case 252:
case 253:
case 254:
case 255:
case 256:
case 257:
case 258:
case 260:
case 261:
case 262:
case 263:
case 264:
case 266:
case 267:
case 268:
case 269:
case 270:
case 271:
case 272:
case 273:
case 274:
case 275:
case 276:
case 277:
case 278:
case 279:
case 280:
case 281:
case 282:
case 283:
case 284:
case 285:
case 286:
case 287:
case 288:
case 289:
case 290:
case 291:
case 292:
case 293:
case 294:
case 295:
case 296:
case 297:
case 298:
case 299:
case 300:
case 301:
case 302:
case 303:
case 304:
case 305:
case 306:
case 307:
case 308:
case 309:
case 310:
case 311:
case 312:
case 313:
case 314:
case 315:
case 316:
case 317:
case 318:
case 319:
case 320:
case 321:
case 322:
case 323:
case 324:
case 325:
case 326:
case 327:
case 328:
case 329:
case 330:
case 331:
case 332:
case 333:
case 334:
case 335:
case 336:
case 337:
HActiveSleep:
cycles.hClock = 338;
if (cycles.count <= 338)
break;
case 338:
if (++scanline_sleep < PPU_DENDY_VSLEEP)
{
cycles.hClock = 0;
cycles.vClock += HCLOCK_DUMMY;
if (cycles.count <= HCLOCK_DUMMY)
break;
cycles.count -= HCLOCK_DUMMY;
goto HActiveSleep;
}
else
{
cycles.hClock = HCLOCK_VBLANK_0;
if (cycles.count <= HCLOCK_VBLANK_0)
break;
}
case HCLOCK_VBLANK_0:
goto VBlank0;
case HCLOCK_VBLANK_1:
goto VBlank1;
case HCLOCK_VBLANK_2:
goto VBlank2;
}
}
else if (regs.ctrl[1] & Regs::CTRL1_BG_SP_ENABLED)
{
switch (cycles.hClock)
{
case 0:
case 8:
case 16:
case 24:
case 32:
case 40:
case 48:
case 56:
case 72:
case 80:
case 88:
case 96:
case 104:
case 112:
case 120:
case 128:
case 136:
case 144:
case 152:
case 160:
case 168:
case 176:
case 184:
case 192:
case 200:
case 208:
case 216:
case 224:
case 232:
case 240:
case 248:
HActive:
LoadTiles();
EvaluateSpritesEven();
OpenName();
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
case 1:
case 9:
case 17:
case 25:
case 33:
case 41:
case 49:
case 57:
case 65:
case 73:
case 81:
case 89:
case 97:
case 105:
case 113:
case 121:
case 129:
case 137:
case 145:
case 153:
case 161:
case 169:
case 177:
case 185:
case 193:
case 201:
case 209:
case 217:
case 225:
case 233:
case 241:
case 249:
FetchName();
EvaluateSpritesOdd();
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
case 2:
case 10:
case 18:
case 26:
case 34:
case 42:
case 50:
case 58:
case 66:
case 74:
case 82:
case 90:
case 98:
case 106:
case 114:
case 122:
case 130:
case 138:
case 146:
case 154:
case 162:
case 170:
case 178:
case 186:
case 194:
case 202:
case 210:
case 218:
case 226:
case 234:
case 242:
case 250:
EvaluateSpritesEven();
OpenAttribute();
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
case 3:
case 11:
case 19:
case 27:
case 35:
case 43:
case 51:
case 59:
case 67:
case 75:
case 83:
case 91:
case 99:
case 107:
case 115:
case 123:
case 131:
case 139:
case 147:
case 155:
case 163:
case 171:
case 179:
case 187:
case 195:
case 203:
case 211:
case 219:
case 227:
case 235:
case 243:
case 251:
FetchAttribute();
EvaluateSpritesOdd();
if (cycles.hClock == 251)
scroll.ClockY();
scroll.ClockX();
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
case 4:
case 12:
case 20:
case 28:
case 36:
case 44:
case 52:
case 60:
case 68:
case 76:
case 84:
case 92:
case 100:
case 108:
case 116:
case 124:
case 132:
case 140:
case 148:
case 156:
case 164:
case 172:
case 180:
case 188:
case 196:
case 204:
case 212:
case 220:
case 228:
case 236:
case 244:
case 252:
EvaluateSpritesEven();
OpenPattern( io.pattern | 0x0 );
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
case 5:
case 13:
case 21:
case 29:
case 37:
case 45:
case 53:
case 61:
case 69:
case 77:
case 85:
case 93:
case 101:
case 109:
case 117:
case 125:
case 133:
case 141:
case 149:
case 157:
case 165:
case 173:
case 181:
case 189:
case 197:
case 205:
case 213:
case 221:
case 229:
case 237:
case 245:
case 253:
FetchBgPattern0();
EvaluateSpritesOdd();
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
case 6:
case 14:
case 22:
case 30:
case 38:
case 46:
case 54:
case 62:
case 70:
case 78:
case 86:
case 94:
case 102:
case 110:
case 118:
case 126:
case 134:
case 142:
case 150:
case 158:
case 166:
case 174:
case 182:
case 190:
case 198:
case 206:
case 214:
case 222:
case 230:
case 238:
case 246:
case 254:
EvaluateSpritesEven();
OpenPattern( io.pattern | 0x8 );
RenderPixel();
if (cycles.count <= cycles.hClock)
break;
if (cycles.hClock == 255)
goto HActive255;
case 7:
case 15:
case 23:
case 31:
case 39:
case 47:
case 55:
case 63:
case 71:
case 79:
case 87:
case 95:
case 103:
case 111:
case 119:
case 127:
case 135:
case 143:
case 151:
case 159:
case 167:
case 175:
case 183:
case 191:
case 199:
case 207:
case 215:
case 223:
case 231:
case 239:
case 247:
FetchBgPattern1();
EvaluateSpritesOdd();
RenderPixel();
tiles.mask = tiles.show[0];
oam.mask = oam.show[0];
if (cycles.count <= cycles.hClock)
break;
if (cycles.hClock != 64)
goto HActive;
case 64:
NST_VERIFY( regs.oam == 0 );
oam.address = regs.oam & Oam::OFFSET_TO_0_1;
oam.phase = &Ppu::EvaluateSpritesPhase1;
oam.latch = 0xFF;
goto HActive;
case 255:
HActive255:
FetchBgPattern1();
EvaluateSpritesOdd();
RenderPixel255();
if (cycles.count <= 256)
break;
case 256:
OpenName();
oam.latch = 0xFF;
cycles.hClock = 257;
if (cycles.count <= 257)
break;
case 257:
if (hBlankHook)
hBlankHook.Execute();
scroll.ResetX();
oam.visible = oam.output;
cycles.hClock = 258;
if (cycles.count <= 258)
break;
case 258:
case 266:
case 274:
case 282:
case 290:
case 298:
case 306:
case 314:
HBlankSp:
OpenName();
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case 260:
case 268:
case 276:
case 284:
case 292:
case 300:
case 308:
case 316:
{
const byte* const buffer = oam.buffer + ((cycles.hClock - 260) / 2);
OpenPattern( buffer >= oam.buffered ? OpenSprite() : OpenSprite(buffer) );
if (scanline == 238 && cycles.hClock == 316)
regs.oam = 0;
if (cycles.count <= ++cycles.hClock)
break;
}
case 261:
case 269:
case 277:
case 285:
case 293:
case 301:
case 309:
case 317:
if (oam.buffer + ((cycles.hClock - 261) / 2) < oam.buffered)
io.pattern = FetchSpPattern();
if (cycles.count <= ++cycles.hClock)
break;
case 262:
case 270:
case 278:
case 286:
case 294:
case 302:
case 310:
case 318:
OpenPattern( io.address | 0x8 );
if (cycles.count <= ++cycles.hClock)
break;
case 263:
case 271:
case 279:
case 287:
case 295:
case 303:
case 311:
case 319:
{
const byte* const buffer = oam.buffer + ((cycles.hClock - 263) / 2);
if (buffer < oam.buffered)
LoadSprite( io.pattern, FetchSpPattern(), buffer );
if (cycles.count <= ++cycles.hClock)
break;
if (cycles.hClock == 320)
goto HBlankBg;
}
case 264:
case 272:
case 280:
case 288:
case 296:
case 304:
case 312:
OpenName();
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
goto HBlankSp;
case 320:
HBlankBg:
if (oam.buffer + (8*4) < oam.buffered)
LoadExtendedSprites();
OpenName();
if (hActiveHook)
hActiveHook.Execute();
oam.latch = oam.ram[0];
oam.buffered = oam.buffer;
oam.spriteZeroInLine = false;
oam.index = 0;
oam.phase = &Ppu::EvaluateSpritesPhase0;
cycles.hClock = 321;
if (cycles.count <= 321)
break;
case 321:
FetchName();
cycles.hClock = 322;
if (cycles.count <= 322)
break;
case 322:
OpenAttribute();
cycles.hClock = 323;
if (cycles.count <= 323)
break;
case 323:
FetchAttribute();
scroll.ClockX();
cycles.hClock = 324;
if (cycles.count <= 324)
break;
case 324:
OpenPattern( io.pattern | 0x0 );
cycles.hClock = 325;
if (cycles.count <= 325)
break;
case 325:
FetchBgPattern0();
cycles.hClock = 326;
if (cycles.count <= 326)
break;
case 326:
OpenPattern( io.pattern | 0x8 );
cycles.hClock = 327;
if (cycles.count <= 327)
break;
case 327:
FetchBgPattern1();
cycles.hClock = 328;
if (cycles.count <= 328)
break;
case 328:
PreLoadTiles();
OpenName();
cycles.hClock = 329;
if (cycles.count <= 329)
break;
case 329:
FetchName();
cycles.hClock = 330;
if (cycles.count <= 330)
break;
case 330:
OpenAttribute();
cycles.hClock = 331;
if (cycles.count <= 331)
break;
case 331:
FetchAttribute();
scroll.ClockX();
cycles.hClock = 332;
if (cycles.count <= 332)
break;
case 332:
OpenPattern( io.pattern | 0x0 );
cycles.hClock = 333;
if (cycles.count <= 333)
break;
case 333:
FetchBgPattern0();
cycles.hClock = 334;
if (cycles.count <= 334)
break;
case 334:
OpenPattern( io.pattern | 0x8 );
cycles.hClock = 335;
if (cycles.count <= 335)
break;
case 335:
FetchBgPattern1();
cycles.hClock = 336;
if (cycles.count <= 336)
break;
case 336:
OpenName();
cycles.hClock = 337;
if (cycles.count <= 337)
break;
case 337:
tiles.mask = tiles.show[1];
oam.mask = oam.show[1];
if (scanline == SCANLINE_HDUMMY && model == PPU_RP2C02)
{
if (regs.frame)
{
output.burstPhase = (output.burstPhase + 2) % 3;
cpu.SetFrameCycles( PPU_RP2C02_HVSYNC_1 );
}
else
{
output.burstPhase = (output.burstPhase + 1) % 3;
}
}
cycles.hClock = 338;
if (cycles.count <= 338)
break;
case 338:
OpenName();
if (scanline++ != 239)
{
const uint line = (scanline != 0 || model != PPU_RP2C02 || !regs.frame ? HCLOCK_DUMMY : (HCLOCK_DUMMY - 1));
cycles.hClock = 0;
cycles.vClock += line;
if (cycles.count <= line)
break;
cycles.count -= line;
goto HActive;
}
else
{
if (model == PPU_DENDY)
{
scanline_sleep = 1;
cycles.hClock = 0;
cycles.vClock += HCLOCK_DUMMY;
if (cycles.count <= HCLOCK_DUMMY)
break;
cycles.count -= HCLOCK_DUMMY;
goto HActiveSleep;
}
else
{
cycles.hClock = HCLOCK_VBLANK_0;
if (cycles.count <= HCLOCK_VBLANK_0)
break;
}
}
case HCLOCK_VBLANK_0:
VBlank0:
regs.status |= Regs::STATUS_VBLANKING;
cycles.hClock = HCLOCK_VBLANK_1;
if (cycles.count <= HCLOCK_VBLANK_1)
break;
case HCLOCK_VBLANK_1:
VBlank1:
regs.status = (regs.status & 0xFF) | (regs.status >> 1 & Regs::STATUS_VBLANK);
oam.visible = oam.output;
cycles.hClock = HCLOCK_VBLANK_2;
if (cycles.count <= HCLOCK_VBLANK_2)
break;
case HCLOCK_VBLANK_2:
VBlank2:
scanline_sleep = 0;
cycles.hClock = HCLOCK_DUMMY;
cycles.count = Cpu::CYCLE_MAX;
cycles.reset = 0;
if (regs.ctrl[0] & regs.status & Regs::CTRL0_NMI)
cpu.DoNMI( cpu.GetFrameCycles() );
return;
case HCLOCK_BOOT:
goto Boot;
case HCLOCK_DUMMY+0:
regs.status = 0;
scanline = SCANLINE_HDUMMY;
case HCLOCK_DUMMY+8:
case HCLOCK_DUMMY+16:
case HCLOCK_DUMMY+24:
case HCLOCK_DUMMY+32:
case HCLOCK_DUMMY+40:
case HCLOCK_DUMMY+48:
case HCLOCK_DUMMY+56:
case HCLOCK_DUMMY+64:
case HCLOCK_DUMMY+72:
case HCLOCK_DUMMY+80:
case HCLOCK_DUMMY+88:
case HCLOCK_DUMMY+96:
case HCLOCK_DUMMY+104:
case HCLOCK_DUMMY+112:
case HCLOCK_DUMMY+120:
case HCLOCK_DUMMY+128:
case HCLOCK_DUMMY+136:
case HCLOCK_DUMMY+144:
case HCLOCK_DUMMY+152:
case HCLOCK_DUMMY+160:
case HCLOCK_DUMMY+168:
case HCLOCK_DUMMY+176:
case HCLOCK_DUMMY+184:
case HCLOCK_DUMMY+192:
case HCLOCK_DUMMY+200:
case HCLOCK_DUMMY+208:
case HCLOCK_DUMMY+216:
case HCLOCK_DUMMY+224:
case HCLOCK_DUMMY+232:
case HCLOCK_DUMMY+240:
case HCLOCK_DUMMY+248:
HDummyBg:
OpenName();
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case HCLOCK_DUMMY+2:
case HCLOCK_DUMMY+10:
case HCLOCK_DUMMY+18:
case HCLOCK_DUMMY+26:
case HCLOCK_DUMMY+34:
case HCLOCK_DUMMY+42:
case HCLOCK_DUMMY+50:
case HCLOCK_DUMMY+58:
case HCLOCK_DUMMY+66:
case HCLOCK_DUMMY+74:
case HCLOCK_DUMMY+82:
case HCLOCK_DUMMY+90:
case HCLOCK_DUMMY+98:
case HCLOCK_DUMMY+106:
case HCLOCK_DUMMY+114:
case HCLOCK_DUMMY+122:
case HCLOCK_DUMMY+130:
case HCLOCK_DUMMY+138:
case HCLOCK_DUMMY+146:
case HCLOCK_DUMMY+154:
case HCLOCK_DUMMY+162:
case HCLOCK_DUMMY+170:
case HCLOCK_DUMMY+178:
case HCLOCK_DUMMY+186:
case HCLOCK_DUMMY+194:
case HCLOCK_DUMMY+202:
case HCLOCK_DUMMY+210:
case HCLOCK_DUMMY+218:
case HCLOCK_DUMMY+226:
case HCLOCK_DUMMY+234:
case HCLOCK_DUMMY+242:
case HCLOCK_DUMMY+250:
OpenAttribute();
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case HCLOCK_DUMMY+4:
case HCLOCK_DUMMY+12:
case HCLOCK_DUMMY+20:
case HCLOCK_DUMMY+28:
case HCLOCK_DUMMY+36:
case HCLOCK_DUMMY+44:
case HCLOCK_DUMMY+52:
case HCLOCK_DUMMY+60:
case HCLOCK_DUMMY+68:
case HCLOCK_DUMMY+76:
case HCLOCK_DUMMY+84:
case HCLOCK_DUMMY+92:
case HCLOCK_DUMMY+100:
case HCLOCK_DUMMY+108:
case HCLOCK_DUMMY+116:
case HCLOCK_DUMMY+124:
case HCLOCK_DUMMY+132:
case HCLOCK_DUMMY+140:
case HCLOCK_DUMMY+148:
case HCLOCK_DUMMY+156:
case HCLOCK_DUMMY+164:
case HCLOCK_DUMMY+172:
case HCLOCK_DUMMY+180:
case HCLOCK_DUMMY+188:
case HCLOCK_DUMMY+196:
case HCLOCK_DUMMY+204:
case HCLOCK_DUMMY+212:
case HCLOCK_DUMMY+220:
case HCLOCK_DUMMY+228:
case HCLOCK_DUMMY+236:
case HCLOCK_DUMMY+244:
case HCLOCK_DUMMY+252:
OpenPattern( regs.ctrl[0] << 8 & 0x1000 );
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case HCLOCK_DUMMY+6:
case HCLOCK_DUMMY+14:
case HCLOCK_DUMMY+22:
case HCLOCK_DUMMY+30:
case HCLOCK_DUMMY+38:
case HCLOCK_DUMMY+46:
case HCLOCK_DUMMY+54:
case HCLOCK_DUMMY+62:
case HCLOCK_DUMMY+70:
case HCLOCK_DUMMY+78:
case HCLOCK_DUMMY+86:
case HCLOCK_DUMMY+94:
case HCLOCK_DUMMY+102:
case HCLOCK_DUMMY+110:
case HCLOCK_DUMMY+118:
case HCLOCK_DUMMY+126:
case HCLOCK_DUMMY+134:
case HCLOCK_DUMMY+142:
case HCLOCK_DUMMY+150:
case HCLOCK_DUMMY+158:
case HCLOCK_DUMMY+166:
case HCLOCK_DUMMY+174:
case HCLOCK_DUMMY+182:
case HCLOCK_DUMMY+190:
case HCLOCK_DUMMY+198:
case HCLOCK_DUMMY+206:
case HCLOCK_DUMMY+214:
case HCLOCK_DUMMY+222:
case HCLOCK_DUMMY+230:
case HCLOCK_DUMMY+238:
case HCLOCK_DUMMY+246:
case HCLOCK_DUMMY+254:
OpenPattern( io.address | 0x8 );
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
if (cycles.hClock != HCLOCK_DUMMY+256)
goto HDummyBg;
case HCLOCK_DUMMY+256:
case HCLOCK_DUMMY+264:
case HCLOCK_DUMMY+272:
case HCLOCK_DUMMY+280:
case HCLOCK_DUMMY+288:
case HCLOCK_DUMMY+296:
case HCLOCK_DUMMY+312:
HDummySp:
OpenName();
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case HCLOCK_DUMMY+258:
case HCLOCK_DUMMY+266:
case HCLOCK_DUMMY+274:
case HCLOCK_DUMMY+282:
case HCLOCK_DUMMY+290:
case HCLOCK_DUMMY+298:
case HCLOCK_DUMMY+306:
case HCLOCK_DUMMY+314:
OpenName();
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case HCLOCK_DUMMY+260:
case HCLOCK_DUMMY+268:
case HCLOCK_DUMMY+276:
case HCLOCK_DUMMY+284:
case HCLOCK_DUMMY+292:
case HCLOCK_DUMMY+300:
case HCLOCK_DUMMY+308:
case HCLOCK_DUMMY+316:
OpenPattern( OpenSprite() );
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
case HCLOCK_DUMMY+262:
case HCLOCK_DUMMY+270:
case HCLOCK_DUMMY+278:
case HCLOCK_DUMMY+286:
case HCLOCK_DUMMY+294:
case HCLOCK_DUMMY+302:
case HCLOCK_DUMMY+310:
case HCLOCK_DUMMY+318:
OpenPattern( io.address | 0x8 );
if (cycles.hClock != HCLOCK_DUMMY+318)
{
cycles.hClock += 2;
if (cycles.count <= cycles.hClock)
break;
if (cycles.hClock != HCLOCK_DUMMY+304)
goto HDummySp;
}
else
{
cycles.hClock = 320;
cycles.vClock += HCLOCK_DUMMY;
cycles.count -= HCLOCK_DUMMY;
if (cycles.count <= cycles.hClock)
break;
goto HBlankBg;
}
case HCLOCK_DUMMY+304:
scroll.address = scroll.latch;
goto HDummySp;
default:
NST_UNREACHABLE();
}
}
else
{
switch (cycles.hClock)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
case 20:
case 21:
case 22:
case 23:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
case 38:
case 39:
case 40:
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
case 47:
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
case 58:
case 59:
case 60:
case 61:
case 62:
case 63:
case 64:
case 65:
case 66:
case 67:
case 68:
case 69:
case 70:
case 71:
case 72:
case 73:
case 74:
case 75:
case 76:
case 77:
case 78:
case 79:
case 80:
case 81:
case 82:
case 83:
case 84:
case 85:
case 86:
case 87:
case 88:
case 89:
case 90:
case 91:
case 92:
case 93:
case 94:
case 95:
case 96:
case 97:
case 98:
case 99:
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
case 108:
case 109:
case 110:
case 111:
case 112:
case 113:
case 114:
case 115:
case 116:
case 117:
case 118:
case 119:
case 120:
case 121:
case 122:
case 123:
case 124:
case 125:
case 126:
case 127:
case 128:
case 129:
case 130:
case 131:
case 132:
case 133:
case 134:
case 135:
case 136:
case 137:
case 138:
case 139:
case 140:
case 141:
case 142:
case 143:
case 144:
case 145:
case 146:
case 147:
case 148:
case 149:
case 150:
case 151:
case 152:
case 153:
case 154:
case 155:
case 156:
case 157:
case 158:
case 159:
case 160:
case 161:
case 162:
case 163:
case 164:
case 165:
case 166:
case 167:
case 168:
case 169:
case 170:
case 171:
case 172:
case 173:
case 174:
case 175:
case 176:
case 177:
case 178:
case 179:
case 180:
case 181:
case 182:
case 183:
case 184:
case 185:
case 186:
case 187:
case 188:
case 189:
case 190:
case 191:
case 192:
case 193:
case 194:
case 195:
case 196:
case 197:
case 198:
case 199:
case 200:
case 201:
case 202:
case 203:
case 204:
case 205:
case 206:
case 207:
case 208:
case 209:
case 210:
case 211:
case 212:
case 213:
case 214:
case 215:
case 216:
case 217:
case 218:
case 219:
case 220:
case 221:
case 222:
case 223:
case 224:
case 225:
case 226:
case 227:
case 228:
case 229:
case 230:
case 231:
case 232:
case 233:
case 234:
case 235:
case 236:
case 237:
case 238:
case 239:
case 240:
case 241:
case 242:
case 243:
case 244:
case 245:
case 246:
case 247:
case 248:
case 249:
case 250:
case 251:
case 252:
case 253:
case 254:
case 255:
HActiveOff:
{
const uint pixel = output.palette[(scroll.address & 0x3F00) == 0x3F00 ? (scroll.address & 0x001F) : 0];
uint i = cycles.hClock;
const uint hClock = NST_MIN(cycles.count,256);
NST_ASSERT( i < hClock );
cycles.hClock = hClock;
tiles.index = (hClock - 1) & 8;
byte* const NST_RESTRICT tile = tiles.pixels;
Video::Screen::Pixel* NST_RESTRICT target = output.target;
do
{
tile[i++ & 15] = 0;
*target++ = pixel;
}
while (i != hClock);
output.target = target;
if (cycles.count <= 256)
break;
}
case 256:
cycles.hClock = 257;
if (cycles.count <= 257)
break;
case 257:
if (hBlankHook)
hBlankHook.Execute();
oam.visible = oam.output;
cycles.hClock = 258;
if (cycles.count <= 258)
break;
case 258:
case 260:
case 261:
case 262:
case 263:
case 264:
case 266:
case 268:
case 269:
case 270:
case 271:
case 272:
case 274:
case 276:
case 277:
case 278:
case 279:
case 280:
case 282:
case 284:
case 285:
case 286:
case 287:
case 288:
case 290:
case 292:
case 293:
case 294:
case 295:
case 296:
case 298:
case 300:
case 301:
case 302:
case 303:
case 304:
case 306:
case 308:
case 309:
case 310:
case 311:
case 312:
case 314:
case 316:
case 317:
case 318:
case 319:
if (cycles.count <= 320)
{
cycles.hClock = cycles.count + ((cycles.count & 0x7) == 3 || (cycles.count & 0x7) == 1);
break;
}
case 320:
HBlankOff:
cycles.hClock = 321;
if (hActiveHook)
hActiveHook.Execute();
oam.buffered = oam.buffer;
oam.spriteZeroInLine = false;
oam.index = 0;
oam.phase = &Ppu::EvaluateSpritesPhase0;
if (cycles.count <= 321)
break;
case 321:
case 322:
case 323:
case 324:
case 325:
case 326:
case 327:
case 328:
case 329:
case 330:
case 331:
case 332:
case 333:
case 334:
case 335:
case 336:
case 337:
cycles.hClock = cycles.count;
if (cycles.count <= 338)
break;
case 338:
if (scanline++ != 239)
{
tiles.mask = tiles.show[1];
oam.mask = oam.show[1];
if (scanline == 0 && model == PPU_RP2C02)
output.burstPhase = (output.burstPhase + 1) % 3;
cycles.vClock += HCLOCK_DUMMY;
cycles.hClock = 0;
if (cycles.count <= HCLOCK_DUMMY)
break;
cycles.count -= HCLOCK_DUMMY;
goto HActiveOff;
}
else
{
if (model == PPU_DENDY)
{
scanline_sleep = 1;
cycles.vClock += HCLOCK_DUMMY;
cycles.hClock = 0;
if (cycles.count <= HCLOCK_DUMMY)
break;
cycles.count -= HCLOCK_DUMMY;
goto HActiveSleep;
}
else
{
cycles.hClock = HCLOCK_VBLANK_0;
if (cycles.count <= HCLOCK_VBLANK_0)
break;
}
}
case HCLOCK_VBLANK_0:
goto VBlank0;
case HCLOCK_VBLANK_1:
goto VBlank1;
case HCLOCK_VBLANK_2:
goto VBlank2;
case HCLOCK_BOOT:
Boot:
regs.status |= Regs::STATUS_VBLANK;
cycles.hClock = HCLOCK_DUMMY;
cycles.count = Cpu::CYCLE_MAX;
if (cycles.reset)
{
switch (model)
{
case PPU_RP2C07: cycles.reset = PPU_RP2C07_HVREGBOOT - PPU_RP2C07_HVSYNCBOOT; break;
case PPU_DENDY: cycles.reset = PPU_DENDY_HVREGBOOT - PPU_DENDY_HVSYNCBOOT; break;
default: cycles.reset = PPU_RP2C02_HVREGBOOT - PPU_RP2C02_HVSYNCBOOT; break;
}
}
return;
case HCLOCK_DUMMY+0:
regs.status = 0;
scanline = SCANLINE_HDUMMY;
case HCLOCK_DUMMY+2:
case HCLOCK_DUMMY+4:
case HCLOCK_DUMMY+6:
case HCLOCK_DUMMY+8:
case HCLOCK_DUMMY+10:
case HCLOCK_DUMMY+12:
case HCLOCK_DUMMY+14:
case HCLOCK_DUMMY+16:
case HCLOCK_DUMMY+18:
case HCLOCK_DUMMY+20:
case HCLOCK_DUMMY+22:
case HCLOCK_DUMMY+24:
case HCLOCK_DUMMY+26:
case HCLOCK_DUMMY+28:
case HCLOCK_DUMMY+30:
case HCLOCK_DUMMY+32:
case HCLOCK_DUMMY+34:
case HCLOCK_DUMMY+36:
case HCLOCK_DUMMY+38:
case HCLOCK_DUMMY+40:
case HCLOCK_DUMMY+42:
case HCLOCK_DUMMY+44:
case HCLOCK_DUMMY+46:
case HCLOCK_DUMMY+48:
case HCLOCK_DUMMY+50:
case HCLOCK_DUMMY+52:
case HCLOCK_DUMMY+54:
case HCLOCK_DUMMY+56:
case HCLOCK_DUMMY+58:
case HCLOCK_DUMMY+60:
case HCLOCK_DUMMY+62:
case HCLOCK_DUMMY+64:
case HCLOCK_DUMMY+66:
case HCLOCK_DUMMY+68:
case HCLOCK_DUMMY+70:
case HCLOCK_DUMMY+72:
case HCLOCK_DUMMY+74:
case HCLOCK_DUMMY+76:
case HCLOCK_DUMMY+78:
case HCLOCK_DUMMY+80:
case HCLOCK_DUMMY+82:
case HCLOCK_DUMMY+84:
case HCLOCK_DUMMY+86:
case HCLOCK_DUMMY+88:
case HCLOCK_DUMMY+90:
case HCLOCK_DUMMY+92:
case HCLOCK_DUMMY+94:
case HCLOCK_DUMMY+96:
case HCLOCK_DUMMY+98:
case HCLOCK_DUMMY+100:
case HCLOCK_DUMMY+102:
case HCLOCK_DUMMY+104:
case HCLOCK_DUMMY+106:
case HCLOCK_DUMMY+108:
case HCLOCK_DUMMY+110:
case HCLOCK_DUMMY+112:
case HCLOCK_DUMMY+114:
case HCLOCK_DUMMY+116:
case HCLOCK_DUMMY+118:
case HCLOCK_DUMMY+120:
case HCLOCK_DUMMY+122:
case HCLOCK_DUMMY+124:
case HCLOCK_DUMMY+126:
case HCLOCK_DUMMY+128:
case HCLOCK_DUMMY+130:
case HCLOCK_DUMMY+132:
case HCLOCK_DUMMY+134:
case HCLOCK_DUMMY+136:
case HCLOCK_DUMMY+138:
case HCLOCK_DUMMY+140:
case HCLOCK_DUMMY+142:
case HCLOCK_DUMMY+144:
case HCLOCK_DUMMY+146:
case HCLOCK_DUMMY+148:
case HCLOCK_DUMMY+150:
case HCLOCK_DUMMY+152:
case HCLOCK_DUMMY+154:
case HCLOCK_DUMMY+156:
case HCLOCK_DUMMY+158:
case HCLOCK_DUMMY+160:
case HCLOCK_DUMMY+162:
case HCLOCK_DUMMY+164:
case HCLOCK_DUMMY+166:
case HCLOCK_DUMMY+168:
case HCLOCK_DUMMY+170:
case HCLOCK_DUMMY+172:
case HCLOCK_DUMMY+174:
case HCLOCK_DUMMY+176:
case HCLOCK_DUMMY+178:
case HCLOCK_DUMMY+180:
case HCLOCK_DUMMY+182:
case HCLOCK_DUMMY+184:
case HCLOCK_DUMMY+186:
case HCLOCK_DUMMY+188:
case HCLOCK_DUMMY+190:
case HCLOCK_DUMMY+192:
case HCLOCK_DUMMY+194:
case HCLOCK_DUMMY+196:
case HCLOCK_DUMMY+198:
case HCLOCK_DUMMY+200:
case HCLOCK_DUMMY+202:
case HCLOCK_DUMMY+204:
case HCLOCK_DUMMY+206:
case HCLOCK_DUMMY+208:
case HCLOCK_DUMMY+210:
case HCLOCK_DUMMY+212:
case HCLOCK_DUMMY+214:
case HCLOCK_DUMMY+216:
case HCLOCK_DUMMY+218:
case HCLOCK_DUMMY+220:
case HCLOCK_DUMMY+222:
case HCLOCK_DUMMY+224:
case HCLOCK_DUMMY+226:
case HCLOCK_DUMMY+228:
case HCLOCK_DUMMY+230:
case HCLOCK_DUMMY+232:
case HCLOCK_DUMMY+234:
case HCLOCK_DUMMY+236:
case HCLOCK_DUMMY+238:
case HCLOCK_DUMMY+240:
case HCLOCK_DUMMY+242:
case HCLOCK_DUMMY+244:
case HCLOCK_DUMMY+246:
case HCLOCK_DUMMY+248:
case HCLOCK_DUMMY+250:
case HCLOCK_DUMMY+252:
case HCLOCK_DUMMY+254:
case HCLOCK_DUMMY+256:
case HCLOCK_DUMMY+258:
case HCLOCK_DUMMY+260:
case HCLOCK_DUMMY+262:
case HCLOCK_DUMMY+264:
case HCLOCK_DUMMY+266:
case HCLOCK_DUMMY+268:
case HCLOCK_DUMMY+270:
case HCLOCK_DUMMY+272:
case HCLOCK_DUMMY+274:
case HCLOCK_DUMMY+276:
case HCLOCK_DUMMY+278:
case HCLOCK_DUMMY+280:
case HCLOCK_DUMMY+282:
case HCLOCK_DUMMY+284:
case HCLOCK_DUMMY+286:
case HCLOCK_DUMMY+288:
case HCLOCK_DUMMY+290:
case HCLOCK_DUMMY+292:
case HCLOCK_DUMMY+294:
case HCLOCK_DUMMY+296:
case HCLOCK_DUMMY+298:
case HCLOCK_DUMMY+300:
case HCLOCK_DUMMY+302:
case HCLOCK_DUMMY+304:
case HCLOCK_DUMMY+306:
case HCLOCK_DUMMY+308:
case HCLOCK_DUMMY+310:
case HCLOCK_DUMMY+312:
case HCLOCK_DUMMY+314:
case HCLOCK_DUMMY+316:
{
NST_COMPILE_ASSERT( HCLOCK_DUMMY & 1 );
cycles.hClock = cycles.count | 1;
if (cycles.count <= HCLOCK_DUMMY+318)
break;
}
case HCLOCK_DUMMY+318:
cycles.hClock = 320;
cycles.vClock += HCLOCK_DUMMY;
cycles.count -= HCLOCK_DUMMY;
if (cycles.count <= 320)
break;
goto HBlankOff;
default:
NST_UNREACHABLE();
}
}
cycles.count = GetCycles();
}
}
}