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