327 lines
6.8 KiB
C++
327 lines
6.8 KiB
C++
|
#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<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);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|