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); } } } }