297 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|