GBA.Unity/Assets/emulator/PpuGba.cs

297 lines
11 KiB
C#

using static OptimeGBA.Bits;
using System.Runtime.CompilerServices;
using static OptimeGBA.PpuRenderer;
using System;
namespace OptimeGBA
{
public sealed unsafe class PpuGba
{
Gba Gba;
Scheduler Scheduler;
public PpuRenderer Renderer;
public PpuGba(Gba gba, Scheduler scheduler)
{
Gba = gba;
Scheduler = scheduler;
Renderer = new PpuRenderer(null, 240, 160);
Scheduler.AddEventRelative(SchedulerId.Ppu, 960, EndDrawingToHblank);
/*
⠀⠀⠀⡯⡯⡾⠝⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢊⠘⡮⣣⠪⠢⡑⡌
⠀⠀⠀⠟⠝⠈⠀⠀⠀⠡⠀⠠⢈⠠⢐⢠⢂⢔⣐⢄⡂⢔⠀⡁⢉⠸⢨⢑⠕⡌
⠀⠀⡀⠁⠀⠀⠀⡀⢂⠡⠈⡔⣕⢮⣳⢯⣿⣻⣟⣯⣯⢷⣫⣆⡂⠀⠀⢐⠑⡌
⢀⠠⠐⠈⠀⢀⢂⠢⡂⠕⡁⣝⢮⣳⢽⡽⣾⣻⣿⣯⡯⣟⣞⢾⢜⢆⠀⡀⠀⠪
⣬⠂⠀⠀⢀⢂⢪⠨⢂⠥⣺⡪⣗⢗⣽⢽⡯⣿⣽⣷⢿⡽⡾⡽⣝⢎⠀⠀⠀⢡
⣿⠀⠀⠀⢂⠢⢂⢥⢱⡹⣪⢞⡵⣻⡪⡯⡯⣟⡾⣿⣻⡽⣯⡻⣪⠧⠑⠀⠁⢐
⣿⠀⠀⠀⠢⢑⠠⠑⠕⡝⡎⡗⡝⡎⣞⢽⡹⣕⢯⢻⠹⡹⢚⠝⡷⡽⡨⠀⠀⢔
⣿⡯⠀⢈⠈⢄⠂⠂⠐⠀⠌⠠⢑⠱⡱⡱⡑⢔⠁⠀⡀⠐⠐⠐⡡⡹⣪⠀⠀⢘
⣿⣽⠀⡀⡊⠀⠐⠨⠈⡁⠂⢈⠠⡱⡽⣷⡑⠁⠠⠑⠀⢉⢇⣤⢘⣪⢽⠀⢌⢎
⣿⢾⠀⢌⠌⠀⡁⠢⠂⠐⡀⠀⢀⢳⢽⣽⡺⣨⢄⣑⢉⢃⢭⡲⣕⡭⣹⠠⢐⢗
⣿⡗⠀⠢⠡⡱⡸⣔⢵⢱⢸⠈⠀⡪⣳⣳⢹⢜⡵⣱⢱⡱⣳⡹⣵⣻⢔⢅⢬⡷
⣷⡇⡂⠡⡑⢕⢕⠕⡑⠡⢂⢊⢐⢕⡝⡮⡧⡳⣝⢴⡐⣁⠃⡫⡒⣕⢏⡮⣷⡟
⣷⣻⣅⠑⢌⠢⠁⢐⠠⠑⡐⠐⠌⡪⠮⡫⠪⡪⡪⣺⢸⠰⠡⠠⠐⢱⠨⡪⡪⡰
⣯⢷⣟⣇⡂⡂⡌⡀⠀⠁⡂⠅⠂⠀⡑⡄⢇⠇⢝⡨⡠⡁⢐⠠⢀⢪⡐⡜⡪⡊
⣿⢽⡾⢹⡄⠕⡅⢇⠂⠑⣴⡬⣬⣬⣆⢮⣦⣷⣵⣷⡗⢃⢮⠱⡸⢰⢱⢸⢨⢌
⣯⢯⣟⠸⣳⡅⠜⠔⡌⡐⠈⠻⠟⣿⢿⣿⣿⠿⡻⣃⠢⣱⡳⡱⡩⢢⠣⡃⠢⠁
⡯⣟⣞⡇⡿⣽⡪⡘⡰⠨⢐⢀⠢⢢⢄⢤⣰⠼⡾⢕⢕⡵⣝⠎⢌⢪⠪⡘⡌⠀
⡯⣳⠯⠚⢊⠡⡂⢂⠨⠊⠔⡑⠬⡸⣘⢬⢪⣪⡺⡼⣕⢯⢞⢕⢝⠎⢻⢼⣀⠀
⠁⡂⠔⡁⡢⠣⢀⠢⠀⠅⠱⡐⡱⡘⡔⡕⡕⣲⡹⣎⡮⡏⡑⢜⢼⡱⢩⣗⣯⣟
⢀⢂⢑⠀⡂⡃⠅⠊⢄⢑⠠⠑⢕⢕⢝⢮⢺⢕⢟⢮⢊⢢⢱⢄⠃⣇⣞⢞⣞⢾
⢀⠢⡑⡀⢂⢊⠠⠁⡂⡐⠀⠅⡈⠪⠪⠪⠣⠫⠑⡁⢔⠕⣜⣜⢦⡰⡎⡯⡾⡽
*/
if (Gba.Provider.BootBios)
{
PrideMode = true;
// 250 frames
Scheduler.AddEventRelative(SchedulerId.None, 70224000, DisablePrideMode);
// 120 frames
Scheduler.AddEventRelative(SchedulerId.None, 33707520, EnablePrideModeLayer2);
}
}
public byte[] Vram = new byte[98304];
public long ScanlineStartCycles;
public bool PrideMode = false;
public bool PrideModeLayer2 = false;
public ushort DISPCNTValue;
// DISPSTAT
public bool VCounterMatch;
public bool VBlankIrqEnable;
public bool HBlankIrqEnable;
public bool VCounterIrqEnable;
public byte VCountSetting;
// State
public uint VCount;
public void DisablePrideMode(long cyclesLate)
{
PrideMode = false;
}
public void EnablePrideModeLayer2(long cyclesLate)
{
PrideModeLayer2 = true;
}
public long GetScanlineCycles()
{
return Scheduler.CurrentTicks - ScanlineStartCycles;
}
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000000: // DISPCNT B0
return (byte)(DISPCNTValue >> 0);
case 0x4000001: // DISPCNT B1
return (byte)(DISPCNTValue >> 8);
case 0x4000004: // DISPSTAT B0
// Vblank flag is set in scanlines 160-226, not including 227 for some reason
if (VCount >= 160 && VCount <= 226) val = BitSet(val, 0);
// Hblank flag is set at cycle 1006, not cycle 960
if (GetScanlineCycles() >= 1006) val = BitSet(val, 1);
if (VCounterMatch) val = BitSet(val, 2);
if (VBlankIrqEnable) val = BitSet(val, 3);
if (HBlankIrqEnable) val = BitSet(val, 4);
if (VCounterIrqEnable) val = BitSet(val, 5);
break;
case 0x4000005: // DISPSTAT B1
val |= VCountSetting;
break;
case 0x4000006: // VCOUNT B0 - B1 only exists for Nintendo DS
val |= (byte)VCount;
break;
case 0x4000007:
return 0;
default:
return Renderer.ReadHwio8(addr & 0xFF);
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
switch (addr)
{
case 0x4000000: // DISPCNT B0
Renderer.BgMode = (uint)(val & 0b111);
Renderer.CgbMode = BitTest(val, 3);
Renderer.DisplayFrameSelect = BitTest(val, 4);
Renderer.HBlankIntervalFree = BitTest(val, 5);
Renderer.ObjCharOneDimensional = BitTest(val, 6);
Renderer.ForcedBlank = BitTest(val, 7);
DISPCNTValue &= 0xFF00;
DISPCNTValue |= (ushort)(val << 0);
Renderer.BackgroundSettingsDirty = true;
return;
case 0x4000001: // DISPCNT B1
Renderer.ScreenDisplayBg[0] = BitTest(val, 8 - 8);
Renderer.ScreenDisplayBg[1] = BitTest(val, 9 - 8);
Renderer.ScreenDisplayBg[2] = BitTest(val, 10 - 8);
Renderer.ScreenDisplayBg[3] = BitTest(val, 11 - 8);
Renderer.ScreenDisplayObj = BitTest(val, 12 - 8);
Renderer.Window0DisplayFlag = BitTest(val, 13 - 8);
Renderer.Window1DisplayFlag = BitTest(val, 14 - 8);
Renderer.ObjWindowDisplayFlag = BitTest(val, 15 - 8);
Renderer.AnyWindowEnabled = (val & 0b11100000) != 0;
DISPCNTValue &= 0x00FF;
DISPCNTValue |= (ushort)(val << 8);
Renderer.BackgroundSettingsDirty = true;
return;
case 0x4000004: // DISPSTAT B0
VBlankIrqEnable = BitTest(val, 3);
HBlankIrqEnable = BitTest(val, 4);
VCounterIrqEnable = BitTest(val, 5);
return;
case 0x4000005: // DISPSTAT B1
VCountSetting = val;
return;
default:
Renderer.WriteHwio8(addr & 0xFF, val);
return;
}
}
public void EndDrawingToHblank(long cyclesLate)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 272 - cyclesLate, EndHblank);
if (HBlankIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.HBlank);
}
if (Renderer.DebugEnableRendering)
{
Renderer.RenderScanlineGba(VCount, Vram);
}
Renderer.IncrementMosaicCounters();
Gba.Dma.RepeatHblank();
}
public void EndVblankToHblank(long cyclesLate)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 272 - cyclesLate, EndHblank);
if (HBlankIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.HBlank);
}
}
public void EndHblank(long cyclesLate)
{
ScanlineStartCycles = Scheduler.CurrentTicks;
if (VCount != 227)
{
VCount++;
if (VCount > 159)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndVblankToHblank);
if (VCount == 160)
{
#if DS_RESOLUTION
while (VCount < HEIGHT) {
RenderScanline();
Renderer.IncrementMosaicCounters();
VCount++;
}
VCount = 160;
#endif
Gba.Dma.RepeatVblank();
Renderer.RunVblankOperations();
if (VBlankIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.VBlank);
}
Renderer.TotalFrames++;
if (Renderer.DebugEnableRendering) Renderer.SwapBuffers();
Renderer.RenderingDone = true;
}
}
else
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndDrawingToHblank);
}
}
else
{
if (PrideMode)
{
uint objE0 = 8 * 3;
uint objE1 = 8 * 19;
uint objM0 = 8 * 4;
uint objM1 = 8 * 20;
for (uint i = 0; i < 6; i++)
{
Renderer.Oam[objE0++] = 0;
Renderer.Oam[objE1++] = 0;
}
Renderer.Oam[objM0 + 4] = 68;
Renderer.Oam[objM0 + 5] |= 2;
if (PrideModeLayer2)
{
Renderer.Oam[objM1 + 4] = 68;
Renderer.Oam[objM1 + 5] |= 2;
}
}
VCount = 0;
VCounterMatch = VCount == VCountSetting;
if (VCounterMatch && VCounterIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.VCounterMatch);
}
Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndDrawingToHblank);
// Pre-render sprites for line zero
fixed (byte* vram = Vram)
{
if (Renderer.DebugEnableObj && Renderer.ScreenDisplayObj) Renderer.RenderObjs(0, vram);
}
}
VCounterMatch = VCount == VCountSetting;
if (VCounterMatch && VCounterIrqEnable)
{
Gba.HwControl.FlagInterrupt((uint)InterruptGba.VCounterMatch);
}
}
}
}