1
0
Fork 0
2019-nes-emulator/Source/PPU/PPU.cpp

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);
}