PPU cycle-accurate rendering
This commit is contained in:
parent
f6e339105a
commit
ac1ce83de3
|
@ -18,7 +18,6 @@ target_sources(
|
|||
Source/NES.cpp
|
||||
Source/Platform.cpp
|
||||
Source/PPU/PPU.cpp
|
||||
Source/PPU/Registers.hpp
|
||||
Source/Main.cpp)
|
||||
|
||||
target_compile_options(
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
void CPU::Implicit()
|
||||
{
|
||||
pc += 1;
|
||||
|
||||
// Do nothing
|
||||
fetchedByte = acc;
|
||||
}
|
||||
|
||||
void CPU::Immediate()
|
||||
|
@ -15,7 +12,6 @@ void CPU::Immediate()
|
|||
pc += 2;
|
||||
|
||||
fetchedAddress = pc - 1;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::ZeroPage()
|
||||
|
@ -23,7 +19,6 @@ void CPU::ZeroPage()
|
|||
pc += 2;
|
||||
|
||||
fetchedAddress = nes->Read(BusSource::CPU, pc - 1);
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::ZeroPageX()
|
||||
|
@ -32,7 +27,6 @@ void CPU::ZeroPageX()
|
|||
|
||||
uint8_t operand = nes->Read(BusSource::CPU, pc - 1);
|
||||
fetchedAddress = (operand + x) & 0xFFu;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::ZeroPageY()
|
||||
|
@ -41,7 +35,6 @@ void CPU::ZeroPageY()
|
|||
|
||||
uint8_t operand = nes->Read(BusSource::CPU, pc - 1);
|
||||
fetchedAddress = (operand + y) & 0xFFu;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::Absolute()
|
||||
|
@ -52,7 +45,6 @@ void CPU::Absolute()
|
|||
uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1);
|
||||
|
||||
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::AbsoluteX()
|
||||
|
@ -64,7 +56,6 @@ void CPU::AbsoluteX()
|
|||
|
||||
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
||||
fetchedAddress = preAddress + x;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
|
||||
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
||||
}
|
||||
|
@ -78,7 +69,6 @@ void CPU::AbsoluteY()
|
|||
|
||||
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
||||
fetchedAddress = preAddress + y;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
|
||||
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
||||
}
|
||||
|
@ -103,7 +93,6 @@ void CPU::Indirect()
|
|||
uint8_t addressMsb = nes->Read(BusSource::CPU, addressIndirect);
|
||||
|
||||
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::IndirectX()
|
||||
|
@ -117,7 +106,6 @@ void CPU::IndirectX()
|
|||
uint16_t addressMsb = nes->Read(BusSource::CPU, (addressIndirect + 1) % 0x100);
|
||||
|
||||
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
void CPU::IndirectY()
|
||||
|
@ -132,7 +120,6 @@ void CPU::IndirectY()
|
|||
|
||||
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
||||
fetchedAddress = preAddress + y;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
|
||||
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
||||
}
|
||||
|
@ -146,7 +133,6 @@ void CPU::Relative()
|
|||
uint16_t oldPc = pc;
|
||||
|
||||
fetchedAddress = pc + operand;
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
|
||||
pageBoundaryCrossed = IsPageBoundaryCrossed(oldPc, fetchedAddress);
|
||||
}
|
||||
|
|
|
@ -348,7 +348,7 @@ uint8_t CPU::StackPop()
|
|||
return ReadMemory(address);
|
||||
}
|
||||
|
||||
void CPU::Cycle()
|
||||
uint64_t CPU::Cycle()
|
||||
{
|
||||
if (nes->nmi)
|
||||
{
|
||||
|
@ -357,9 +357,7 @@ void CPU::Cycle()
|
|||
|
||||
opcode = ReadMemory(pc);
|
||||
|
||||
#ifndef NDEBUG
|
||||
//Log();
|
||||
#endif
|
||||
|
||||
pageBoundaryCrossed = false;
|
||||
prevCycles = cycles;
|
||||
|
@ -369,6 +367,8 @@ void CPU::Cycle()
|
|||
((*this).*(instructions[opcode].execute))();
|
||||
|
||||
cycles += instructions[opcode].cycles;
|
||||
|
||||
return cycles - prevCycles;
|
||||
}
|
||||
|
||||
void CPU::NMI()
|
||||
|
@ -476,7 +476,7 @@ void CPU::Log()
|
|||
printf("%02X %02X %02X %s $%02X%02X,Y ", op, operand1, operand2, instruction.name, operand2, operand1);
|
||||
}
|
||||
|
||||
printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X, CYC:%lu\n", acc, x, y, status.GetByte(), sp, cycles);
|
||||
printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%lu\n", acc, x, y, status.GetByte(), sp, cycles);
|
||||
}
|
||||
|
||||
bool CPU::TestBits(uint8_t value, uint8_t bits)
|
||||
|
|
|
@ -63,7 +63,7 @@ class CPU
|
|||
{
|
||||
public:
|
||||
CPU();
|
||||
void Cycle();
|
||||
uint64_t Cycle();
|
||||
void Log();
|
||||
void Reset();
|
||||
void NMI();
|
||||
|
@ -230,7 +230,7 @@ public:
|
|||
uint8_t x{};
|
||||
uint8_t y{};
|
||||
Status status{};
|
||||
uint8_t prevCycles{};
|
||||
uint64_t prevCycles{};
|
||||
uint64_t cycles{};
|
||||
bool irq{};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// --------------- ARITHMETIC --------------- //
|
||||
void CPU::ADC()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
uint16_t tempSum = acc + fetchedByte;
|
||||
|
||||
tempSum += status.carry;
|
||||
|
@ -28,6 +29,7 @@ void CPU::ADC()
|
|||
|
||||
void CPU::SBC()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
uint16_t tempDiff = acc - fetchedByte;
|
||||
|
||||
tempDiff -= (1u - status.carry);
|
||||
|
@ -52,6 +54,7 @@ void CPU::SBC()
|
|||
|
||||
void CPU::CMP()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
status.carry = (acc >= fetchedByte) ? 1u : 0u;
|
||||
status.zero = (acc == fetchedByte) ? 1u : 0u;
|
||||
status.negative = (IsNegative(acc - fetchedByte)) ? 1u : 0u;
|
||||
|
@ -61,6 +64,7 @@ void CPU::CMP()
|
|||
|
||||
void CPU::CPX()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
status.carry = (x >= fetchedByte) ? 1u : 0u;
|
||||
status.zero = (x == fetchedByte) ? 1u : 0u;
|
||||
status.negative = (IsNegative(x - fetchedByte)) ? 1u : 0u;
|
||||
|
@ -68,6 +72,7 @@ void CPU::CPX()
|
|||
|
||||
void CPU::CPY()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
status.carry = (y >= fetchedByte) ? 1u : 0u;
|
||||
status.zero = (y == fetchedByte) ? 1u : 0u;
|
||||
status.negative = (IsNegative(y - fetchedByte)) ? 1u : 0u;
|
||||
|
@ -159,6 +164,7 @@ void CPU::BEQ()
|
|||
// --------------- INCREMENT/DECREMENT --------------- //
|
||||
void CPU::INC()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
++fetchedByte;
|
||||
|
||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
||||
|
@ -185,6 +191,7 @@ void CPU::INY()
|
|||
|
||||
void CPU::DEC()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
--fetchedByte;
|
||||
|
||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
||||
|
@ -241,6 +248,7 @@ void CPU::RTS()
|
|||
// --------------- LOAD/STORE --------------- //
|
||||
void CPU::LDA()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||
|
||||
acc = fetchedByte;
|
||||
|
@ -251,6 +259,7 @@ void CPU::LDA()
|
|||
|
||||
void CPU::LDX()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||
|
||||
x = fetchedByte;
|
||||
|
@ -261,6 +270,7 @@ void CPU::LDX()
|
|||
|
||||
void CPU::LDY()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||
|
||||
y = fetchedByte;
|
||||
|
@ -288,6 +298,8 @@ void CPU::STY()
|
|||
// --------------- LOGICAL --------------- //
|
||||
void CPU::AND()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
|
||||
acc &= fetchedByte;
|
||||
|
||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||
|
@ -297,6 +309,7 @@ void CPU::AND()
|
|||
|
||||
void CPU::EOR()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
acc ^= fetchedByte;
|
||||
|
||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||
|
@ -306,6 +319,7 @@ void CPU::EOR()
|
|||
|
||||
void CPU::ORA()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
acc |= fetchedByte;
|
||||
|
||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||
|
@ -315,6 +329,7 @@ void CPU::ORA()
|
|||
|
||||
void CPU::BIT()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
status.zero = (acc & fetchedByte) ? 0u : 1u;
|
||||
status.overflow = (TestBits(fetchedByte, (1u << 6u))) ? 1u : 0u;
|
||||
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
||||
|
@ -324,6 +339,15 @@ void CPU::BIT()
|
|||
// --------------- SHIFTS --------------- //
|
||||
void CPU::ASL()
|
||||
{
|
||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
||||
{
|
||||
fetchedByte = acc;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
// Set carry to fetchedByte of MSB pre-shift
|
||||
if (TestBits(fetchedByte, 0x80))
|
||||
{
|
||||
|
@ -351,6 +375,15 @@ void CPU::ASL()
|
|||
|
||||
void CPU::LSR()
|
||||
{
|
||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
||||
{
|
||||
fetchedByte = acc;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
// Set carry to fetchedByte of LSB pre-shift
|
||||
if (TestBits(fetchedByte, 0x01))
|
||||
{
|
||||
|
@ -378,6 +411,15 @@ void CPU::LSR()
|
|||
|
||||
void CPU::ROL()
|
||||
{
|
||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
||||
{
|
||||
fetchedByte = acc;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
uint8_t oldCarry = status.carry;
|
||||
|
||||
// Set carry to fetchedByte of MSB pre-shift
|
||||
|
@ -413,6 +455,15 @@ void CPU::ROL()
|
|||
|
||||
void CPU::ROR()
|
||||
{
|
||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
||||
{
|
||||
fetchedByte = acc;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
}
|
||||
|
||||
uint8_t oldCarry = status.carry;
|
||||
|
||||
// Set carry to fetchedByte of LSB pre-shift
|
||||
|
@ -691,6 +742,7 @@ void CPU::ARR()
|
|||
|
||||
void CPU::AXS()
|
||||
{
|
||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
||||
x = (acc & x) - fetchedByte;
|
||||
|
||||
status.negative = (IsNegative(x)) ? 1u : 0u;
|
||||
|
|
|
@ -6,6 +6,7 @@ const unsigned int VIDEO_SCALE = 4u;
|
|||
const unsigned int WINDOW_WIDTH = VIDEO_WIDTH * VIDEO_SCALE;
|
||||
const unsigned int WINDOW_HEIGHT = VIDEO_HEIGHT * VIDEO_SCALE;
|
||||
const unsigned int VIDEO_PITCH = VIDEO_WIDTH * 4u;
|
||||
const unsigned int PPU_CYCLES_PER_CPU_CYCLE = 3u;
|
||||
|
||||
|
||||
void DrawDebugLines(uint32_t* buffer)
|
||||
|
@ -39,7 +40,6 @@ void DrawDebugLines(uint32_t* buffer)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
NES::NES()
|
||||
: cpu(std::make_unique<CPU>()),
|
||||
ppu(std::make_unique<PPU>()),
|
||||
|
@ -53,62 +53,77 @@ NES::NES()
|
|||
palette[1] = Color(0, 30, 116);
|
||||
palette[2] = Color(8, 16, 144);
|
||||
palette[3] = Color(48, 0, 136);
|
||||
|
||||
palette[4] = Color(68, 0, 100);
|
||||
palette[5] = Color(92, 0, 48);
|
||||
palette[6] = Color(84, 4, 0);
|
||||
palette[7] = Color(60, 24, 0);
|
||||
|
||||
palette[8] = Color(32, 42, 0);
|
||||
palette[9] = Color(8, 58, 0);
|
||||
palette[10] = Color(0, 64, 0);
|
||||
palette[11] = Color(0, 60, 0);
|
||||
|
||||
palette[12] = Color(0, 50, 60);
|
||||
palette[13] = Color(0, 0, 0);
|
||||
palette[14] = Color(0, 0, 0);
|
||||
palette[15] = Color(0, 0, 0);
|
||||
|
||||
palette[16] = Color(152, 150, 152);
|
||||
palette[17] = Color(8, 76, 196);
|
||||
palette[18] = Color(48, 50, 236);
|
||||
palette[19] = Color(92, 30, 228);
|
||||
|
||||
palette[20] = Color(136, 20, 176);
|
||||
palette[21] = Color(160, 20, 100);
|
||||
palette[22] = Color(152, 34, 32);
|
||||
palette[23] = Color(120, 60, 0);
|
||||
|
||||
palette[24] = Color(84, 90, 0);
|
||||
palette[25] = Color(40, 114, 0);
|
||||
palette[26] = Color(8, 124, 0);
|
||||
palette[27] = Color(0, 118, 40);
|
||||
|
||||
palette[28] = Color(0, 102, 120);
|
||||
palette[29] = Color(0, 0, 0);
|
||||
palette[30] = Color(0, 0, 0);
|
||||
palette[31] = Color(0, 0, 0);
|
||||
|
||||
palette[32] = Color(236, 238, 236);
|
||||
palette[33] = Color(76, 154, 236);
|
||||
palette[34] = Color(120, 124, 236);
|
||||
palette[35] = Color(176, 98, 236);
|
||||
|
||||
palette[36] = Color(228, 84, 236);
|
||||
palette[37] = Color(236, 88, 180);
|
||||
palette[38] = Color(236, 106, 100);
|
||||
palette[39] = Color(212, 136, 32);
|
||||
|
||||
palette[40] = Color(160, 170, 0);
|
||||
palette[41] = Color(116, 196, 0);
|
||||
palette[42] = Color(76, 208, 32);
|
||||
palette[43] = Color(56, 204, 108);
|
||||
|
||||
palette[44] = Color(56, 180, 204);
|
||||
palette[45] = Color(60, 60, 60);
|
||||
palette[46] = Color(0, 0, 0);
|
||||
palette[47] = Color(0, 0, 0);
|
||||
|
||||
palette[48] = Color(236, 238, 236);
|
||||
palette[49] = Color(168, 204, 236);
|
||||
palette[50] = Color(188, 188, 236);
|
||||
palette[51] = Color(212, 178, 236);
|
||||
|
||||
palette[52] = Color(236, 174, 236);
|
||||
palette[53] = Color(236, 174, 212);
|
||||
palette[54] = Color(236, 180, 176);
|
||||
palette[55] = Color(228, 196, 144);
|
||||
|
||||
palette[56] = Color(204, 210, 120);
|
||||
palette[57] = Color(180, 222, 120);
|
||||
palette[58] = Color(168, 226, 144);
|
||||
palette[59] = Color(152, 226, 180);
|
||||
|
||||
palette[60] = Color(160, 214, 228);
|
||||
palette[61] = Color(160, 162, 160);
|
||||
palette[62] = Color(0, 0, 0);
|
||||
|
@ -128,17 +143,18 @@ void NES::Run()
|
|||
|
||||
while (!platform->ProcessInput(buttons))
|
||||
{
|
||||
cpu->Cycle();
|
||||
|
||||
input->Strobe(buttons);
|
||||
|
||||
uint8_t cpuCyclesTaken = (cpu->cycles - cpu->prevCycles) * 3u;
|
||||
uint64_t cpuCycles = cpu->Cycle();
|
||||
|
||||
ppu->Cycle(cpuCyclesTaken);
|
||||
|
||||
if (ppu->scanline == 260)
|
||||
for (auto i = 0; i < cpuCycles * PPU_CYCLES_PER_CPU_CYCLE; ++i)
|
||||
{
|
||||
//DrawDebugLines(video);
|
||||
ppu->Tick();
|
||||
}
|
||||
|
||||
if (ppu->currentScanline == 261)
|
||||
{
|
||||
//DrawDebugLines(ppu->video);
|
||||
platform->Update(ppu->video, VIDEO_PITCH);
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +177,7 @@ void NES::Write(BusSource source, uint16_t address, uint8_t value)
|
|||
// PPU Registers and Mirrors
|
||||
else if (address < 0x4000u)
|
||||
{
|
||||
auto index = static_cast<PpuRegister>(address % 8u);
|
||||
auto index = static_cast<PPU::Register>(address % 8u);
|
||||
|
||||
ppu->WriteRegister(index, value);
|
||||
}
|
||||
|
@ -180,7 +196,7 @@ void NES::Write(BusSource source, uint16_t address, uint8_t value)
|
|||
{
|
||||
uint16_t addr = (addrMsb << 8u) | addrLsb;
|
||||
|
||||
ppu->oam[ppu->oamAddr + addrLsb] = Read(BusSource::CPU, addr);
|
||||
ppu->oam[ppu->regOAMADDR + addrLsb] = Read(BusSource::CPU, addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,9 +252,10 @@ void NES::Write(BusSource source, uint16_t address, uint8_t value)
|
|||
// Palette RAM Indexes and Mirrors
|
||||
else if (address < 0x3F20u)
|
||||
{
|
||||
uint8_t index = address & 0x1Fu;
|
||||
uint8_t index = (address & 0x1Cu) >> 2u;
|
||||
uint8_t entry = address & 0x03u;
|
||||
|
||||
ppu->paletteIndexes[index] = value;
|
||||
ppu->palette[index][entry] = value;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -265,7 +282,7 @@ uint8_t NES::Read(BusSource source, uint16_t address)
|
|||
// PPU Registers and Mirrors
|
||||
else if (address < 0x4000u)
|
||||
{
|
||||
auto index = static_cast<PpuRegister>(address % 8u);
|
||||
auto index = static_cast<PPU::Register>(address % 8u);
|
||||
|
||||
byte = ppu->ReadRegister(index);
|
||||
}
|
||||
|
@ -343,9 +360,10 @@ uint8_t NES::Read(BusSource source, uint16_t address)
|
|||
// Palette RAM Indexes and Mirrors
|
||||
else if (address < 0x3F20u)
|
||||
{
|
||||
uint8_t index = address & 0x1Fu;
|
||||
uint8_t index = address & 0x0Cu;
|
||||
uint8_t entry = address & 0x03u;
|
||||
|
||||
byte = ppu->paletteIndexes[index];
|
||||
byte = ppu->palette[index][entry];
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
#include "NES.hpp"
|
||||
|
||||
|
||||
const unsigned int tilesPerWidth = 32u;
|
||||
const unsigned int tilesPerHeight = 30u;
|
||||
const unsigned int BG_PALETTE_START_ADDRESS = 0x3F00u;
|
||||
const unsigned int PATTERN_TABLE_0_START = 0x0000u;
|
||||
const unsigned int PATTERN_TABLE_1_START = 0x1000u;
|
||||
const unsigned int ATTRIBUTE_TABLE_START = 0x23C0u;
|
||||
|
||||
unsigned char reverse(uint8_t b)
|
||||
{
|
||||
|
@ -13,303 +15,404 @@ unsigned char reverse(uint8_t b)
|
|||
return b;
|
||||
}
|
||||
|
||||
void PPU::Cycle(uint8_t cpuCyclesElapsed)
|
||||
bool CheckBit(uint32_t value, uint32_t bit)
|
||||
{
|
||||
for (int cpuCycle = 0; cpuCycle < cpuCyclesElapsed; ++cpuCycle)
|
||||
{
|
||||
if (scanline < 240u && cycles < 256u)
|
||||
{
|
||||
// Draw BG
|
||||
uint8_t bgTilePositionX = cycles / 8u;
|
||||
uint8_t bgTilePositionY = scanline / 8u;
|
||||
uint8_t bgBlockX = bgTilePositionX / 4u;
|
||||
uint8_t bgBlockY = bgTilePositionY / 4u;
|
||||
uint16_t blockAddress = (bgBlockY * 8 + bgBlockX);
|
||||
uint8_t attribute = ReadMemory(0x23C0 + blockAddress);
|
||||
|
||||
uint8_t paletteIndex[4] = {
|
||||
static_cast<uint8_t>(attribute & 0x03u), // Upper Left
|
||||
static_cast<uint8_t>((attribute & 0x0Cu) >> 2u), // Upper Right
|
||||
static_cast<uint8_t>((attribute & 0x30u) >> 4u), // Bottom Left
|
||||
static_cast<uint8_t>((attribute & 0xC0u) >> 6u), // Bottom Right
|
||||
};
|
||||
|
||||
// Which quadrant of block are we in?
|
||||
uint8_t blockX = (bgTilePositionX % 4u) / 2;
|
||||
uint8_t blockY = (bgTilePositionY % 4u) / 2;
|
||||
|
||||
uint8_t pIndex = paletteIndex[(blockY << 1u) | blockX];
|
||||
|
||||
uint8_t bgTileIndex = ram[bgTilePositionY * tilesPerWidth + bgTilePositionX];
|
||||
uint8_t bgPixelWithinTileX = cycles % 8u;
|
||||
uint8_t bgPixelWithinTileY = scanline % 8u;
|
||||
uint16_t bgTileAddress = (0x1000u | (bgTileIndex << 4u)) + bgPixelWithinTileY;
|
||||
|
||||
int screenPixelX = (bgTilePositionX * 8) + bgPixelWithinTileX;
|
||||
int screenPixelY = (bgTilePositionY * 8) + bgPixelWithinTileY;
|
||||
|
||||
uint8_t bgPixelColorLsb =
|
||||
(ReadMemory(bgTileAddress) & (1u << (7u - bgPixelWithinTileX))) >> (7u - bgPixelWithinTileX);
|
||||
uint8_t bgPixelColorMsb =
|
||||
(ReadMemory(bgTileAddress + 8) & (1u << (7u - bgPixelWithinTileX))) >> (7u - bgPixelWithinTileX);
|
||||
|
||||
uint8_t bgPixelColor = bgPixelColorMsb << 1u | bgPixelColorLsb;
|
||||
|
||||
uint8_t systemPaletteIndex = paletteIndexes[(4 * pIndex) + bgPixelColor];
|
||||
|
||||
uint32_t color = nes->palette[systemPaletteIndex].GetValue();
|
||||
|
||||
video[screenPixelY * VIDEO_WIDTH + screenPixelX] = color;
|
||||
|
||||
// Draw sprites - lower index = higher priority
|
||||
for (int sprite = 63; sprite >= 0; --sprite)
|
||||
{
|
||||
uint8_t spritePositionY = oam[4 * sprite + 0];
|
||||
uint8_t spriteTileIndex = oam[4 * sprite + 1];
|
||||
uint8_t spriteAttributes = oam[4 * sprite + 2];
|
||||
uint8_t spritePositionX = oam[4 * sprite + 3];
|
||||
|
||||
uint8_t spritePalette = spriteAttributes & 0x3u;
|
||||
|
||||
bool spriteWithinScanline = (scanline >= spritePositionY) && (scanline < (spritePositionY + 8));
|
||||
bool spriteWithinCycle = (cycles >= spritePositionX) && (cycles < (spritePositionX + 8));
|
||||
|
||||
// Sprite on scanline
|
||||
if (spriteWithinScanline && spriteWithinCycle)
|
||||
{
|
||||
uint8_t spritePixelWithinTileY = scanline - spritePositionY;
|
||||
uint8_t spritePixelWithinTileX = cycles - spritePositionX;
|
||||
uint16_t spriteTileAddress = (spriteTileIndex << 4u) + spritePixelWithinTileY;
|
||||
|
||||
uint8_t pixelX = ((spritePositionX) + spritePixelWithinTileX) % 256;
|
||||
uint8_t pixelY = ((spritePositionY) + spritePixelWithinTileY) % 240;
|
||||
|
||||
uint8_t spriteLsb = ReadMemory(spriteTileAddress);
|
||||
uint8_t spriteMsb = ReadMemory(spriteTileAddress + 8);
|
||||
uint8_t spriteChr = spriteMsb | spriteLsb;
|
||||
|
||||
// Flip horizontally
|
||||
if (spriteAttributes & 0x40u)
|
||||
{
|
||||
spriteLsb = reverse(spriteLsb);
|
||||
spriteMsb = reverse(spriteMsb);
|
||||
return (value & bit) == bit;
|
||||
}
|
||||
|
||||
uint8_t spritePixelColorLsb =
|
||||
(spriteLsb & (1u << (7u - spritePixelWithinTileX))) >> (7u - spritePixelWithinTileX);
|
||||
uint8_t spritePixelColorMsb =
|
||||
(spriteMsb & (1u << (7u - spritePixelWithinTileX))) >> (7u - spritePixelWithinTileX);
|
||||
|
||||
uint8_t spritePixelColor = spritePixelColorMsb << 1u | spritePixelColorLsb;
|
||||
|
||||
uint8_t spriteSystemPaletteIndex = paletteIndexes[0x10 + (4 * spritePalette) + spritePixelColor];
|
||||
|
||||
uint32_t spriteColor = nes->palette[spriteSystemPaletteIndex].GetValue();
|
||||
|
||||
// Do not draw empty pixels of sprite
|
||||
if (spriteChr != 0u)
|
||||
PPU::PPU()
|
||||
{
|
||||
if (sprite == 0)
|
||||
// Visible Frame
|
||||
{
|
||||
if (bgPixelColor != 0u)
|
||||
for (int scanline = 0; scanline <= 239; ++scanline)
|
||||
{
|
||||
ppuStatus.sprite0Hit = 1u;
|
||||
// Data fetching
|
||||
for (int cycle = 1; cycle <= 256; cycle += 8)
|
||||
{
|
||||
events[scanline][cycle + 0] |= Event::FETCH_NT;
|
||||
events[scanline][cycle + 2] |= Event::FETCH_AT;
|
||||
events[scanline][cycle + 4] |= Event::FETCH_PT_LOW;
|
||||
events[scanline][cycle + 6] |= Event::FETCH_PT_HIGH;
|
||||
}
|
||||
|
||||
// Incrementing Hori(V)
|
||||
for (int cycle = 8; cycle <= 256; cycle += 8)
|
||||
{
|
||||
events[scanline][cycle] |= Event::INCREMENT_TILE_X;
|
||||
}
|
||||
|
||||
// Draw and shift
|
||||
for (int cycle = 2; cycle <= 257; ++cycle)
|
||||
{
|
||||
events[scanline][cycle] |= Event::DRAW | Event::SHIFT_BACKGROUND;
|
||||
}
|
||||
|
||||
// Reload shift registers
|
||||
for (int cycle = 9; cycle <= 257; cycle += 8)
|
||||
{
|
||||
events[scanline][cycle] |= Event::LOAD_BACKGROUND;
|
||||
}
|
||||
|
||||
// Inc Vert(v)
|
||||
events[scanline][256] |= Event::INCREMENT_TILE_Y;
|
||||
|
||||
// Hori(v) = Hori(T)
|
||||
events[scanline][257] |= Event::SET_TILE_X;
|
||||
|
||||
// Data fetching
|
||||
for (int cycle = 321; cycle <= 336; cycle += 8)
|
||||
{
|
||||
events[scanline][cycle + 0] |= Event::FETCH_NT;
|
||||
events[scanline][cycle + 2] |= Event::FETCH_AT;
|
||||
events[scanline][cycle + 4] |= Event::FETCH_PT_LOW;
|
||||
events[scanline][cycle + 6] |= Event::FETCH_PT_HIGH;
|
||||
}
|
||||
|
||||
// Shift
|
||||
for (int cycle = 322; cycle <= 337; ++cycle)
|
||||
{
|
||||
events[scanline][cycle] |= Event::SHIFT_BACKGROUND;
|
||||
}
|
||||
|
||||
// Reload shift registers
|
||||
for (int cycle = 329; cycle <= 337; cycle += 8)
|
||||
{
|
||||
events[scanline][cycle] |= Event::LOAD_BACKGROUND;
|
||||
}
|
||||
|
||||
events[scanline][328] |= Event::INCREMENT_TILE_X;
|
||||
events[scanline][336] |= Event::INCREMENT_TILE_X;
|
||||
|
||||
// Unused Fetches
|
||||
events[scanline][337] |= Event::FETCH_NT;
|
||||
events[scanline][339] |= Event::FETCH_NT;
|
||||
}
|
||||
}
|
||||
|
||||
video[pixelY * VIDEO_WIDTH + pixelX] = spriteColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (scanline == 241u && cycles == 0u)
|
||||
// VBlank
|
||||
{
|
||||
ppuStatus.vblankStarted = 1u;
|
||||
events[241][1] |= Event::SET_VBLANK;
|
||||
}
|
||||
|
||||
if (ppuCtrl.nmiEnable)
|
||||
// Pre-render
|
||||
{
|
||||
nes->nmi = true;
|
||||
}
|
||||
}
|
||||
else if (scanline == 261u)
|
||||
// Clear VBlank, Sprite0 Hit, and Sprite Overflow
|
||||
events[261][1] |= Event::CLEAR_FLAGS;
|
||||
|
||||
// Data fetching
|
||||
for (int cycle = 1; cycle <= 256; cycle += 8)
|
||||
{
|
||||
ppuStatus.sprite0Hit = 0u;
|
||||
ppuStatus.vblankStarted = 0;
|
||||
events[261][cycle + 0] |= Event::FETCH_NT;
|
||||
events[261][cycle + 2] |= Event::FETCH_AT;
|
||||
events[261][cycle + 4] |= Event::FETCH_PT_LOW;
|
||||
events[261][cycle + 6] |= Event::FETCH_PT_HIGH;
|
||||
}
|
||||
|
||||
++cycles;
|
||||
|
||||
if (cycles == 341u)
|
||||
// Incrementing Hori(V)
|
||||
for (int cycle = 8; cycle <= 256; cycle += 8)
|
||||
{
|
||||
cycles = 0;
|
||||
events[261][cycle] |= Event::INCREMENT_TILE_X;
|
||||
}
|
||||
|
||||
++scanline;
|
||||
|
||||
if (scanline == 262u)
|
||||
// Shift
|
||||
for (int cycle = 2; cycle <= 257; ++cycle)
|
||||
{
|
||||
scanline = 0;
|
||||
events[261][cycle] |= Event::SHIFT_BACKGROUND;
|
||||
}
|
||||
|
||||
// Reload shift registers
|
||||
for (int cycle = 9; cycle <= 257; cycle += 8)
|
||||
{
|
||||
events[261][cycle] |= Event::LOAD_BACKGROUND;
|
||||
}
|
||||
|
||||
// Inc Vert(v)
|
||||
events[261][256] |= Event::INCREMENT_TILE_Y;
|
||||
|
||||
// Hori(v) = Hori(t)
|
||||
events[261][257] |= Event::SET_TILE_X;
|
||||
|
||||
// Vert(v) = Vert(t)
|
||||
for (int cycle = 280; cycle <= 304; ++cycle)
|
||||
{
|
||||
events[261][cycle] |= Event::SET_TILE_Y;
|
||||
}
|
||||
|
||||
// Data fetching
|
||||
for (int cycle = 321; cycle <= 336; cycle += 8)
|
||||
{
|
||||
events[261][cycle + 0] |= Event::FETCH_NT;
|
||||
events[261][cycle + 2] |= Event::FETCH_AT;
|
||||
events[261][cycle + 4] |= Event::FETCH_PT_LOW;
|
||||
events[261][cycle + 6] |= Event::FETCH_PT_HIGH;
|
||||
}
|
||||
|
||||
// Shift
|
||||
for (int cycle = 322; cycle <= 337; ++cycle)
|
||||
{
|
||||
events[261][cycle] |= Event::SHIFT_BACKGROUND;
|
||||
}
|
||||
|
||||
// Reload shift registers
|
||||
for (int cycle = 329; cycle <= 337; cycle += 8)
|
||||
{
|
||||
events[261][cycle] |= Event::LOAD_BACKGROUND;
|
||||
}
|
||||
|
||||
events[261][328] |= Event::INCREMENT_TILE_X;
|
||||
events[261][336] |= Event::INCREMENT_TILE_X;
|
||||
|
||||
// Unused Fetches
|
||||
events[261][337] |= Event::FETCH_NT;
|
||||
events[261][339] |= Event::FETCH_NT;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::Tick()
|
||||
{
|
||||
int event = events[currentScanline][currentCycle];
|
||||
|
||||
if (CheckBit(event, Event::DRAW))
|
||||
{
|
||||
Draw();
|
||||
}
|
||||
|
||||
if (CheckBit(event, Event::SHIFT_BACKGROUND))
|
||||
{
|
||||
ShiftBG();
|
||||
}
|
||||
|
||||
if (CheckBit(event, Event::FETCH_NT))
|
||||
{
|
||||
FetchNT();
|
||||
}
|
||||
else if (CheckBit(event, Event::FETCH_AT))
|
||||
{
|
||||
FetchAT();
|
||||
}
|
||||
else if (CheckBit(event, Event::FETCH_PT_LOW))
|
||||
{
|
||||
FetchPTLow();
|
||||
}
|
||||
else if (CheckBit(event, Event::FETCH_PT_HIGH))
|
||||
{
|
||||
FetchPTHigh();
|
||||
}
|
||||
|
||||
if (CheckBit(event, Event::LOAD_BACKGROUND))
|
||||
{
|
||||
LoadBackground();
|
||||
}
|
||||
|
||||
if (CheckBit(event, Event::SET_TILE_X))
|
||||
{
|
||||
SetTileX();
|
||||
}
|
||||
else if (CheckBit(event, Event::SET_TILE_Y))
|
||||
{
|
||||
SetTileY();
|
||||
}
|
||||
|
||||
if (CheckBit(event, Event::INCREMENT_TILE_Y))
|
||||
{
|
||||
IncrementTileY();
|
||||
}
|
||||
else if (CheckBit(event, Event::INCREMENT_TILE_X))
|
||||
{
|
||||
IncrementTileX();
|
||||
}
|
||||
|
||||
if (CheckBit(event, Event::SET_VBLANK))
|
||||
{
|
||||
SetVBlank();
|
||||
}
|
||||
else if (CheckBit(event, Event::CLEAR_FLAGS))
|
||||
{
|
||||
ClearFlags();
|
||||
}
|
||||
|
||||
++currentCycle;
|
||||
|
||||
// Odd frame, skip
|
||||
if (currentScanline == 261 && currentCycle == 340 && (frameNumber % 2))
|
||||
{
|
||||
currentScanline = 0;
|
||||
currentCycle = 0;
|
||||
++frameNumber;
|
||||
}
|
||||
else if (currentCycle == 341)
|
||||
{
|
||||
currentCycle = 0;
|
||||
++currentScanline;
|
||||
|
||||
if (currentScanline == 262)
|
||||
{
|
||||
currentScanline = 0;
|
||||
++frameNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::WriteRegister(PpuRegister reg, uint8_t value)
|
||||
void PPU::WriteRegister(Register reg, uint8_t value)
|
||||
{
|
||||
switch (reg)
|
||||
{
|
||||
case PpuRegister::PPUCTRL:
|
||||
case Register::PPUCTRL:
|
||||
{
|
||||
ppuCtrl.SetByte(value);
|
||||
regT |= (value & 0x3u) << 10u;
|
||||
regPPUCTRL.SetByte(value);
|
||||
|
||||
// yyy NNYY YYYX XXXX
|
||||
// t: --- BA-- ---- ---- = d: ---- --BA
|
||||
tempAddress.nametableY = (value & 0x2u) >> 1u;
|
||||
tempAddress.nametableX = (value & 0x1u) >> 1u;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUMASK:
|
||||
|
||||
case Register::PPUMASK:
|
||||
{
|
||||
ppuMask.SetByte(value);
|
||||
regPPUMASK.SetByte(value);
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUSTATUS:
|
||||
|
||||
case Register::OAMADDR:
|
||||
{
|
||||
ppuStatus.SetByte(value);
|
||||
regW = 0u;
|
||||
regOAMADDR = value;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::OAMADDR:
|
||||
|
||||
case Register::OAMDATA:
|
||||
{
|
||||
oamAddr = value;
|
||||
oam[regOAMADDR] = value;
|
||||
|
||||
++regOAMADDR;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::OAMDATA:
|
||||
{
|
||||
oamData = value;
|
||||
|
||||
break;
|
||||
case Register::PPUSCROLL:
|
||||
{
|
||||
if (firstWrite)
|
||||
{
|
||||
// yyy NNYY YYYX XXXX
|
||||
// t: --- ---- ---H GFED = d: HGFE D---
|
||||
// x: CBA = d: ---- -CBA
|
||||
// w: = 1
|
||||
tempAddress.scrollCoarseX = (value & 0xF8u) >> 3u;
|
||||
scrollFineX = value & 0x7u;
|
||||
|
||||
firstWrite = false;
|
||||
}
|
||||
case PpuRegister::PPUSCROLL:
|
||||
else
|
||||
{
|
||||
ppuScroll = value;
|
||||
// yyy NNYY YYYX XXXX
|
||||
// t: CBA --HG FED- ---- = d: HGFE DCBA
|
||||
// w: = 0
|
||||
tempAddress.scrollCoarseY = (value & 0xF8u) >> 3u;
|
||||
tempAddress.scrollFineY = value & 0x7u;
|
||||
|
||||
if (regW == 0u)
|
||||
{
|
||||
regT |= (value & 0xF8u) >> 3u;
|
||||
regX = value & 0x07u;
|
||||
|
||||
regW = 1u;
|
||||
}
|
||||
else if (regW == 1u)
|
||||
{
|
||||
regT |= (value & 0x07u) << 12u;
|
||||
regT |= (value & 0x38u) << 2u;
|
||||
regT |= (value & 0xC0u) << 2u;
|
||||
|
||||
regW = 0u;
|
||||
firstWrite = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUADDR:
|
||||
|
||||
case Register::PPUADDR:
|
||||
{
|
||||
// MSB
|
||||
if (!ppuAddrW)
|
||||
if (firstWrite)
|
||||
{
|
||||
ppuAddr = value << 8u;
|
||||
// yyy NNYY YYYX XXXX
|
||||
// t: -FE DCBA ---- ---- = d: --FE DCBA
|
||||
// t: X-- ---- ---- ---- = 0
|
||||
// w: = 1
|
||||
tempAddress.scrollCoarseY &= ~(0x18u);
|
||||
tempAddress.scrollCoarseY |= (value & 0x3u) << 3u;
|
||||
|
||||
ppuAddrW = true;
|
||||
tempAddress.nametableY = (value & 0x8u) >> 3u;
|
||||
tempAddress.nametableX = (value & 0x4u) >> 2u;
|
||||
|
||||
tempAddress.scrollFineY = (value & 0x30u) >> 4u;
|
||||
tempAddress.scrollFineY &= ~(0x4u);
|
||||
|
||||
firstWrite = false;
|
||||
}
|
||||
|
||||
// LSB
|
||||
else
|
||||
{
|
||||
ppuAddr |= value;
|
||||
// yyy NNYY YYYX XXXX
|
||||
// t: ... .... HGFE DCBA = d: HGFE DCBA
|
||||
// v = t
|
||||
// w: = 0
|
||||
tempAddress.scrollCoarseY &= ~(0x7u);
|
||||
tempAddress.scrollCoarseY |= (value & 0xE0u) >> 5u;
|
||||
|
||||
ppuAddrW = false;
|
||||
}
|
||||
tempAddress.scrollCoarseX = value & 0x1Fu;
|
||||
|
||||
if (regW == 0u)
|
||||
{
|
||||
regT |= (value & 0x3Fu) << 8u;
|
||||
regT &= 0x7Fu;
|
||||
regW = 1u;
|
||||
}
|
||||
else if (regW == 1u)
|
||||
{
|
||||
regT |= value;
|
||||
regV = regT;
|
||||
regW = 0u;
|
||||
currentAddress = tempAddress;
|
||||
|
||||
firstWrite = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUDATA:
|
||||
case Register::PPUDATA:
|
||||
{
|
||||
WriteMemory(ppuAddr, value);
|
||||
WriteMemory(currentAddress.GetValue(), value);
|
||||
|
||||
ppuAddr += (ppuCtrl.vramAddrIncrement) ? 32u : 1u;
|
||||
uint16_t temp = currentAddress.GetValue();
|
||||
temp += (regPPUCTRL.vramAddrIncrement) ? 32u : 1u;
|
||||
currentAddress.SetValue(temp);
|
||||
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PPU::ReadRegister(PpuRegister reg)
|
||||
uint8_t PPU::ReadRegister(Register reg)
|
||||
{
|
||||
uint8_t byte{};
|
||||
|
||||
switch (reg)
|
||||
{
|
||||
case PpuRegister::PPUCTRL:
|
||||
case Register::PPUSTATUS:
|
||||
{
|
||||
byte = ppuCtrl.GetByte();
|
||||
byte = regPPUSTATUS.GetByte();
|
||||
|
||||
regPPUSTATUS.vblankStarted = 0u;
|
||||
firstWrite = true;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUMASK:
|
||||
case Register::OAMDATA:
|
||||
{
|
||||
byte = ppuMask.GetByte();
|
||||
byte = oam[regOAMADDR];
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUSTATUS:
|
||||
case Register::PPUDATA:
|
||||
{
|
||||
byte = ppuStatus.GetByte();
|
||||
ppuStatus.vblankStarted = 0u;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::OAMADDR:
|
||||
{
|
||||
byte = oamAddr;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::OAMDATA:
|
||||
{
|
||||
byte = oamData;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUSCROLL:
|
||||
{
|
||||
byte = ppuScroll;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUADDR:
|
||||
{
|
||||
byte = ppuAddr;
|
||||
|
||||
break;
|
||||
}
|
||||
case PpuRegister::PPUDATA:
|
||||
{
|
||||
byte = ppuData;
|
||||
// Returned byte is buffered from last read of PPUDATA
|
||||
byte = oldByte;
|
||||
oldByte = ReadMemory(currentAddress.GetValue());
|
||||
|
||||
// If reading palette data, return actual byte instead of buffered byte
|
||||
if (currentAddress.GetValue() >= BG_PALETTE_START_ADDRESS)
|
||||
{
|
||||
byte = oldByte;
|
||||
}
|
||||
|
||||
uint16_t temp = currentAddress.GetValue();
|
||||
temp += (regPPUCTRL.vramAddrIncrement) ? 32u : 1u;
|
||||
currentAddress.SetValue(temp);
|
||||
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
return byte;
|
||||
|
@ -324,3 +427,277 @@ uint8_t PPU::ReadMemory(uint16_t address)
|
|||
{
|
||||
return nes->Read(BusSource::PPU, address);
|
||||
}
|
||||
|
||||
void PPU::FetchNT()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
uint16_t address = 0x2000u | (currentAddress.scrollCoarseY << 5u) | currentAddress.scrollCoarseX;
|
||||
|
||||
nametableLatch = ReadMemory(address);
|
||||
}
|
||||
|
||||
void PPU::FetchAT()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
// Bits 4,3,2 of Coarse Y Scroll are Bits 5,4,3 of attribute table address
|
||||
// Bits 4,3,2 of Coarse X Scroll are Bits 2,1,0 of attribute table address
|
||||
uint16_t address = ATTRIBUTE_TABLE_START
|
||||
| ((currentAddress.scrollCoarseY & 0x1Cu) << 1u)
|
||||
| ((currentAddress.scrollCoarseX & 0x1Cu) >> 2u);
|
||||
|
||||
// Bit 1 of Coarse Y Scroll is Block Row (0 or 1)
|
||||
// Bit 1 of Coarse X Scroll is Block Col (0 or 1)
|
||||
|
||||
// Y determines if top or bottom
|
||||
// X determines if left or right
|
||||
|
||||
uint8_t at = ReadMemory(address);
|
||||
|
||||
uint8_t location = ((currentAddress.scrollCoarseY & 0x2u))
|
||||
| ((currentAddress.scrollCoarseX & 0x2u) >> 1u);
|
||||
|
||||
switch (location)
|
||||
{
|
||||
// Top Left
|
||||
case 0:
|
||||
{
|
||||
attributeTableLatch = at & 0x03u;
|
||||
break;
|
||||
}
|
||||
|
||||
// Top Right
|
||||
case 1:
|
||||
{
|
||||
attributeTableLatch = (at & 0x0Cu) >> 2u;
|
||||
break;
|
||||
}
|
||||
|
||||
// Bottom Left
|
||||
case 2:
|
||||
{
|
||||
attributeTableLatch = (at & 0x30u) >> 4u;
|
||||
break;
|
||||
}
|
||||
|
||||
// Bottom Right
|
||||
case 3:
|
||||
{
|
||||
attributeTableLatch = (at & 0xC0u) >> 6u;
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::FetchPTLow()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
uint16_t address = regPPUCTRL.bgPatternTableAddr ? PATTERN_TABLE_1_START : PATTERN_TABLE_0_START;
|
||||
address += (nametableLatch * 16u) + currentAddress.scrollFineY;
|
||||
|
||||
patternTableLowLatch = ReadMemory(address);
|
||||
}
|
||||
|
||||
void PPU::FetchPTHigh()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
uint16_t address = regPPUCTRL.bgPatternTableAddr ? PATTERN_TABLE_1_START : PATTERN_TABLE_0_START;
|
||||
address += (nametableLatch * 16u) + 8u + currentAddress.scrollFineY;
|
||||
|
||||
patternTableHighLatch = ReadMemory(address);
|
||||
}
|
||||
|
||||
void PPU::SetVBlank()
|
||||
{
|
||||
regPPUSTATUS.vblankStarted = 1u;
|
||||
|
||||
if (regPPUCTRL.nmiEnable)
|
||||
{
|
||||
nes->nmi = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::SetTileX()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
// yyy NNYY YYYX XXXX
|
||||
// v: --- -F-- ---E DCBA = t: --- -F-- ---E DCBA
|
||||
currentAddress.nametableX = tempAddress.nametableX;
|
||||
currentAddress.scrollCoarseX = tempAddress.scrollCoarseX;
|
||||
}
|
||||
|
||||
void PPU::SetTileY()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
// yyy NNYY YYYX XXXX
|
||||
// v: IHG F-ED CBA- ---- = t: IHG F-ED CBA- ----
|
||||
currentAddress.scrollCoarseY = tempAddress.scrollCoarseY;
|
||||
currentAddress.nametableY = tempAddress.nametableY;
|
||||
currentAddress.scrollFineY = tempAddress.scrollFineY;
|
||||
}
|
||||
|
||||
void PPU::Draw()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
// Draw occurs on cycle 2
|
||||
uint8_t x = currentCycle - 2;
|
||||
uint8_t h = (patternTableHighShift & 0x8000u) >> (15u - scrollFineX);
|
||||
uint8_t l = (patternTableLowShift & 0x8000u) >> (15u - scrollFineX);
|
||||
|
||||
uint8_t bgColor = (h << 1u) | l;
|
||||
|
||||
uint8_t paletteIndexHigh = (atShiftHigh & 0x80u) >> (7u - scrollFineX);
|
||||
uint8_t paletteIndexLow = (atShiftLow & 0x80u) >> (7u - scrollFineX);
|
||||
|
||||
uint8_t pIndex = (paletteIndexHigh << 1u) | paletteIndexLow;
|
||||
|
||||
uint8_t paletteIndex = palette[pIndex][bgColor];
|
||||
|
||||
uint32_t color = nes->palette[paletteIndex].GetValue();
|
||||
|
||||
video[currentScanline * VIDEO_WIDTH + x] = color;
|
||||
}
|
||||
|
||||
void PPU::LoadBackground()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
patternTableHighShift &= ~(0xFFu);
|
||||
patternTableHighShift |= (patternTableHighLatch);
|
||||
|
||||
patternTableLowShift &= ~(0xFFu);
|
||||
patternTableLowShift |= (patternTableLowLatch);
|
||||
|
||||
attribute = attributeTableLatch;
|
||||
}
|
||||
|
||||
void PPU::IncrementTileX()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
// Increment coarse X scroll and wrap if needed
|
||||
if (currentAddress.scrollCoarseX == 31)
|
||||
{
|
||||
currentAddress.scrollCoarseX = 0;
|
||||
currentAddress.nametableX = currentAddress.nametableX ? 0u : 1u;
|
||||
}
|
||||
else
|
||||
{
|
||||
++currentAddress.scrollCoarseX;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::IncrementTileY()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
// Increment Y scroll and wrap if needed
|
||||
if (currentAddress.scrollFineY < 7)
|
||||
{
|
||||
++currentAddress.scrollFineY;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentAddress.scrollFineY = 0;
|
||||
|
||||
if (currentAddress.scrollCoarseY == 29)
|
||||
{
|
||||
currentAddress.scrollCoarseY = 0;
|
||||
currentAddress.nametableY = currentAddress.nametableY ? 0u : 1u;
|
||||
}
|
||||
else if (currentAddress.scrollCoarseY == 31)
|
||||
{
|
||||
currentAddress.scrollCoarseY = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
++currentAddress.scrollCoarseY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::ClearFlags()
|
||||
{
|
||||
regPPUSTATUS.vblankStarted = 0u;
|
||||
regPPUSTATUS.spriteOverflow = 0u;
|
||||
regPPUSTATUS.sprite0Hit = 0u;
|
||||
}
|
||||
|
||||
void PPU::ShiftBG()
|
||||
{
|
||||
if (!regPPUMASK.showBackground)
|
||||
{return;}
|
||||
|
||||
patternTableHighShift <<= 1u;
|
||||
patternTableLowShift <<= 1u;
|
||||
atShiftLow <<= 1u;
|
||||
atShiftHigh <<= 1u;
|
||||
|
||||
atShiftLow |= attribute & 0x1u;
|
||||
atShiftHigh |= (attribute & 0x2u) >> 1u;
|
||||
}
|
||||
|
||||
uint8_t PPU::PpuStatus::GetByte()
|
||||
{
|
||||
uint8_t byte = vblankStarted << 7u | sprite0Hit << 6u | spriteOverflow << 5u | previousLsb;
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
void PPU::PpuMask::SetByte(uint8_t byte)
|
||||
{
|
||||
blueEmphasis = (byte & 0x80u) >> 7u;
|
||||
greenEmphasis = (byte & 0x40u) >> 6u;
|
||||
redEmphasis = (byte & 0x20u) >> 5u;
|
||||
showSprites = (byte & 0x10u) >> 4u;
|
||||
showBackground = (byte & 0x08u) >> 3u;
|
||||
showSpritesLeft = (byte & 0x04u) >> 2u;
|
||||
showBackgroundLeft = (byte & 0x02u) >> 1u;
|
||||
grayscale = byte & 0x01u;
|
||||
}
|
||||
|
||||
void PPU::PpuCtrl::SetByte(uint8_t byte)
|
||||
{
|
||||
nmiEnable = (byte & 0x80u) >> 7u;
|
||||
masterSlaveSelect = (byte & 0x40u) >> 6u;
|
||||
spriteSize = (byte & 0x20u) >> 5u;
|
||||
bgPatternTableAddr = (byte & 0x10u) >> 4u;
|
||||
spritePatternTableAddr = (byte & 0x08u) >> 3u;
|
||||
vramAddrIncrement = (byte & 0x04u) >> 2u;
|
||||
nametableBaseAddr = ((byte & 0x02u) >> 1u | (byte & 0x01u));
|
||||
}
|
||||
|
||||
void PPU::Address::SetValue(uint16_t value)
|
||||
{
|
||||
// yyy NNYY YYYX XXXX
|
||||
scrollFineY = (value & 0x7000u) >> 12u;
|
||||
nametableX = (value & 0x0800u) >> 11u;
|
||||
nametableY = (value & 0x0400u) >> 10u;
|
||||
scrollCoarseY = (value & 0x03E0u) >> 5u;
|
||||
scrollCoarseX = value & 0x0001Fu;
|
||||
}
|
||||
|
||||
uint16_t PPU::Address::GetValue()
|
||||
{
|
||||
// yyy NNYY YYYX XXXX
|
||||
return (scrollFineY << 12u) | (nametableY << 11u) | (nametableX << 10u) | (scrollCoarseY << 5u) | scrollCoarseX;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "Registers.hpp"
|
||||
#include <cstdint>
|
||||
#include <queue>
|
||||
|
||||
|
||||
class NES;
|
||||
|
||||
|
||||
const unsigned int VRAM_SIZE = 2048u;
|
||||
const unsigned int PALETTE_SIZE = 8u;
|
||||
const unsigned int OAM_SIZE = 256u;
|
||||
const unsigned int VIDEO_WIDTH = 256u;
|
||||
const unsigned int VIDEO_HEIGHT = 240u;
|
||||
const unsigned int TOTAL_SCANLINES = 262u;
|
||||
const unsigned int TOTAL_DOTS = 341u;
|
||||
|
||||
|
||||
class PPU
|
||||
{
|
||||
public:
|
||||
void Cycle(uint8_t cpuCyclesElapsed);
|
||||
void WriteRegister(PpuRegister reg, uint8_t value);
|
||||
uint8_t ReadRegister(PpuRegister reg);
|
||||
PPU();
|
||||
enum Register
|
||||
{
|
||||
PPUCTRL = 0x00,
|
||||
PPUMASK = 0x01,
|
||||
PPUSTATUS = 0x02,
|
||||
OAMADDR = 0x03,
|
||||
OAMDATA = 0x04,
|
||||
PPUSCROLL = 0x05,
|
||||
PPUADDR = 0x06,
|
||||
PPUDATA = 0x07,
|
||||
};
|
||||
|
||||
void Tick();
|
||||
void WriteRegister(Register reg, uint8_t value);
|
||||
uint8_t ReadRegister(Register reg);
|
||||
|
||||
void WriteMemory(uint16_t address, uint8_t value);
|
||||
uint8_t ReadMemory(uint16_t address);
|
||||
|
@ -25,30 +42,123 @@ public:
|
|||
private:
|
||||
friend class NES;
|
||||
|
||||
PpuRegisterCTRL ppuCtrl{};
|
||||
PpuRegisterMask ppuMask{};
|
||||
PpuRegisterStatus ppuStatus{};
|
||||
uint8_t oamAddr{};
|
||||
uint8_t oamData{};
|
||||
uint8_t oam[256]{};
|
||||
uint8_t ppuScroll{};
|
||||
uint16_t ppuAddr{};
|
||||
uint8_t ppuData{};
|
||||
void FetchNT();
|
||||
void FetchAT();
|
||||
void FetchPTLow();
|
||||
void FetchPTHigh();
|
||||
void SetVBlank();
|
||||
void SetTileX();
|
||||
void SetTileY();
|
||||
void LoadBackground();
|
||||
void Draw();
|
||||
void IncrementTileX();
|
||||
void IncrementTileY();
|
||||
void ClearFlags();
|
||||
|
||||
bool ppuAddrW{};
|
||||
enum Event
|
||||
{
|
||||
FETCH_NT = 0x1,
|
||||
FETCH_AT = 0x2,
|
||||
FETCH_PT_LOW = 0x4,
|
||||
FETCH_PT_HIGH = 0x8,
|
||||
LOAD_BACKGROUND = 0x10,
|
||||
SHIFT_BACKGROUND = 0x20,
|
||||
SET_VBLANK = 0x40,
|
||||
SET_TILE_X = 0x80,
|
||||
SET_TILE_Y = 0x100,
|
||||
DRAW = 0x200,
|
||||
INCREMENT_TILE_X = 0x400,
|
||||
INCREMENT_TILE_Y = 0x800,
|
||||
CLEAR_FLAGS = 0x1000
|
||||
};
|
||||
|
||||
uint8_t regV{};
|
||||
uint8_t regT{};
|
||||
uint8_t regX{};
|
||||
uint8_t regW{};
|
||||
struct PpuCtrl
|
||||
{
|
||||
void SetByte(uint8_t byte);
|
||||
|
||||
uint16_t cycles{};
|
||||
uint16_t scanline{};
|
||||
uint16_t nametableBaseAddr;
|
||||
uint8_t vramAddrIncrement;
|
||||
uint8_t spritePatternTableAddr;
|
||||
uint8_t bgPatternTableAddr;
|
||||
uint8_t spriteSize;
|
||||
uint8_t masterSlaveSelect;
|
||||
uint8_t nmiEnable;
|
||||
};
|
||||
|
||||
struct PpuMask
|
||||
{
|
||||
void SetByte(uint8_t byte);
|
||||
|
||||
uint8_t grayscale;
|
||||
uint8_t showBackgroundLeft;
|
||||
uint8_t showSpritesLeft;
|
||||
uint8_t showBackground;
|
||||
uint8_t showSprites;
|
||||
uint8_t redEmphasis;
|
||||
uint8_t greenEmphasis;
|
||||
uint8_t blueEmphasis;
|
||||
};
|
||||
|
||||
struct PpuStatus
|
||||
{
|
||||
uint8_t GetByte();
|
||||
|
||||
uint8_t vblankStarted;
|
||||
uint8_t sprite0Hit;
|
||||
uint8_t spriteOverflow;
|
||||
uint8_t previousLsb;
|
||||
};
|
||||
|
||||
struct Address
|
||||
{
|
||||
uint16_t GetValue();
|
||||
void SetValue(uint16_t value);
|
||||
|
||||
uint8_t scrollFineY;
|
||||
uint8_t nametableX;
|
||||
uint8_t nametableY;
|
||||
uint8_t scrollCoarseX;
|
||||
uint8_t scrollCoarseY;
|
||||
};
|
||||
|
||||
// External Registersgg
|
||||
PpuCtrl regPPUCTRL{}; // $2000, W
|
||||
PpuMask regPPUMASK{}; // $2001, W
|
||||
PpuStatus regPPUSTATUS{}; // $2002, R
|
||||
uint8_t regOAMADDR{}; // $2003, W
|
||||
uint8_t oldByte{};
|
||||
|
||||
// Internal Registers
|
||||
Address currentAddress{};
|
||||
Address tempAddress{};
|
||||
uint8_t scrollFineX{};
|
||||
bool firstWrite{true};
|
||||
|
||||
// Timing tracking
|
||||
uint32_t events[TOTAL_SCANLINES][TOTAL_DOTS]{};
|
||||
uint16_t currentCycle{};
|
||||
uint16_t currentScanline{};
|
||||
|
||||
// Memory
|
||||
uint8_t ram[VRAM_SIZE]{};
|
||||
uint8_t paletteIndexes[32]{};
|
||||
|
||||
uint8_t palette[PALETTE_SIZE][4]{};
|
||||
uint8_t oam[OAM_SIZE]{};
|
||||
uint32_t video[VIDEO_WIDTH * VIDEO_HEIGHT]{};
|
||||
|
||||
uint64_t frameNumber{};
|
||||
|
||||
// Shift registers and latches
|
||||
uint16_t patternTableHighShift{};
|
||||
uint16_t patternTableLowShift{};
|
||||
uint8_t atShiftLow{};
|
||||
uint8_t atShiftHigh{};
|
||||
uint8_t nametableLatch{};
|
||||
uint8_t attributeTableLatch{};
|
||||
uint8_t attribute;
|
||||
uint8_t patternTableLowLatch{};
|
||||
uint8_t patternTableHighLatch{};
|
||||
|
||||
|
||||
NES* nes;
|
||||
void ShiftBG();
|
||||
};
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
enum PpuRegister
|
||||
{
|
||||
PPUCTRL = 0x00,
|
||||
PPUMASK = 0x01,
|
||||
PPUSTATUS = 0x02,
|
||||
OAMADDR = 0x03,
|
||||
OAMDATA = 0x04,
|
||||
PPUSCROLL = 0x05,
|
||||
PPUADDR = 0x06,
|
||||
PPUDATA = 0x07,
|
||||
};
|
||||
|
||||
struct PpuRegisterCTRL
|
||||
{
|
||||
void SetByte(uint8_t byte)
|
||||
{
|
||||
nmiEnable = (byte & 0x80u) >> 7u;
|
||||
masterSlaveSelect = (byte & 0x40u) >> 6u;
|
||||
spriteSize = (byte & 0x20u) >> 5u;
|
||||
bgPatternTableAddr = (byte & 0x10u) >> 4u;
|
||||
spritePatternTableAddr = (byte & 0x08u) >> 3u;
|
||||
vramAddrIncrement = (byte & 0x04u) >> 2u;
|
||||
nametableBaseAddr = ((byte & 0x02u) >> 1u | (byte & 0x01u));
|
||||
}
|
||||
|
||||
uint8_t GetByte()
|
||||
{
|
||||
return nmiEnable << 7u | masterSlaveSelect << 6u | spriteSize << 5u | bgPatternTableAddr << 4u
|
||||
| spritePatternTableAddr << 3u | vramAddrIncrement << 2u | (nametableBaseAddr & 0xFF00u) << 1u
|
||||
| (nametableBaseAddr & 0xFFu);
|
||||
}
|
||||
|
||||
uint16_t nametableBaseAddr;
|
||||
uint8_t vramAddrIncrement;
|
||||
uint8_t spritePatternTableAddr;
|
||||
uint8_t bgPatternTableAddr;
|
||||
uint8_t spriteSize;
|
||||
uint8_t masterSlaveSelect;
|
||||
uint8_t nmiEnable;
|
||||
};
|
||||
|
||||
struct PpuRegisterMask
|
||||
{
|
||||
void SetByte(uint8_t byte)
|
||||
{
|
||||
blueEmphasis = (byte & 0x80u) >> 7u;
|
||||
greenEmphasis = (byte & 0x40u) >> 6u;
|
||||
redEmphasis = (byte & 0x20u) >> 5u;
|
||||
showSprites = (byte & 0x10u) >> 4u;
|
||||
showBackground = (byte & 0x08u) >> 3u;
|
||||
showSpritesLeft = (byte & 0x04u) >> 2u;
|
||||
showBackgroundLeft = (byte & 0x02u) >> 1u;
|
||||
grayscale = byte & 0x01u;
|
||||
}
|
||||
|
||||
uint8_t GetByte()
|
||||
{
|
||||
return blueEmphasis << 7u | greenEmphasis << 6u | redEmphasis << 5u | showSprites << 4u
|
||||
| showBackground << 3u | showSpritesLeft << 2u | showBackgroundLeft << 1u | grayscale;
|
||||
}
|
||||
|
||||
uint8_t grayscale;
|
||||
uint8_t showBackgroundLeft;
|
||||
uint8_t showSpritesLeft;
|
||||
uint8_t showBackground;
|
||||
uint8_t showSprites;
|
||||
uint8_t redEmphasis;
|
||||
uint8_t greenEmphasis;
|
||||
uint8_t blueEmphasis;
|
||||
};
|
||||
|
||||
struct PpuRegisterStatus
|
||||
{
|
||||
void SetByte(uint8_t byte)
|
||||
{
|
||||
vblankStarted = (byte & 0x80u) >> 7u;
|
||||
sprite0Hit = (byte & 0x40u) >> 6u;
|
||||
spriteOverflow = (byte & 0x20u) >> 5u;
|
||||
previousLsb = byte & 0x1Fu;
|
||||
}
|
||||
|
||||
uint8_t GetByte()
|
||||
{
|
||||
uint8_t byte = vblankStarted << 7u | sprite0Hit << 6u | spriteOverflow << 5u | previousLsb;
|
||||
vblankStarted = 0u;
|
||||
return byte;
|
||||
}
|
||||
|
||||
uint8_t vblankStarted;
|
||||
uint8_t sprite0Hit;
|
||||
uint8_t spriteOverflow;
|
||||
uint8_t previousLsb;
|
||||
};
|
Loading…
Reference in New Issue