GBA.Unity/Assets/emulator/PpuNds.cs

926 lines
37 KiB
C#

using static OptimeGBA.Bits;
using static OptimeGBA.MemoryUtil;
using System.Runtime.CompilerServices;
using System;
namespace OptimeGBA
{
public sealed unsafe class PpuNds
{
Nds Nds;
Scheduler Scheduler;
public PpuRenderer[] Renderers;
public PpuNds(Nds nds, Scheduler scheduler)
{
Nds = nds;
Scheduler = scheduler;
Renderers = new PpuRenderer[] {
new PpuRenderer(nds, 256, 192),
new PpuRenderer(nds, 256, 192)
};
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536, EndDrawingToHblank);
}
// Raw VRAM Blocks
public byte[] VramA = new byte[131072];
public byte[] VramB = new byte[131072];
public byte[] VramC = new byte[131072];
public byte[] VramD = new byte[131072];
public byte[] VramE = new byte[65536];
public byte[] VramF = new byte[16384];
public byte[] VramG = new byte[16384];
public byte[] VramH = new byte[32768];
public byte[] VramI = new byte[16384];
// Built arrays (Passed to PpuRenderer for rendering)
public byte[] VramLcdc = new byte[671744];
public byte[] VramBgA = new byte[524288];
public byte[] VramObjA = new byte[262144];
public byte[] VramBgB = new byte[131072];
public byte[] VramObjB = new byte[131072];
public bool DebugDisableVramUpdates;
public byte ReadVram8Arm9(uint addr)
{
switch (addr & 0xFFE00000)
{
case 0x06000000: // Engine A BG VRAM
return ReadVram8Arm9BgA(addr);
case 0x06200000: // Engine B BG VRAM
return ReadVram8Arm9BgB(addr);
case 0x06400000: // Engine A OBJ VRAM
return ReadVram8Arm9ObjA(addr);
case 0x06600000: // Engine B OBJ VRAM
return ReadVram8Arm9ObjB(addr);
case 0x06800000: // LCDC VRAM
return ReadVram8Arm9Lcdc(addr);
}
return 0;
}
public byte ReadVram8Arm9BgA(uint addr)
{
addr &= 0x1FFFFF;
byte val = 0;
uint offs = Nds.MemoryControl.GetOffset(0) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 1))
{
val |= VramA[addr & 0x1FFFF];
}
offs = Nds.MemoryControl.GetOffset(1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 1))
{
val |= VramB[addr & 0x1FFFF];
}
offs = Nds.MemoryControl.GetOffset(2) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 1))
{
val |= VramC[addr & 0x1FFFF];
}
offs = Nds.MemoryControl.GetOffset(3) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 1))
{
val |= VramD[addr & 0x1FFFF];
}
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 1))
{
val |= VramE[addr & 0xFFFF];
}
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 1))
{
val |= VramF[addr & 0x3FFF];
}
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 1))
{
val |= VramG[addr & 0x3FFF];
}
return val;
}
public byte ReadVram8Arm9BgB(uint addr)
{
byte val = 0;
addr &= 0x1FFFFF;
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(2, 4))
{
val |= VramC[addr & 0x1FFFF];
}
if (addr < 0x8000 && Nds.MemoryControl.VramEnabledAndSet(7, 1))
{
val |= VramH[addr & 0x7FFF];
}
if (addr >= 0x8000 && addr < 0xC000 && Nds.MemoryControl.VramEnabledAndSet(8, 1))
{
val |= VramI[addr & 0x3FFF];
}
return val;
}
public byte ReadVram8Arm9ObjA(uint addr)
{
byte val = 0;
addr &= 0x1FFFFF;
uint offs = (Nds.MemoryControl.GetOffset(0) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 2))
{
val |= VramA[addr & 0x1FFFF];
}
offs = (Nds.MemoryControl.GetOffset(1) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 2))
{
val |= VramB[addr & 0x1FFFF];
}
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 2))
{
val |= VramE[addr & 0xFFFF];
}
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 2))
{
val |= VramF[addr & 0x3FFF];
}
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 2))
{
val |= VramG[addr & 0x3FFF];
}
return val;
}
public byte ReadVram8Arm9ObjB(uint addr)
{
byte val = 0;
addr &= 0x1FFFFF;
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(3, 4))
{
val |= VramD[addr & 0x1FFFF];
}
if (addr < 0x4000 && Nds.MemoryControl.VramEnabledAndSet(8, 2))
{
val |= VramI[addr & 0x3FFF];
}
return val;
}
public byte ReadVram8Arm9Lcdc(uint addr)
{
switch (addr & 0xE0000)
{
case 0x00000: // A
if (Nds.MemoryControl.VramEnabledAndSet(0, 0))
return VramA[addr & 0x1FFFF];
return 0;
case 0x20000: // B
if (Nds.MemoryControl.VramEnabledAndSet(1, 0))
return VramB[addr & 0x1FFFF];
return 0;
case 0x40000: // C
if (Nds.MemoryControl.VramEnabledAndSet(2, 0))
return VramC[addr & 0x1FFFF];
return 0;
case 0x60000: // D
if (Nds.MemoryControl.VramEnabledAndSet(3, 0))
return VramD[addr & 0x1FFFF];
return 0;
case 0x80000: // E, F, G, H
switch (addr & 0xFF000)
{
case 0x00000:
if (Nds.MemoryControl.VramEnabledAndSet(4, 0))
return VramE[addr & 0xFFFF];
return 0;
case 0x90000: // F
if (Nds.MemoryControl.VramEnabledAndSet(5, 0))
return VramF[addr & 0x3FFF];
return 0;
case 0x94000: // G
if (Nds.MemoryControl.VramEnabledAndSet(6, 0))
return VramG[addr & 0x3FFF];
return 0;
case 0x98000: // H
if (Nds.MemoryControl.VramEnabledAndSet(7, 0))
return VramH[addr & 0x7FFF];
return 0;
}
break;
case 0x8A000: // I
if (Nds.MemoryControl.VramEnabledAndSet(8, 0))
return VramI[addr & 0x3FFF];
return 0;
}
return 0;
}
public void WriteVram8Arm9(uint addr, byte val)
{
uint offs;
byte readVal = 0;
switch (addr & 0xFFE00000)
{
case 0x06000000: // Engine A BG VRAM
addr &= 0x1FFFFF;
offs = Nds.MemoryControl.GetOffset(0) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 1))
{
readVal |= val; VramA[addr & 0x1FFFF] = val;
}
offs = Nds.MemoryControl.GetOffset(1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 1))
{
readVal |= val; VramB[addr & 0x1FFFF] = val;
}
offs = Nds.MemoryControl.GetOffset(2) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 1))
{
readVal |= val; VramC[addr & 0x1FFFF] = val;
}
offs = Nds.MemoryControl.GetOffset(3) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 1))
{
readVal |= val; VramD[addr & 0x1FFFF] = val;
}
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 1))
{
readVal |= val; VramE[addr & 0xFFFF] = val;
}
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 1))
{
readVal |= val; VramF[addr & 0x3FFF] = val;
}
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 1))
{
readVal |= val; VramG[addr & 0x3FFF] = val;
}
VramBgA[addr & 0x1FFFFF] = readVal;
break;
case 0x06200000: // Engine B BG VRAM
addr &= 0x1FFFFF;
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(2, 4))
{
readVal |= val; VramC[addr & 0x1FFFF] = val;
}
if (addr < 0x8000 && Nds.MemoryControl.VramEnabledAndSet(7, 1))
{
readVal |= val; VramH[addr & 0x7FFF] = val;
}
if (addr >= 0x8000 && addr < 0xC000 && Nds.MemoryControl.VramEnabledAndSet(8, 1))
{
readVal |= val; VramI[addr & 0x3FFF] = val;
}
VramBgB[addr & 0x1FFFF] = readVal;
break;
case 0x06400000: // Engine A OBJ VRAM
addr &= 0x1FFFFF;
offs = (Nds.MemoryControl.GetOffset(0) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 2))
{
readVal |= val; VramA[addr & 0x1FFFF] = val;
}
offs = (Nds.MemoryControl.GetOffset(1) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 2))
{
readVal |= val; VramB[addr & 0x1FFFF] = val;
}
if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 2))
{
readVal |= val; VramE[addr & 0xFFFF] = val;
}
offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 2))
{
readVal |= val; VramF[addr & 0x3FFF] = val;
}
offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000;
if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 2))
{
readVal |= val; VramG[addr & 0x3FFF] = val;
}
VramObjA[addr & 0xFFFFF] = readVal;
break;
case 0x06600000: // Engine B OBJ VRAM
addr &= 0x1FFFFF;
if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(3, 4))
{
readVal |= val; VramD[addr & 0x1FFFF] = val;
}
if (addr < 0x4000 && Nds.MemoryControl.VramEnabledAndSet(8, 2))
{
readVal |= val; VramI[addr & 0x3FFF] = val;
}
VramObjB[addr & 0x1FFFF] = readVal;
break;
case 0x06800000: // LCDC VRAM
switch (addr & 0xFFFE0000)
{
case 0x06800000: // A
if (Nds.MemoryControl.VramEnabledAndSet(0, 0))
readVal |= val; VramA[addr & 0x1FFFF] = val;
break;
case 0x06820000: // B
if (Nds.MemoryControl.VramEnabledAndSet(1, 0))
readVal |= val; VramB[addr & 0x1FFFF] = val;
break;
case 0x06840000: // C
if (Nds.MemoryControl.VramEnabledAndSet(2, 0))
readVal |= val; VramC[addr & 0x1FFFF] = val;
break;
case 0x06860000: // D
if (Nds.MemoryControl.VramEnabledAndSet(3, 0))
readVal |= val; VramD[addr & 0x1FFFF] = val;
break;
case 0x06880000: // E, F, G, H
switch (addr & 0xFFFFF000)
{
case 0x68800000:
if (Nds.MemoryControl.VramEnabledAndSet(4, 0))
readVal |= val; VramE[addr & 0xFFFF] = val;
break;
case 0x06890000: // F
if (Nds.MemoryControl.VramEnabledAndSet(5, 0))
readVal |= val; VramF[addr & 0x3FFF] = val;
break;
case 0x06894000: // G
if (Nds.MemoryControl.VramEnabledAndSet(6, 0))
readVal |= val; VramG[addr & 0x3FFF] = val;
break;
case 0x06898000: // H
if (Nds.MemoryControl.VramEnabledAndSet(7, 0))
readVal |= val; VramH[addr & 0x7FFF] = val;
break;
}
break;
case 0x068A0000: // I
if (Nds.MemoryControl.VramEnabledAndSet(8, 0))
readVal |= val; VramI[addr & 0x3FFF] = val;
break;
}
addr &= 0xFFFFF;
if (addr < 671744)
{
VramLcdc[addr] = readVal;
}
break;
}
}
public byte ReadVram8Arm7(uint addr)
{
uint offs;
byte val = 0;
addr &= 0x1FFFFF;
offs = (Nds.MemoryControl.GetOffset(2) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 2))
{
val |= VramC[addr & 0x1FFFF];
}
offs = (Nds.MemoryControl.GetOffset(3) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 2))
{
val |= VramD[addr & 0x1FFFF];
}
return val;
}
public void WriteVram8Arm7(uint addr, byte val)
{
uint offs;
addr &= 0x1FFFFF;
offs = (Nds.MemoryControl.GetOffset(2) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 2))
{
VramC[addr & 0x1FFFF] = val;
}
offs = (Nds.MemoryControl.GetOffset(3) & 1) * 0x20000;
if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 2))
{
VramD[addr & 0x1FFFF] = val;
}
}
public void CompileVram()
{
if (Nds.MemoryControl.VramConfigDirty && !DebugDisableVramUpdates)
{
Nds.MemoryControl.VramConfigDirty = false;
if (Renderers[0].DisplayMode == 2) // LCDC MODE
{
uint index = 0;
VramA.CopyTo(VramLcdc, index); index += 131072;
VramB.CopyTo(VramLcdc, index); index += 131072;
VramC.CopyTo(VramLcdc, index); index += 131072;
VramD.CopyTo(VramLcdc, index); index += 131072;
VramE.CopyTo(VramLcdc, index); index += 65536;
VramF.CopyTo(VramLcdc, index); index += 16384;
VramG.CopyTo(VramLcdc, index); index += 16384;
VramH.CopyTo(VramLcdc, index); index += 32768;
VramI.CopyTo(VramLcdc, index); index += 16384;
}
else
{
Console.WriteLine("VRAM reconfigured, recompiling from scratch");
for (uint i = 0; i < 524288; i++)
{
VramBgA[i] = ReadVram8Arm9(0x06000000 + i);
}
for (uint i = 0; i < 262144; i++)
{
VramObjA[i] = ReadVram8Arm9(0x06400000 + i);
}
for (uint i = 0; i < 131072; i++)
{
VramBgB[i] = ReadVram8Arm9(0x06200000 + i);
}
for (uint i = 0; i < 131072; i++)
{
VramObjB[i] = ReadVram8Arm9(0x06600000 + i);
}
}
}
}
public long ScanlineStartCycles;
public uint DISPCNTAValue;
public uint DISPCNTBValue;
// DISPSTAT
public bool VCounterMatch7;
public bool VBlankIrqEnable7;
public bool HBlankIrqEnable7;
public bool VCounterIrqEnable7;
public uint VCountSetting7;
public bool VCounterMatch9;
public bool VBlankIrqEnable9;
public bool HBlankIrqEnable9;
public bool VCounterIrqEnable9;
public uint VCountSetting9;
// State
public uint VCount;
public long GetScanlineCycles()
{
return Scheduler.CurrentTicks - ScanlineStartCycles;
}
public byte ReadHwio8Arm9(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000000: // DISPCNTA B0
return (byte)(DISPCNTAValue >> 0);
case 0x4000001: // DISPCNTA B1
return (byte)(DISPCNTAValue >> 8);
case 0x4000002: // DISPCNTA B2
return (byte)(DISPCNTAValue >> 16);
case 0x4000003: // DISPCNTA B3
return (byte)(DISPCNTAValue >> 24);
case 0x4000004: // DISPSTAT B0
// Vblank flag is set in scanlines 192-261, not including 262 for some reason
if (VCount >= 192 && VCount <= 261) val = BitSet(val, 0);
// Hblank flag is set at cycle 1606, not cycle 1536
if (GetScanlineCycles() >= 1606) val = BitSet(val, 1);
if (VCounterMatch9) val = BitSet(val, 2);
if (VBlankIrqEnable9) val = BitSet(val, 3);
if (HBlankIrqEnable9) val = BitSet(val, 4);
if (VCounterIrqEnable9) val = BitSet(val, 5);
val |= (byte)((VCountSetting9 >> 1) & 0x80);
return val;
case 0x4000005: // DISPSTAT B1
val |= (byte)VCountSetting9;
return val;
case 0x4000006: // VCOUNT B0 - B1 only exists for Nintendo DS
val |= (byte)VCount;
return val;
case 0x4000007:
val |= (byte)((VCount >> 8) & 1);
return val;
case 0x4001000: // DISPCNTB B0
return (byte)(DISPCNTBValue >> 0);
case 0x4001001: // DISPCNTB B1
return (byte)(DISPCNTBValue >> 8);
case 0x4001002: // DISPCNTB B2
return (byte)(DISPCNTBValue >> 16);
case 0x4001003: // DISPCNTB B3
return (byte)(DISPCNTBValue >> 24);
}
if (addr >= 0x4000000 && addr < 0x4000058)
{
return Renderers[0].ReadHwio8(addr & 0xFF);
}
if (addr >= 0x4001000 && addr < 0x4001058)
{
return Renderers[1].ReadHwio8(addr & 0xFF);
}
return val;
}
public void WriteHwio8Arm9(uint addr, byte val)
{
switch (addr)
{
// A lot of these DISPCNT values are shared between A/B.
case 0x4000000: // DISPCNT B0
// A
Renderers[0].Bg0Is3D = BitTest(val, 3);
// A+B
Renderers[0].BgMode = BitRange(val, 0, 2);
Renderers[0].ObjCharOneDimensional = BitTest(val, 4);
Renderers[0].BitmapObjShape = BitTest(val, 5);
Renderers[0].BitmapObjMapping = BitTest(val, 6);
Renderers[0].ForcedBlank = BitTest(val, 7);
Renderers[0].BackgroundSettingsDirty = true;
DISPCNTAValue &= 0xFFFFFF00;
DISPCNTAValue |= (uint)(val << 0);
break;
case 0x4000001: // DISPCNT B1
// A+B
Renderers[0].ScreenDisplayBg[0] = BitTest(val, 8 - 8);
Renderers[0].ScreenDisplayBg[1] = BitTest(val, 9 - 8);
Renderers[0].ScreenDisplayBg[2] = BitTest(val, 10 - 8);
Renderers[0].ScreenDisplayBg[3] = BitTest(val, 11 - 8);
Renderers[0].ScreenDisplayObj = BitTest(val, 12 - 8);
Renderers[0].Window0DisplayFlag = BitTest(val, 13 - 8);
Renderers[0].Window1DisplayFlag = BitTest(val, 14 - 8);
Renderers[0].ObjWindowDisplayFlag = BitTest(val, 15 - 8);
Renderers[0].AnyWindowEnabled = (val & 0b11100000) != 0;
Renderers[0].BackgroundSettingsDirty = true;
DISPCNTAValue &= 0xFFFF00FF;
DISPCNTAValue |= (uint)(val << 8);
break;
case 0x4000002: // DISPCNT B2
// A
Renderers[0].LcdcVramBlock = BitRange(val, 2, 3);
Renderers[0].BitmapObj1DBoundary = BitTest(val, 6);
// A+B
// var oldDisplayMode = Renderers[0].DisplayMode;
// if (Renderers[0].DisplayMode != oldDisplayMode) VramDirty = true;
Renderers[0].DisplayMode = BitRange(val, 0, 1);
Renderers[0].TileObj1DBoundary = BitRange(val, 4, 5);
Renderers[0].HBlankIntervalFree = BitTest(val, 7);
Renderers[0].BackgroundSettingsDirty = true;
DISPCNTAValue &= 0xFF00FFFF;
DISPCNTAValue |= (uint)(val << 16);
break;
case 0x4000003: // DISPCNT B3
// A
Renderers[0].CharBaseBlockCoarse = BitRange(val, 0, 2);
Renderers[0].MapBaseBlockCoarse = BitRange(val, 3, 5);
// A+B
Renderers[0].BgExtendedPalettes = BitTest(val, 6);
Renderers[0].ObjExtendedPalettes = BitTest(val, 7);
DISPCNTAValue &= 0x00FFFFFF;
DISPCNTAValue |= (uint)(val << 24);
break;
case 0x4000004: // DISPSTAT B0
VBlankIrqEnable9 = BitTest(val, 3);
HBlankIrqEnable9 = BitTest(val, 4);
VCounterIrqEnable9 = BitTest(val, 5);
VCountSetting9 &= 0x0FFU;
VCountSetting9 |= (uint)((val & 0x80) << 1);
break;
case 0x4000005: // DISPSTAT B1
VCountSetting9 &= 0x100U;
VCountSetting9 |= val;
break;
case 0x4000006: // Vcount
case 0x4000007:
// throw new NotImplementedException("NDS: write to vcount");
break;
case 0x4001000: // DISPCNTB B0
// A+B
Renderers[1].BgMode = BitRange(val, 0, 2);
Renderers[1].ObjCharOneDimensional = BitTest(val, 4);
Renderers[1].BitmapObjShape = BitTest(val, 5);
Renderers[1].BitmapObjMapping = BitTest(val, 6);
Renderers[1].ForcedBlank = BitTest(val, 7);
Renderers[1].BackgroundSettingsDirty = true;
DISPCNTBValue &= 0xFFFFFF00;
DISPCNTBValue |= (uint)(val << 0);
break;
case 0x4001001: // DISPCNTB B1
// A+B
Renderers[1].ScreenDisplayBg[0] = BitTest(val, 8 - 8);
Renderers[1].ScreenDisplayBg[1] = BitTest(val, 9 - 8);
Renderers[1].ScreenDisplayBg[2] = BitTest(val, 10 - 8);
Renderers[1].ScreenDisplayBg[3] = BitTest(val, 11 - 8);
Renderers[1].ScreenDisplayObj = BitTest(val, 12 - 8);
Renderers[1].Window0DisplayFlag = BitTest(val, 13 - 8);
Renderers[1].Window1DisplayFlag = BitTest(val, 14 - 8);
Renderers[1].ObjWindowDisplayFlag = BitTest(val, 15 - 8);
Renderers[1].AnyWindowEnabled = (val & 0b11100000) != 0;
Renderers[1].BackgroundSettingsDirty = true;
DISPCNTBValue &= 0xFFFF00FF;
DISPCNTBValue |= (uint)(val << 8);
break;
case 0x4001002: // DISPCNTB B2
// A+B
// var oldDisplayModeB = Renderers[1].DisplayMode;
// if (Renderers[1].DisplayMode != oldDisplayModeB) VramDirty = true;
Renderers[1].DisplayMode = BitRange(val, 0, 1);
Renderers[1].TileObj1DBoundary = BitRange(val, 4, 5);
Renderers[1].HBlankIntervalFree = BitTest(val, 7);
Renderers[1].BackgroundSettingsDirty = true;
DISPCNTBValue &= 0xFF00FFFF;
DISPCNTBValue |= (uint)(val << 16);
break;
case 0x4001003: // DISPCNTB B3
// A+B
Renderers[1].BgExtendedPalettes = BitTest(val, 6);
Renderers[1].ObjExtendedPalettes = BitTest(val, 7);
DISPCNTBValue &= 0x00FFFFFF;
DISPCNTBValue |= (uint)(val << 24);
break;
}
if (addr >= 0x4000000 && addr < 0x4000058)
{
Renderers[0].WriteHwio8(addr & 0xFF, val);
}
if (addr >= 0x4001000 && addr < 0x4001058)
{
Renderers[1].WriteHwio8(addr & 0xFF, val);
}
}
public byte ReadHwio8Arm7(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000004: // DISPSTAT B0
// Vblank flag is set in scanlines 192-261, not including 262 for some reason
if (VCount >= 192 && VCount <= 261) val = BitSet(val, 0);
// Hblank flag is set at cycle 1606, not cycle 1536
if (GetScanlineCycles() >= 1606) val = BitSet(val, 1);
if (VCounterMatch7) val = BitSet(val, 2);
if (VBlankIrqEnable7) val = BitSet(val, 3);
if (HBlankIrqEnable7) val = BitSet(val, 4);
if (VCounterIrqEnable7) val = BitSet(val, 5);
val |= (byte)((VCountSetting7 >> 1) & 0x80);
return val;
case 0x4000005: // DISPSTAT B1
val |= (byte)VCountSetting7;
return val;
}
return 0;
}
public void WriteHwio8Arm7(uint addr, byte val)
{
switch (addr)
{
case 0x4000004: // DISPSTAT B0
VBlankIrqEnable7 = BitTest(val, 3);
HBlankIrqEnable7 = BitTest(val, 4);
VCounterIrqEnable7 = BitTest(val, 5);
VCountSetting7 &= 0x0FFU;
VCountSetting7 |= (uint)((val & 0x80) << 1);
break;
case 0x4000005: // DISPSTAT B1
VCountSetting7 &= 0x100U;
VCountSetting7 |= val;
break;
}
}
public byte ReadOam8(uint addr)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
return GetByte(Renderers[id].Oam, addr);
}
public ushort ReadOam16(uint addr)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
return GetUshort(Renderers[id].Oam, addr);
}
public uint ReadOam32(uint addr)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
return GetUint(Renderers[id].Oam, addr);
}
public void WriteOam16(uint addr, ushort val)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
SetUshort(Renderers[id].Oam, addr, val);
}
public void WriteOam32(uint addr, uint val)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
SetUint(Renderers[id].Oam, addr, val);
}
public byte ReadPalettes8(uint addr)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
return GetByte(Renderers[id].Palettes, addr);
}
public ushort ReadPalettes16(uint addr)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
return GetUshort(Renderers[id].Palettes, addr);
}
public uint ReadPalettes32(uint addr)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
return GetUint(Renderers[id].Palettes, addr);
}
public void WritePalettes16(uint addr, ushort val)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
if (GetUshort(Renderers[id].Palettes, addr) != val)
{
SetUshort(Renderers[id].Palettes, addr, val);
}
}
public void WritePalettes32(uint addr, uint val)
{
addr &= 0x7FF;
var id = addr >= 0x400 ? 1 : 0;
addr &= 0x3FF;
if (GetUint(Renderers[id].Palettes, addr) != val)
{
SetUint(Renderers[id].Palettes, addr, val);
}
}
public void EndDrawingToHblank(long cyclesLate)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 594 - cyclesLate, EndHblank);
// if (HBlankIrqEnable)
// {
// Gba.HwControl.FlagInterrupt(InterruptGba.HBlank);
// }
if (Renderers[0].DisplayMode == 2) // LCDC MODE
{
Renderers[0].RenderScanlineNds(VCount, VramLcdc, VramLcdc);
}
else
{
if (Renderers[0].DebugEnableRendering) Renderers[0].RenderScanlineNds(VCount, VramBgA, VramObjA);
}
if (Renderers[1].DisplayMode == 2)
{
Renderers[1].RenderScanlineNds(VCount, VramLcdc, VramLcdc);
}
else
{
if (Renderers[1].DebugEnableRendering) Renderers[1].RenderScanlineNds(VCount, VramBgB, VramObjB);
}
Renderers[0].IncrementMosaicCounters();
Renderers[1].IncrementMosaicCounters();
Nds.Dma9.Repeat((byte)DmaStartTimingNds9.HBlank);
}
public void EndVblankToHblank(long cyclesLate)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 594 - cyclesLate, EndHblank);
// if (HBlankIrqEnable)
// {
// Nds.HwControl.FlagInterrupt(InterruptGba.HBlank);
// }
}
public void EndHblank(long cyclesLate)
{
ScanlineStartCycles = Scheduler.CurrentTicks;
if (VCount != 262)
{
VCount++;
if (VCount > 191)
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndVblankToHblank);
if (VCount == 192)
{
// Nds.Dma.RepeatVblank();
if (VBlankIrqEnable7)
{
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.VBlank);
}
if (VBlankIrqEnable9)
{
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.VBlank);
}
Renderers[0].RunVblankOperations();
Renderers[1].RunVblankOperations();
Renderers[0].TotalFrames++;
if (Renderers[0].DebugEnableRendering) Renderers[0].SwapBuffers();
if (Renderers[1].DebugEnableRendering) Renderers[1].SwapBuffers();
Renderers[0].RenderingDone = true;
}
}
else
{
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndDrawingToHblank);
}
}
else
{
VCount = 0;
Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndDrawingToHblank);
// CompileVram();
// Pre-render sprites for line zero
fixed (byte* vramObjA = VramObjA, vramObjB = VramObjB)
{
if (Renderers[0].DebugEnableObj && Renderers[0].ScreenDisplayObj) Renderers[0].RenderObjs(0, vramObjA);
if (Renderers[1].DebugEnableObj && Renderers[1].ScreenDisplayObj) Renderers[1].RenderObjs(0, vramObjB);
}
}
VCounterMatch7 = VCount == VCountSetting7;
VCounterMatch9 = VCount == VCountSetting9;
if (VCounterMatch7 && VCounterIrqEnable7)
{
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.VCounterMatch);
}
if (VCounterMatch9 && VCounterIrqEnable9)
{
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.VCounterMatch);
}
}
}
}