feat(video): add PPU register state and LCDC/STAT handling
This commit is contained in:
1 parent
b1f15a9436
commit
2c4b039ce4
2 files changed
+229
No files matched your search
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace GameBoy.Video
|
||||
{
|
||||
public sealed partial class Ppu
|
||||
{
|
||||
public void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(lcdc); writer.Write(statEnables);
|
||||
writer.Write(scy); writer.Write(scx);
|
||||
writer.Write(ly); writer.Write(lyc);
|
||||
writer.Write(bgp); writer.Write(obp0); writer.Write(obp1);
|
||||
writer.Write(wy); writer.Write(wx);
|
||||
writer.Write(dotCounter); writer.Write(mode); writer.Write(windowLine);
|
||||
writer.Write(statLine); writer.Write(FrameReady);
|
||||
writer.Write(vram);
|
||||
writer.Write(oam);
|
||||
writer.Write(Framebuffer);
|
||||
}
|
||||
|
||||
public void LoadState(BinaryReader reader)
|
||||
{
|
||||
lcdc = reader.ReadByte(); statEnables = reader.ReadByte();
|
||||
scy = reader.ReadByte(); scx = reader.ReadByte();
|
||||
ly = reader.ReadByte(); lyc = reader.ReadByte();
|
||||
bgp = reader.ReadByte(); obp0 = reader.ReadByte(); obp1 = reader.ReadByte();
|
||||
wy = reader.ReadByte(); wx = reader.ReadByte();
|
||||
dotCounter = reader.ReadInt32(); mode = reader.ReadInt32(); windowLine = reader.ReadInt32();
|
||||
statLine = reader.ReadBoolean(); FrameReady = reader.ReadBoolean();
|
||||
Array.Copy(reader.ReadBytes(vram.Length), vram, vram.Length);
|
||||
Array.Copy(reader.ReadBytes(oam.Length), oam, oam.Length);
|
||||
Array.Copy(reader.ReadBytes(Framebuffer.Length), Framebuffer, Framebuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using GameBoy.Cpu;
|
||||
|
||||
namespace GameBoy.Video
|
||||
{
|
||||
public sealed partial class Ppu
|
||||
{
|
||||
public const int ScreenWidth = 160;
|
||||
public const int ScreenHeight = 144;
|
||||
|
||||
private const int OamScanDots = 80;
|
||||
private const int DrawDots = 172;
|
||||
private const int DotsPerLine = 456;
|
||||
private const int VisibleLines = 144;
|
||||
private const int TotalLines = 154;
|
||||
|
||||
private readonly InterruptController interrupts;
|
||||
private readonly byte[] vram = new byte[0x2000];
|
||||
private readonly byte[] oam = new byte[0xA0];
|
||||
|
||||
public readonly byte[] Framebuffer = new byte[ScreenWidth * ScreenHeight];
|
||||
|
||||
private byte lcdc;
|
||||
private byte statEnables;
|
||||
private byte scy, scx;
|
||||
private byte ly, lyc;
|
||||
private byte bgp, obp0, obp1;
|
||||
private byte wy, wx;
|
||||
|
||||
private int dotCounter;
|
||||
private int mode = 2;
|
||||
private int windowLine;
|
||||
private bool statLine;
|
||||
|
||||
public bool FrameReady { get; private set; }
|
||||
|
||||
public Ppu(InterruptController interrupts)
|
||||
{
|
||||
this.interrupts = interrupts;
|
||||
}
|
||||
|
||||
public void ConsumeFrame() => FrameReady = false;
|
||||
|
||||
private bool LcdEnabled => (lcdc & 0x80) != 0;
|
||||
|
||||
public void Step()
|
||||
{
|
||||
if (!LcdEnabled)
|
||||
return;
|
||||
|
||||
dotCounter++;
|
||||
if (dotCounter == DotsPerLine)
|
||||
{
|
||||
dotCounter = 0;
|
||||
ly++;
|
||||
if (ly == TotalLines)
|
||||
{
|
||||
ly = 0;
|
||||
windowLine = 0;
|
||||
}
|
||||
|
||||
if (ly < VisibleLines)
|
||||
mode = 2;
|
||||
else if (ly == VisibleLines)
|
||||
{
|
||||
mode = 1;
|
||||
FrameReady = true;
|
||||
interrupts.Request(Interrupt.VBlank);
|
||||
}
|
||||
}
|
||||
else if (ly < VisibleLines)
|
||||
{
|
||||
if (dotCounter == OamScanDots)
|
||||
{
|
||||
mode = 3;
|
||||
RenderScanline();
|
||||
}
|
||||
else if (dotCounter == OamScanDots + DrawDots)
|
||||
{
|
||||
mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatInterrupt();
|
||||
}
|
||||
|
||||
private void UpdateStatInterrupt()
|
||||
{
|
||||
bool lycEqual = ly == lyc;
|
||||
bool line =
|
||||
(mode == 0 && (statEnables & 0x08) != 0) ||
|
||||
(mode == 2 && (statEnables & 0x20) != 0) ||
|
||||
(mode == 1 && (statEnables & 0x10) != 0) ||
|
||||
(lycEqual && (statEnables & 0x40) != 0);
|
||||
|
||||
if (line && !statLine)
|
||||
interrupts.Request(Interrupt.LcdStat);
|
||||
statLine = line;
|
||||
}
|
||||
|
||||
public byte ReadVram(ushort address)
|
||||
{
|
||||
if (LcdEnabled && mode == 3)
|
||||
return 0xFF;
|
||||
return vram[address - 0x8000];
|
||||
}
|
||||
|
||||
public void WriteVram(ushort address, byte value)
|
||||
{
|
||||
if (LcdEnabled && mode == 3)
|
||||
return;
|
||||
vram[address - 0x8000] = value;
|
||||
}
|
||||
|
||||
public byte ReadOam(ushort address)
|
||||
{
|
||||
if (LcdEnabled && (mode == 2 || mode == 3))
|
||||
return 0xFF;
|
||||
return oam[address - 0xFE00];
|
||||
}
|
||||
|
||||
public void WriteOam(ushort address, byte value)
|
||||
{
|
||||
if (LcdEnabled && (mode == 2 || mode == 3))
|
||||
return;
|
||||
oam[address - 0xFE00] = value;
|
||||
}
|
||||
|
||||
public void WriteOamDma(int index, byte value)
|
||||
{
|
||||
oam[index] = value;
|
||||
}
|
||||
|
||||
public byte ReadRegister(ushort address)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0xFF40: return lcdc;
|
||||
case 0xFF41: return (byte)(0x80 | statEnables | (ly == lyc ? 0x04 : 0x00) | mode);
|
||||
case 0xFF42: return scy;
|
||||
case 0xFF43: return scx;
|
||||
case 0xFF44: return ly;
|
||||
case 0xFF45: return lyc;
|
||||
case 0xFF47: return bgp;
|
||||
case 0xFF48: return obp0;
|
||||
case 0xFF49: return obp1;
|
||||
case 0xFF4A: return wy;
|
||||
case 0xFF4B: return wx;
|
||||
default: return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteRegister(ushort address, byte value)
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0xFF40:
|
||||
bool wasOn = LcdEnabled;
|
||||
lcdc = value;
|
||||
if (wasOn && !LcdEnabled)
|
||||
TurnOff();
|
||||
else if (!wasOn && LcdEnabled)
|
||||
TurnOn();
|
||||
break;
|
||||
case 0xFF41: statEnables = (byte)(value & 0x78); break;
|
||||
case 0xFF42: scy = value; break;
|
||||
case 0xFF43: scx = value; break;
|
||||
case 0xFF45: lyc = value; break;
|
||||
case 0xFF47: bgp = value; break;
|
||||
case 0xFF48: obp0 = value; break;
|
||||
case 0xFF49: obp1 = value; break;
|
||||
case 0xFF4A: wy = value; break;
|
||||
case 0xFF4B: wx = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TurnOff()
|
||||
{
|
||||
ly = 0;
|
||||
dotCounter = 0;
|
||||
mode = 0;
|
||||
windowLine = 0;
|
||||
statLine = false;
|
||||
}
|
||||
|
||||
private void TurnOn()
|
||||
{
|
||||
ly = 0;
|
||||
dotCounter = 0;
|
||||
mode = 2;
|
||||
windowLine = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user