2022-02-28 15:04:25 +00:00
|
|
|
#include "PPU.hpp"
|
|
|
|
#include "Log.hpp"
|
|
|
|
#include "Bus.hpp"
|
|
|
|
|
|
|
|
PPU::PPU(Bus* bus) :
|
|
|
|
bus(bus), ppuctrl{0}, ppustatus{0}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::Powerup()
|
|
|
|
{
|
|
|
|
ppuctrl.Raw = 0b00000000;
|
|
|
|
ppumask.Raw = 0b00000000;
|
|
|
|
ppustatus.Raw = 0b10100000;
|
|
|
|
ppuscroll.x = 0x00;
|
|
|
|
ppuscroll.y = 0x00;
|
|
|
|
ppuaddr.Raw = 0x0000;
|
|
|
|
|
2022-03-02 13:59:42 +00:00
|
|
|
// This will cause the Tick() functioon to set both to 0 on the first tick
|
|
|
|
x = 400;
|
|
|
|
y = 400;
|
|
|
|
fineX = 0;
|
2022-02-28 15:04:25 +00:00
|
|
|
addressLatch = 0;
|
2022-03-02 13:59:42 +00:00
|
|
|
|
|
|
|
scanlineType = ScanlineType::Visible;
|
|
|
|
cycleType = CycleType::Idle;
|
|
|
|
fetchPhase = FetchingPhase::NametableByte;
|
2022-02-28 15:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::Reset()
|
|
|
|
{
|
|
|
|
ppuctrl.Raw = 0b00000000;
|
|
|
|
ppumask.Raw = 0b00000000;
|
|
|
|
ppuscroll.x = 0x00;
|
|
|
|
ppuscroll.y = 0x00;
|
|
|
|
|
2022-03-02 13:59:42 +00:00
|
|
|
// This will cause the Tick() functioon to set both to 0 on the first tick
|
|
|
|
x = 400;
|
|
|
|
y = 400;
|
|
|
|
fineX = 0;
|
|
|
|
addressLatch = 0;
|
|
|
|
|
|
|
|
scanlineType = ScanlineType::Visible;
|
|
|
|
cycleType = CycleType::Idle;
|
|
|
|
fetchPhase = FetchingPhase::NametableByte;
|
2022-02-28 15:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::Tick()
|
|
|
|
{
|
2022-03-02 13:59:42 +00:00
|
|
|
// Advance pixel counters
|
|
|
|
x++;
|
|
|
|
if (x > 340)
|
|
|
|
{
|
|
|
|
x = 0;
|
|
|
|
y++;
|
|
|
|
if (y > 261)
|
|
|
|
y = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
current.CoarseX++;
|
|
|
|
if (current.CoarseX > 31)
|
|
|
|
{
|
|
|
|
current.CoarseX = 0;
|
|
|
|
current.NametableSel ^= 0x1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x == 256)
|
|
|
|
{
|
|
|
|
if (current.FineY < 7)
|
|
|
|
{
|
|
|
|
current.FineY++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
current.FineY = 0;
|
|
|
|
if (current.CoarseY == 29)
|
|
|
|
{
|
|
|
|
current.CoarseY = 0;
|
|
|
|
current.NametableSel ^= 0x2;
|
|
|
|
}
|
|
|
|
else if (current.CoarseY == 31)
|
|
|
|
{
|
|
|
|
current.CoarseY = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
current.CoarseY++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateState();
|
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
// On this cycle the VBlankStarted bit is set in the ppustatus
|
2022-02-28 15:04:25 +00:00
|
|
|
if (y == 241 && x == 1)
|
|
|
|
{
|
2022-02-28 16:14:32 +00:00
|
|
|
// Set flag and send NMI if necessary
|
2022-02-28 15:04:25 +00:00
|
|
|
ppustatus.Flag.VBlankStarted = 1;
|
|
|
|
if (ppuctrl.Flag.VBlankNMI)
|
|
|
|
bus->NMI();
|
|
|
|
|
|
|
|
isFrameDone = true;
|
|
|
|
}
|
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
// This cycle resets the VBlankStarted flag
|
2022-02-28 15:04:25 +00:00
|
|
|
if (y == 261 && x == 1)
|
|
|
|
ppustatus.Flag.VBlankStarted = 0;
|
|
|
|
|
2022-03-02 13:59:42 +00:00
|
|
|
|
|
|
|
// Need to render
|
|
|
|
if (scanlineType == ScanlineType::Visible || scanlineType == ScanlineType::PreRender)
|
2022-02-28 15:04:25 +00:00
|
|
|
{
|
2022-03-02 13:59:42 +00:00
|
|
|
PerformRenderAction();
|
2022-02-28 15:04:25 +00:00
|
|
|
}
|
2022-03-02 13:59:42 +00:00
|
|
|
|
2022-02-28 15:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Byte PPU::ReadRegister(Byte id)
|
|
|
|
{
|
2022-02-28 16:14:32 +00:00
|
|
|
// Reading from a register fills the latch with the contents of the register
|
|
|
|
// Write-only regs don't fill the latch
|
|
|
|
// But in any case, the latch contents are returned
|
2022-02-28 15:04:25 +00:00
|
|
|
switch (id)
|
|
|
|
{
|
|
|
|
case 0:
|
2022-02-28 16:14:32 +00:00
|
|
|
latch = ppuctrl.Raw;
|
2022-02-28 15:04:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
2022-02-28 16:14:32 +00:00
|
|
|
latch = ppumask.Raw;
|
2022-02-28 15:04:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
2022-02-28 16:14:32 +00:00
|
|
|
latch = ppustatus.Raw;
|
2022-02-28 15:04:25 +00:00
|
|
|
ppustatus.Flag.VBlankStarted = 0;
|
|
|
|
addressLatch = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 5:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 6:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 7:
|
2022-02-28 16:14:32 +00:00
|
|
|
latch = bus->ReadPPU(ppuaddr.Raw);
|
2022-02-28 15:04:25 +00:00
|
|
|
ppuaddr.Raw += (ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
LOG_CORE_WARN("Tried to read unimplemented PPU register $20{0:02X}", (Word)id);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
return latch;
|
2022-02-28 15:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::WriteRegister(Byte id, Byte val)
|
|
|
|
{
|
|
|
|
switch (id)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
ppuctrl.Raw = val;
|
2022-03-02 13:59:42 +00:00
|
|
|
temporary.NametableSel = ppuctrl.Flag.BaseNametableAddr;
|
2022-02-28 15:04:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
ppumask.Raw = val;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
ppustatus.Raw = val;
|
|
|
|
break;
|
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
// PPUADDR and PPUSCROLL both take 2 accesses to fully set
|
|
|
|
// When writing to them the address latch is switched. The latch
|
|
|
|
// determines whether the hi or lo byte should be written next
|
2022-02-28 15:04:25 +00:00
|
|
|
case 5:
|
|
|
|
if (addressLatch == 0)
|
2022-03-02 13:59:42 +00:00
|
|
|
{
|
2022-02-28 15:04:25 +00:00
|
|
|
ppuscroll.x = val;
|
2022-03-02 13:59:42 +00:00
|
|
|
temporary.CoarseX = (val >> 3);
|
|
|
|
fineX = val & 0x3;
|
|
|
|
}
|
2022-02-28 15:04:25 +00:00
|
|
|
else
|
2022-03-02 13:59:42 +00:00
|
|
|
{
|
2022-02-28 15:04:25 +00:00
|
|
|
ppuscroll.y = val;
|
2022-03-02 13:59:42 +00:00
|
|
|
temporary.CoarseY = (val >> 3);
|
|
|
|
temporary.FineY = val & 0x3;
|
|
|
|
}
|
2022-02-28 15:04:25 +00:00
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
addressLatch = !addressLatch;
|
2022-02-28 15:04:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 6:
|
|
|
|
if (addressLatch == 0)
|
2022-03-02 13:59:42 +00:00
|
|
|
{
|
2022-02-28 15:04:25 +00:00
|
|
|
ppuaddr.Bytes.hi = val;
|
2022-03-02 13:59:42 +00:00
|
|
|
temporary.Raw &= 0xFF;
|
|
|
|
temporary.Raw |= ((Word)(val & 0x3F) << 8);
|
|
|
|
temporary.FineY &= 0x3;
|
|
|
|
}
|
2022-02-28 15:04:25 +00:00
|
|
|
else
|
2022-03-02 13:59:42 +00:00
|
|
|
{
|
2022-02-28 15:04:25 +00:00
|
|
|
ppuaddr.Bytes.lo = val;
|
2022-03-02 13:59:42 +00:00
|
|
|
temporary.Raw &= ((Word)0xFF << 8);
|
|
|
|
temporary.Raw |= val;
|
|
|
|
|
|
|
|
current.Raw = temporary.Raw;
|
|
|
|
}
|
2022-02-28 15:04:25 +00:00
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
addressLatch = !addressLatch;
|
2022-02-28 15:04:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 7:
|
|
|
|
bus->WritePPU(ppuaddr.Raw, val);
|
|
|
|
ppuaddr.Raw += (ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
LOG_CORE_WARN("Tried to write unimplemented PPU register $20{0:02X}", (Word)id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ppustatus.Flag.Unused = val & 0x1F;
|
|
|
|
}
|
|
|
|
|
|
|
|
Byte PPU::Read(Word addr)
|
|
|
|
{
|
|
|
|
return bus->ReadPPU(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::Write(Word addr, Byte val)
|
|
|
|
{
|
|
|
|
bus->WritePPU(addr, val);
|
|
|
|
}
|
2022-03-02 13:59:42 +00:00
|
|
|
|
|
|
|
void PPU::UpdateState()
|
|
|
|
{
|
|
|
|
switch (y)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
scanlineType = ScanlineType::Visible;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 240:
|
|
|
|
scanlineType = ScanlineType::PostRender;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 241:
|
|
|
|
scanlineType = ScanlineType::VBlank;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 261:
|
|
|
|
scanlineType = ScanlineType::PreRender;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (x)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
cycleType = CycleType::Idle;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
cycleType = CycleType::Fetching;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 257:
|
|
|
|
cycleType = CycleType::SpriteFetching;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 321:
|
|
|
|
cycleType = CycleType::PreFetching;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 337:
|
|
|
|
cycleType = CycleType::UnknownFetching;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::PerformRenderAction()
|
|
|
|
{
|
|
|
|
if (cycleType == CycleType::Idle)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (memoryAccessLatch == 1)
|
|
|
|
{
|
|
|
|
switch (fetchPhase)
|
|
|
|
{
|
|
|
|
case FetchingPhase::NametableByte:
|
|
|
|
nametableByte = Read(0x2000 | (current.Raw & 0x0FFF));
|
|
|
|
|
|
|
|
if(cycleType != CycleType::UnknownFetching)
|
|
|
|
fetchPhase = FetchingPhase::AttributeTableByte;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FetchingPhase::AttributeTableByte:
|
|
|
|
attributeTableByte = Read(0x23C0 | (current.Raw & 0x0FFF) | ((current.Raw >> 4) & 0x38) | ((current.Raw >> 2) & 0x07));
|
|
|
|
fetchPhase = FetchingPhase::PatternTableLo;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FetchingPhase::PatternTableLo:
|
|
|
|
patternTableLo = Read(ppuctrl.Flag.BackgrPatternTableAddr | nametableByte);
|
|
|
|
fetchPhase = FetchingPhase::PatternTableHi;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FetchingPhase::PatternTableHi:
|
|
|
|
patternTableLo = Read((ppuctrl.Flag.BackgrPatternTableAddr | nametableByte) + 8);
|
|
|
|
fetchPhase = FetchingPhase::NametableByte;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memoryAccessLatch = 1 - memoryAccessLatch;
|
|
|
|
}
|