mirror of
https://github.com/OpenEmu/Nestopia-Core.git
synced 2025-11-01 11:09:19 +00:00
c5dacd8c13
This matches the changes on the core Nestopia repository that this project points at. Signed-off-by: Taylor Lineman <git@actuallytaylor.com>
3502 lines
73 KiB
C++
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();
|
|
}
|
|
}
|
|
}
|