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;
|
|
|
|
|
|
|
|
x = 0;
|
|
|
|
y = 0;
|
|
|
|
addressLatch = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::Reset()
|
|
|
|
{
|
|
|
|
ppuctrl.Raw = 0b00000000;
|
|
|
|
ppumask.Raw = 0b00000000;
|
|
|
|
ppuscroll.x = 0x00;
|
|
|
|
ppuscroll.y = 0x00;
|
|
|
|
|
|
|
|
x = 0;
|
|
|
|
y = 0;
|
|
|
|
addressLatch = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PPU::Tick()
|
|
|
|
{
|
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-02-28 16:14:32 +00:00
|
|
|
// Advance pixel counters
|
2022-02-28 15:04:25 +00:00
|
|
|
x++;
|
|
|
|
if (x > 340)
|
|
|
|
{
|
|
|
|
x = 0;
|
|
|
|
y++;
|
|
|
|
if (y > 261)
|
|
|
|
y = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
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)
|
|
|
|
ppuscroll.x = val;
|
|
|
|
else
|
|
|
|
ppuscroll.y = val;
|
|
|
|
|
2022-02-28 16:14:32 +00:00
|
|
|
addressLatch = !addressLatch;
|
2022-02-28 15:04:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 6:
|
|
|
|
if (addressLatch == 0)
|
|
|
|
ppuaddr.Bytes.hi = val;
|
|
|
|
else
|
|
|
|
ppuaddr.Bytes.lo = val;
|
|
|
|
|
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);
|
|
|
|
}
|