|
|
|
@ -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,314 +15,689 @@ 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); |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}; |
|
|
|
|
// Incrementing Hori(V)
|
|
|
|
|
for (int cycle = 8; cycle <= 256; cycle += 8) |
|
|
|
|
{ |
|
|
|
|
events[scanline][cycle] |= Event::INCREMENT_TILE_X; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Which quadrant of block are we in?
|
|
|
|
|
uint8_t blockX = (bgTilePositionX % 4u) / 2; |
|
|
|
|
uint8_t blockY = (bgTilePositionY % 4u) / 2; |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint8_t pIndex = paletteIndex[(blockY << 1u) | blockX]; |
|
|
|
|
// Inc Vert(v)
|
|
|
|
|
events[scanline][256] |= Event::INCREMENT_TILE_Y; |
|
|
|
|
|
|
|
|
|
uint8_t bgTileIndex = ram[bgTilePositionY * tilesPerWidth + bgTilePositionX]; |
|
|
|
|
uint8_t bgPixelWithinTileX = cycles % 8u; |
|
|
|
|
uint8_t bgPixelWithinTileY = scanline % 8u; |
|
|
|
|
uint16_t bgTileAddress = (0x1000u | (bgTileIndex << 4u)) + bgPixelWithinTileY; |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int screenPixelX = (bgTilePositionX * 8) + bgPixelWithinTileX; |
|
|
|
|
int screenPixelY = (bgTilePositionY * 8) + bgPixelWithinTileY; |
|
|
|
|
// Shift
|
|
|
|
|
for (int cycle = 322; cycle <= 337; ++cycle) |
|
|
|
|
{ |
|
|
|
|
events[scanline][cycle] |= Event::SHIFT_BACKGROUND; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint8_t bgPixelColorLsb = |
|
|
|
|
(ReadMemory(bgTileAddress) & (1u << (7u - bgPixelWithinTileX))) >> (7u - bgPixelWithinTileX); |
|
|
|
|
uint8_t bgPixelColorMsb = |
|
|
|
|
(ReadMemory(bgTileAddress + 8) & (1u << (7u - bgPixelWithinTileX))) >> (7u - bgPixelWithinTileX); |
|
|
|
|
// Reload shift registers
|
|
|
|
|
for (int cycle = 329; cycle <= 337; cycle += 8) |
|
|
|
|
{ |
|
|
|
|
events[scanline][cycle] |= Event::LOAD_BACKGROUND; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint8_t bgPixelColor = bgPixelColorMsb << 1u | bgPixelColorLsb; |
|
|
|
|
events[scanline][328] |= Event::INCREMENT_TILE_X; |
|
|
|
|
events[scanline][336] |= Event::INCREMENT_TILE_X; |
|
|
|
|
|
|
|
|
|
uint8_t systemPaletteIndex = paletteIndexes[(4 * pIndex) + bgPixelColor]; |
|
|
|
|
// Unused Fetches
|
|
|
|
|
events[scanline][337] |= Event::FETCH_NT; |
|
|
|
|
events[scanline][339] |= Event::FETCH_NT; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint32_t color = nes->palette[systemPaletteIndex].GetValue(); |
|
|
|
|
// VBlank
|
|
|
|
|
{ |
|
|
|
|
events[241][1] |= Event::SET_VBLANK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
video[screenPixelY * VIDEO_WIDTH + screenPixelX] = color; |
|
|
|
|
// Pre-render
|
|
|
|
|
{ |
|
|
|
|
// Clear VBlank, Sprite0 Hit, and Sprite Overflow
|
|
|
|
|
events[261][1] |= Event::CLEAR_FLAGS; |
|
|
|
|
|
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Data fetching
|
|
|
|
|
for (int cycle = 1; cycle <= 256; 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; |
|
|
|
|
} |
|
|
|
|
else if (scanline == 241u && cycles == 0u) |
|
|
|
|
|
|
|
|
|
// Incrementing Hori(V)
|
|
|
|
|
for (int cycle = 8; cycle <= 256; cycle += 8) |
|
|
|
|
{ |
|
|
|
|
ppuStatus.vblankStarted = 1u; |
|
|
|
|
events[261][cycle] |= Event::INCREMENT_TILE_X; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (ppuCtrl.nmiEnable) |
|
|
|
|
{ |
|
|
|
|
nes->nmi = true; |
|
|
|
|
} |
|
|
|
|
// Shift
|
|
|
|
|
for (int cycle = 2; cycle <= 257; ++cycle) |
|
|
|
|
{ |
|
|
|
|
events[261][cycle] |= Event::SHIFT_BACKGROUND; |
|
|
|
|
} |
|
|
|
|
else if (scanline == 261u) |
|
|
|
|
|
|
|
|
|
// Reload shift registers
|
|
|
|
|
for (int cycle = 9; cycle <= 257; cycle += 8) |
|
|
|
|
{ |
|
|
|
|
ppuStatus.sprite0Hit = 0u; |
|
|
|
|
ppuStatus.vblankStarted = 0; |
|
|
|
|
events[261][cycle] |= Event::LOAD_BACKGROUND; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
++cycles; |
|
|
|
|
// Inc Vert(v)
|
|
|
|
|
events[261][256] |= Event::INCREMENT_TILE_Y; |
|
|
|
|
|
|
|
|
|
// Hori(v) = Hori(t)
|
|
|
|
|
events[261][257] |= Event::SET_TILE_X; |
|
|
|
|
|
|
|
|
|
if (cycles == 341u) |
|
|
|
|
// Vert(v) = Vert(t)
|
|
|
|
|
for (int cycle = 280; cycle <= 304; ++cycle) |
|
|
|
|
{ |
|
|
|
|
cycles = 0; |
|
|
|
|
events[261][cycle] |= Event::SET_TILE_Y; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
++scanline; |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (scanline == 262u) |
|
|
|
|
{ |
|
|
|
|
scanline = 0; |
|
|
|
|
} |
|
|
|
|
// 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::WriteRegister(PpuRegister reg, uint8_t value) |
|
|
|
|
void PPU::Tick() |
|
|
|
|
{ |
|
|
|
|
switch (reg) |
|
|
|
|
int event = events[currentScanline][currentCycle]; |
|
|
|
|
|
|
|
|
|
if (CheckBit(event, Event::DRAW)) |
|
|
|
|
{ |
|
|
|
|
case PpuRegister::PPUCTRL: |
|
|
|
|
{ |
|
|
|
|
ppuCtrl.SetByte(value); |
|
|
|
|
regT |= (value & 0x3u) << 10u; |
|
|
|
|
Draw(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::PPUMASK: |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void PPU::WriteRegister(Register reg, uint8_t value) |
|
|
|
|
{ |
|
|
|
|
switch (reg) |
|
|
|
|
{ |
|
|
|
|
case Register::PPUCTRL: |
|
|
|
|
{ |
|
|
|
|
ppuMask.SetByte(value); |
|
|
|
|
regPPUCTRL.SetByte(value); |
|
|
|
|
|
|
|
|
|
// yyy NNYY YYYX XXXX
|
|
|
|
|
// t: --- BA-- ---- ---- = d: ---- --BA
|
|
|
|
|
tempAddress.nametableY = (value & 0x2u) >> 1u; |
|
|
|
|
tempAddress.nametableX = (value & 0x1u) >> 1u; |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::PPUSTATUS: |
|
|
|
|
|
|
|
|
|
case Register::PPUMASK: |
|
|
|
|
{ |
|
|
|
|
ppuStatus.SetByte(value); |
|
|
|
|
regW = 0u; |
|
|
|
|
regPPUMASK.SetByte(value); |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::OAMADDR: |
|
|
|
|
|
|
|
|
|
case Register::OAMADDR: |
|
|
|
|
{ |
|
|
|
|
oamAddr = value; |
|
|
|
|
regOAMADDR = value; |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::OAMDATA: |
|
|
|
|
|
|
|
|
|
case Register::OAMDATA: |
|
|
|
|
{ |
|
|
|
|
oamData = value; |
|
|
|
|
oam[regOAMADDR] = value; |
|
|
|
|
|
|
|
|
|
++regOAMADDR; |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::PPUSCROLL: |
|
|
|
|
{ |
|
|
|
|
ppuScroll = value; |
|
|
|
|
|
|
|
|
|
if (regW == 0u) |
|
|
|
|
case Register::PPUSCROLL: |
|
|
|
|
{ |
|
|
|
|
if (firstWrite) |
|
|
|
|
{ |
|
|
|
|
regT |= (value & 0xF8u) >> 3u; |
|
|
|
|
regX = value & 0x07u; |
|
|
|
|
|
|
|
|
|
regW = 1u; |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
else if (regW == 1u) |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
regT |= (value & 0x07u) << 12u; |
|
|
|
|
regT |= (value & 0x38u) << 2u; |
|
|
|
|
regT |= (value & 0xC0u) << 2u; |
|
|
|
|
// yyy NNYY YYYX XXXX
|
|
|
|
|
// t: CBA --HG FED- ---- = d: HGFE DCBA
|
|
|
|
|
// w: = 0
|
|
|
|
|
tempAddress.scrollCoarseY = (value & 0xF8u) >> 3u; |
|
|
|
|
tempAddress.scrollFineY = value & 0x7u; |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
tempAddress.nametableY = (value & 0x8u) >> 3u; |
|
|
|
|
tempAddress.nametableX = (value & 0x4u) >> 2u; |
|
|
|
|
|
|
|
|
|
ppuAddrW = true; |
|
|
|
|
tempAddress.scrollFineY = (value & 0x30u) >> 4u; |
|
|
|
|
tempAddress.scrollFineY &= ~(0x4u); |
|
|
|
|
|
|
|
|
|
firstWrite = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// LSB
|
|
|
|
|
// 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; |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::OAMADDR: |
|
|
|
|
{ |
|
|
|
|
byte = oamAddr; |
|
|
|
|
default: 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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::OAMDATA: |
|
|
|
|
{ |
|
|
|
|
byte = oamData; |
|
|
|
|
|
|
|
|
|
// Top Right
|
|
|
|
|
case 1: |
|
|
|
|
{ |
|
|
|
|
attributeTableLatch = (at & 0x0Cu) >> 2u; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::PPUSCROLL: |
|
|
|
|
{ |
|
|
|
|
byte = ppuScroll; |
|
|
|
|
|
|
|
|
|
// Bottom Left
|
|
|
|
|
case 2: |
|
|
|
|
{ |
|
|
|
|
attributeTableLatch = (at & 0x30u) >> 4u; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::PPUADDR: |
|
|
|
|
{ |
|
|
|
|
byte = ppuAddr; |
|
|
|
|
|
|
|
|
|
// Bottom Right
|
|
|
|
|
case 3: |
|
|
|
|
{ |
|
|
|
|
attributeTableLatch = (at & 0xC0u) >> 6u; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case PpuRegister::PPUDATA: |
|
|
|
|
{ |
|
|
|
|
byte = ppuData; |
|
|
|
|
|
|
|
|
|
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;} |
|
|
|
|
|