From ac1ce83de3408f3206181a485faba35298fd50f1 Mon Sep 17 00:00:00 2001 From: Austin Morlan Date: Thu, 12 Sep 2019 09:06:18 -0700 Subject: [PATCH] PPU cycle-accurate rendering --- CMakeLists.txt | 1 - Source/CPU/AddressModes.cpp | 14 - Source/CPU/CPU.cpp | 8 +- Source/CPU/CPU.hpp | 4 +- Source/CPU/Instructions.cpp | 52 +++ Source/Main.cpp | 2 +- Source/NES.cpp | 48 ++- Source/PPU/PPU.cpp | 825 ++++++++++++++++++++++++++---------- Source/PPU/PPU.hpp | 154 ++++++- Source/PPU/Registers.hpp | 98 ----- 10 files changed, 825 insertions(+), 381 deletions(-) delete mode 100644 Source/PPU/Registers.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f9f50e2..b8f35b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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( diff --git a/Source/CPU/AddressModes.cpp b/Source/CPU/AddressModes.cpp index 1925c95..fc59dc4 100644 --- a/Source/CPU/AddressModes.cpp +++ b/Source/CPU/AddressModes.cpp @@ -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); } diff --git a/Source/CPU/CPU.cpp b/Source/CPU/CPU.cpp index fb93ce6..da6d956 100644 --- a/Source/CPU/CPU.cpp +++ b/Source/CPU/CPU.cpp @@ -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) diff --git a/Source/CPU/CPU.hpp b/Source/CPU/CPU.hpp index 3d24c89..2e54bb4 100644 --- a/Source/CPU/CPU.hpp +++ b/Source/CPU/CPU.hpp @@ -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{}; diff --git a/Source/CPU/Instructions.cpp b/Source/CPU/Instructions.cpp index de5d0ae..737cc4a 100644 --- a/Source/CPU/Instructions.cpp +++ b/Source/CPU/Instructions.cpp @@ -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; diff --git a/Source/Main.cpp b/Source/Main.cpp index 4ee06a1..49002eb 100644 --- a/Source/Main.cpp +++ b/Source/Main.cpp @@ -6,7 +6,7 @@ int main(int argc, char** argv) { if (argc != 2) { - std::cerr << "Usage: " << argv[0] << "\n"; + std::cerr << "Usage: " << argv[0] << " \n"; std::exit(EXIT_FAILURE); } diff --git a/Source/NES.cpp b/Source/NES.cpp index 744dd6e..912c99b 100644 --- a/Source/NES.cpp +++ b/Source/NES.cpp @@ -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()), ppu(std::make_unique()), @@ -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(address % 8u); + auto index = static_cast(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(address % 8u); + auto index = static_cast(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; diff --git a/Source/PPU/PPU.cpp b/Source/PPU/PPU.cpp index 1ed00a9..a8026ca 100644 --- a/Source/PPU/PPU.cpp +++ b/Source/PPU/PPU.cpp @@ -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) + return (value & bit) == bit; +} + +PPU::PPU() +{ + // Visible Frame { - if (scanline < 240u && cycles < 256u) + for (int scanline = 0; scanline <= 239; ++scanline) { - // 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(attribute & 0x03u), // Upper Left - static_cast((attribute & 0x0Cu) >> 2u), // Upper Right - static_cast((attribute & 0x30u) >> 4u), // Bottom Left - static_cast((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) + // Data fetching + for (int cycle = 1; cycle <= 256; cycle += 8) { - 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); - } - - 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) - { - if (sprite == 0) - { - if (bgPixelColor != 0u) - { - ppuStatus.sprite0Hit = 1u; - } - } - - video[pixelY * VIDEO_WIDTH + pixelX] = spriteColor; - } - } + 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; } - } - else if (scanline == 241u && cycles == 0u) - { - ppuStatus.vblankStarted = 1u; - if (ppuCtrl.nmiEnable) + // Incrementing Hori(V) + for (int cycle = 8; cycle <= 256; cycle += 8) { - nes->nmi = true; + 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; } - else if (scanline == 261u) + } + + // VBlank + { + events[241][1] |= Event::SET_VBLANK; + } + + // Pre-render + { + // 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; + // Shift + for (int cycle = 2; cycle <= 257; ++cycle) + { + events[261][cycle] |= Event::SHIFT_BACKGROUND; + } - if (scanline == 262u) - { - scanline = 0; - } + // 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 PpuRegister::PPUSCROLL: + case Register::PPUSCROLL: { - ppuScroll = value; - - if (regW == 0u) + if (firstWrite) { - regT |= (value & 0xF8u) >> 3u; - regX = value & 0x07u; + // yyy NNYY YYYX XXXX + // t: --- ---- ---H GFED = d: HGFE D--- + // x: CBA = d: ---- -CBA + // w: = 1 + tempAddress.scrollCoarseX = (value & 0xF8u) >> 3u; + scrollFineX = value & 0x7u; - regW = 1u; + firstWrite = false; } - else if (regW == 1u) - { - regT |= (value & 0x07u) << 12u; - regT |= (value & 0x38u) << 2u; - regT |= (value & 0xC0u) << 2u; - - regW = 0u; - } - - break; - } - case PpuRegister::PPUADDR: - { - // MSB - if (!ppuAddrW) - { - ppuAddr = value << 8u; - - ppuAddrW = true; - } - - // LSB else { - ppuAddr |= value; + // yyy NNYY YYYX XXXX + // t: CBA --HG FED- ---- = d: HGFE DCBA + // w: = 0 + tempAddress.scrollCoarseY = (value & 0xF8u) >> 3u; + tempAddress.scrollFineY = value & 0x7u; - ppuAddrW = false; - } - - if (regW == 0u) - { - regT |= (value & 0x3Fu) << 8u; - regT &= 0x7Fu; - regW = 1u; - } - else if (regW == 1u) - { - regT |= value; - regV = regT; - regW = 0u; + firstWrite = true; } break; } - case PpuRegister::PPUDATA: + + case Register::PPUADDR: { - WriteMemory(ppuAddr, value); + // MSB + if (firstWrite) + { + // yyy NNYY YYYX XXXX + // t: -FE DCBA ---- ---- = d: --FE DCBA + // t: X-- ---- ---- ---- = 0 + // w: = 1 + tempAddress.scrollCoarseY &= ~(0x18u); + tempAddress.scrollCoarseY |= (value & 0x3u) << 3u; - ppuAddr += (ppuCtrl.vramAddrIncrement) ? 32u : 1u; + tempAddress.nametableY = (value & 0x8u) >> 3u; + tempAddress.nametableX = (value & 0x4u) >> 2u; + + tempAddress.scrollFineY = (value & 0x30u) >> 4u; + tempAddress.scrollFineY &= ~(0x4u); + + firstWrite = false; + } + + // LSB + else + { + // yyy NNYY YYYX XXXX + // t: ... .... HGFE DCBA = d: HGFE DCBA + // v = t + // w: = 0 + tempAddress.scrollCoarseY &= ~(0x7u); + tempAddress.scrollCoarseY |= (value & 0xE0u) >> 5u; + + tempAddress.scrollCoarseX = value & 0x1Fu; + + currentAddress = tempAddress; + + firstWrite = true; + } break; } + case Register::PPUDATA: + { + WriteMemory(currentAddress.GetValue(), value); + + 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; +} + diff --git a/Source/PPU/PPU.hpp b/Source/PPU/PPU.hpp index fa3d262..4266cf0 100644 --- a/Source/PPU/PPU.hpp +++ b/Source/PPU/PPU.hpp @@ -1,23 +1,40 @@ #pragma once -#include "Registers.hpp" #include +#include 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(); }; diff --git a/Source/PPU/Registers.hpp b/Source/PPU/Registers.hpp deleted file mode 100644 index 662af19..0000000 --- a/Source/PPU/Registers.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - -#include - - -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; -};