#include "PPU.hpp" #include "NES.hpp" const unsigned int tilesPerWidth = 32u; const unsigned int tilesPerHeight = 30u; unsigned char reverse(uint8_t b) { b = (b & 0xF0u) >> 4u | (b & 0x0Fu) << 4u; b = (b & 0xCCu) >> 2u | (b & 0x33u) << 2u; b = (b & 0xAAu) >> 1u | (b & 0x55u) << 1u; return b; } void PPU::Cycle(uint8_t cpuCyclesElapsed) { 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(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) { 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; } } } } else if (scanline == 241u && cycles == 0u) { ppuStatus.vblankStarted = 1u; if (ppuCtrl.nmiEnable) { nes->nmi = true; } } else if (scanline == 261u) { ppuStatus.sprite0Hit = 0u; ppuStatus.vblankStarted = 0; } ++cycles; if (cycles == 341u) { cycles = 0; ++scanline; if (scanline == 262u) { scanline = 0; } } } } void PPU::WriteRegister(PpuRegister reg, uint8_t value) { switch (reg) { case PpuRegister::PPUCTRL: { ppuCtrl.SetByte(value); regT |= (value & 0x3u) << 10u; break; } case PpuRegister::PPUMASK: { ppuMask.SetByte(value); break; } case PpuRegister::PPUSTATUS: { ppuStatus.SetByte(value); regW = 0u; break; } case PpuRegister::OAMADDR: { oamAddr = value; break; } case PpuRegister::OAMDATA: { oamData = value; break; } case PpuRegister::PPUSCROLL: { ppuScroll = value; 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; } break; } case PpuRegister::PPUADDR: { // MSB if (!ppuAddrW) { ppuAddr = value << 8u; ppuAddrW = true; } // LSB else { ppuAddr |= value; ppuAddrW = false; } if (regW == 0u) { regT |= (value & 0x3Fu) << 8u; regT &= 0x7Fu; regW = 1u; } else if (regW == 1u) { regT |= value; regV = regT; regW = 0u; } break; } case PpuRegister::PPUDATA: { WriteMemory(ppuAddr, value); ppuAddr += (ppuCtrl.vramAddrIncrement) ? 32u : 1u; break; } } } uint8_t PPU::ReadRegister(PpuRegister reg) { uint8_t byte{}; switch (reg) { case PpuRegister::PPUCTRL: { byte = ppuCtrl.GetByte(); break; } case PpuRegister::PPUMASK: { byte = ppuMask.GetByte(); break; } case PpuRegister::PPUSTATUS: { 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; break; } } return byte; } void PPU::WriteMemory(uint16_t address, uint8_t value) { nes->Write(BusSource::PPU, address, value); } uint8_t PPU::ReadMemory(uint16_t address) { return nes->Read(BusSource::PPU, address); }