using System; using System.Runtime.InteropServices; using StoicGoose.Common.Attributes; using StoicGoose.Core.Interfaces; using StoicGoose.Core.Machines; using StoicGooseUnity; using static StoicGoose.Common.Utilities.BitHandling; namespace StoicGoose.Core.Display { public unsafe abstract class DisplayControllerCommon : IPortAccessComponent { public const int HorizontalDisp = 224; public const int HorizontalBlank = 32; public const int HorizontalTotal = HorizontalDisp + HorizontalBlank; public const double HorizontalClock = MachineCommon.CpuClock / HorizontalTotal; public const int VerticalDisp = 144; public const int VerticalBlank = 15; public const int VerticalTotal = VerticalDisp + VerticalBlank; public const double VerticalClock = 12000.0 / VerticalTotal; public const int ScreenWidth = HorizontalDisp; public const int ScreenHeight = VerticalDisp; protected const int maxSpriteCount = 128; protected const int maxSpritesPerLine = 32; [Flags] public enum DisplayInterrupts { None = 0, LineCompare = 1 << 0, VBlankTimer = 1 << 1, VBlank = 1 << 2, HBlankTimer = 1 << 3 } protected readonly uint[] spriteData = new uint[maxSpriteCount]; protected readonly uint[] spriteDataNextFrame = new uint[maxSpriteCount]; protected readonly uint[] activeSpritesOnLine = new uint[maxSpritesPerLine]; protected readonly bool[] isUsedBySCR2 = new bool[HorizontalDisp * VerticalDisp]; protected int spriteCountNextFrame = 0, activeSpriteCountOnLine = 0; protected int cycleCount = 0; //protected readonly byte[] outputFramebuffer = new byte[ScreenWidth * ScreenHeight * 4]; #region //指针化 outputFramebuffer protected byte[] outputFramebuffer_src; protected GCHandle outputFramebuffer_handle; protected IntPtr outputFramebuffer_IntPtr; protected byte* outputFramebuffer; protected int outputFramebufferLength; protected bool outputFramebuffer_IsNull => outputFramebuffer == null; protected byte[] outputFramebuffer_set { set { outputFramebuffer_handle.ReleaseGCHandle(); outputFramebuffer_src = value; outputFramebufferLength = value.Length; outputFramebuffer_src.GetObjectPtr(ref outputFramebuffer_handle, ref outputFramebuffer, out outputFramebuffer_IntPtr); } } #endregion //public Action SendFramebuffer { get; set; } = default; public Action SendFramebuffer { get; set; } = default; protected readonly IMachine machine = default; /* REG_DISP_CTRL */ protected bool scr1Enable, scr2Enable, sprEnable, sprWindowEnable, scr2WindowDisplayOutside, scr2WindowEnable; /* REG_BACK_COLOR */ protected byte backColorIndex; /* REG_LINE_xxx */ protected int lineCurrent, lineCompare; /* REG_SPR_xxx */ protected int sprBase, sprFirst, sprCount; /* REG_MAP_BASE */ protected int scr1Base, scr2Base; /* REG_SCR2_WIN_xx */ protected int scr2WinX0, scr2WinY0, scr2WinX1, scr2WinY1; /* REG_SPR_WIN_xx */ protected int sprWinX0, sprWinY0, sprWinX1, sprWinY1; /* REG_SCR1_xx */ protected int scr1ScrollX, scr1ScrollY; /* REG_SCR2_xx */ protected int scr2ScrollX, scr2ScrollY; /* REG_LCD_xxx */ protected bool lcdActive; protected bool iconSleep, iconVertical, iconHorizontal, iconAux1, iconAux2, iconAux3; protected int vtotal, vsync; /* REG_PALMONO_POOL_x */ protected readonly byte[] palMonoPools = default; /* REG_PALMONO_x */ protected readonly byte[][] palMonoData = default; /* REG_xTMR_xxx */ protected readonly DisplayTimer hBlankTimer = new(), vBlankTimer = new(); public DisplayControllerCommon(IMachine machine) { this.machine = machine; //初始化指针 outputFramebuffer_set = new byte[ScreenWidth * ScreenHeight * 4]; palMonoPools = new byte[8]; palMonoData = new byte[16][]; for (var i = 0; i < palMonoData.GetLength(0); i++) palMonoData[i] = new byte[4]; } public void Reset() { cycleCount = 0; //Array.Fill(outputFramebuffer, 255); for (var i = 0; i < outputFramebufferLength; i++) outputFramebuffer[i] = 255; Array.Fill(isUsedBySCR2, false); Array.Fill(spriteData, 0); Array.Fill(spriteDataNextFrame, 0); Array.Fill(activeSpritesOnLine, 0); spriteCountNextFrame = 0; activeSpriteCountOnLine = 0; ResetRegisters(); } protected virtual void ResetRegisters() { scr1Enable = scr2Enable = sprEnable = sprWindowEnable = scr2WindowDisplayOutside = scr2WindowEnable = false; backColorIndex = 0; lineCurrent = lineCompare = 0; sprBase = sprFirst = sprCount = 0; scr1Base = scr2Base = 0; scr2WinX0 = scr2WinY0 = scr2WinX1 = scr2WinY1 = 0; sprWinX0 = sprWinY0 = sprWinX1 = sprWinY1 = 0; scr1ScrollX = scr1ScrollY = 0; scr2ScrollX = scr2ScrollY = 0; lcdActive = true; /* NOTE: Final Lap 2000 depends on bootstrap doing this, otherwise LCD stays off? */ iconSleep = iconVertical = iconHorizontal = iconAux1 = iconAux2 = iconAux3 = false; vtotal = VerticalTotal - 1; vsync = VerticalTotal - 4; /* NOTE: Full usage/meaning unknown, so we're ignoring it for now */ Array.Fill(palMonoPools, 0); for (var i = 0; i < palMonoData.GetLength(0); i++) Array.Fill(palMonoData[i], 0); hBlankTimer.Reset(); vBlankTimer.Reset(); } public void Shutdown() { /* Nothing to do... */ } public DisplayInterrupts Step(int clockCyclesInStep) { var interrupt = DisplayInterrupts.None; cycleCount += clockCyclesInStep; if (cycleCount >= HorizontalTotal) { /* Sprite fetch */ if (lineCurrent == VerticalDisp - 2) { spriteCountNextFrame = 0; for (var j = sprFirst; j < sprFirst + Math.Min(maxSpriteCount, sprCount); j++) { var k = (uint)((sprBase << 9) + (j << 2)); spriteDataNextFrame[spriteCountNextFrame++] = (uint)(machine.ReadMemory(k + 3) << 24 | machine.ReadMemory(k + 2) << 16 | machine.ReadMemory(k + 1) << 8 | machine.ReadMemory(k + 0)); } } /* Render pixels */ for (var x = 0; x < HorizontalDisp; x++) RenderPixel(lineCurrent, x); /* Line compare interrupt */ if (lineCurrent == lineCompare) interrupt |= DisplayInterrupts.LineCompare; /* H-timer interrupt */ if (hBlankTimer.Step()) interrupt |= DisplayInterrupts.HBlankTimer; /* V-blank interrupt */ if (lineCurrent == VerticalDisp) { interrupt |= DisplayInterrupts.VBlank; /* V-timer interrupt */ if (vBlankTimer.Step()) interrupt |= DisplayInterrupts.VBlankTimer; /* Transfer framebuffer */ //SendFramebuffer?.Invoke(outputFramebuffer.Clone() as byte[]); SendFramebuffer?.Invoke(outputFramebuffer_IntPtr, outputFramebufferLength); } /* Advance scanline */ lineCurrent++; /* Is frame finished? */ if (lineCurrent > Math.Max(VerticalDisp, vtotal)) { /* Copy sprite data for next frame */ for (int j = 0, k = spriteCountNextFrame - 1; k >= 0; j++, k--) spriteData[j] = spriteDataNextFrame[k]; Array.Fill(spriteDataNextFrame, 0); /* Reset variables */ lineCurrent = 0; Array.Fill(isUsedBySCR2, false); } /* End of scanline */ cycleCount = 0; } return interrupt; } protected void RenderPixel(int y, int x) { if (y < 0 || y >= VerticalDisp || x < 0 || x >= HorizontalDisp) return; if (lcdActive) { RenderBackColor(y, x); RenderSCR1(y, x); RenderSCR2(y, x); RenderSprites(y, x); } else { /* LCD sleeping */ RenderSleep(y, x); } } protected abstract void RenderSleep(int y, int x); protected abstract void RenderBackColor(int y, int x); protected abstract void RenderSCR1(int y, int x); protected abstract void RenderSCR2(int y, int x); protected abstract void RenderSprites(int y, int x); protected static void ValidateWindowCoordinates(ref int x0, ref int x1, ref int y0, ref int y1) { /* Thank you for this fix, for the encouragement and hints and advice, for just having been there... Thank you for everything, Near. * https://forum.fobby.net/index.php?t=msg&goto=6085 */ if (x0 > x1) (x1, x0) = (x0, x1); if (y0 > y1) (y1, y0) = (y0, y1); } protected bool IsInsideSCR2Window(int y, int x) { var x0 = scr2WinX0; var x1 = scr2WinX1; var y0 = scr2WinY0; var y1 = scr2WinY1; ValidateWindowCoordinates(ref x0, ref x1, ref y0, ref y1); return ((x >= x0 && x <= x1) || (x >= x1 && x <= x0)) && ((y >= y0 && y <= y1) || (y >= y1 && y <= y0)); } protected bool IsOutsideSCR2Window(int y, int x) { var x0 = scr2WinX0; var x1 = scr2WinX1; var y0 = scr2WinY0; var y1 = scr2WinY1; ValidateWindowCoordinates(ref x0, ref x1, ref y0, ref y1); return x < x0 || x > x1 || y < y0 || y > y1; } protected bool IsInsideSPRWindow(int y, int x) { var x0 = sprWinX0; var x1 = sprWinX1; var y0 = sprWinY0; var y1 = sprWinY1; ValidateWindowCoordinates(ref x0, ref x1, ref y0, ref y1); return ((x >= x0 && x <= x1) || (x >= x1 && x <= x0)) && ((y >= y0 && y <= y1) || (y >= y1 && y <= y0)); } protected byte ReadMemory8(uint address) => machine.ReadMemory(address); protected ushort ReadMemory16(uint address) => (ushort)(machine.ReadMemory(address + 1) << 8 | machine.ReadMemory(address)); protected uint ReadMemory32(uint address) => (uint)(machine.ReadMemory(address + 3) << 24 | machine.ReadMemory(address + 2) << 16 | machine.ReadMemory(address + 1) << 8 | machine.ReadMemory(address)); public virtual byte ReadPort(ushort port) { var retVal = (byte)0; switch (port) { case 0x00: /* REG_DISP_CTRL */ ChangeBit(ref retVal, 0, scr1Enable); ChangeBit(ref retVal, 1, scr2Enable); ChangeBit(ref retVal, 2, sprEnable); ChangeBit(ref retVal, 3, sprWindowEnable); ChangeBit(ref retVal, 4, scr2WindowDisplayOutside); ChangeBit(ref retVal, 5, scr2WindowEnable); break; case 0x02: /* REG_LINE_CUR */ retVal |= (byte)(lineCurrent & 0xFF); break; case 0x03: /* REG_LINE_CMP */ retVal |= (byte)(lineCompare & 0xFF); break; case 0x05: /* REG_SPR_FIRST */ retVal |= (byte)(sprFirst & 0x7F); break; case 0x06: /* REG_SPR_COUNT */ retVal |= (byte)(sprCount & 0xFF); break; case 0x08: /* REG_SCR2_WIN_X0 */ retVal |= (byte)(scr2WinX0 & 0xFF); break; case 0x09: /* REG_SCR2_WIN_Y0 */ retVal |= (byte)(scr2WinY0 & 0xFF); break; case 0x0A: /* REG_SCR2_WIN_X1 */ retVal |= (byte)(scr2WinX1 & 0xFF); break; case 0x0B: /* REG_SCR2_WIN_Y1 */ retVal |= (byte)(scr2WinY1 & 0xFF); break; case 0x0C: /* REG_SPR_WIN_X0 */ retVal |= (byte)(sprWinX0 & 0xFF); break; case 0x0D: /* REG_SPR_WIN_Y0 */ retVal |= (byte)(sprWinY0 & 0xFF); break; case 0x0E: /* REG_SPR_WIN_X1 */ retVal |= (byte)(sprWinX1 & 0xFF); break; case 0x0F: /* REG_SPR_WIN_Y1 */ retVal |= (byte)(sprWinY1 & 0xFF); break; case 0x10: /* REG_SCR1_X */ retVal |= (byte)(scr1ScrollX & 0xFF); break; case 0x11: /* REG_SCR1_Y */ retVal |= (byte)(scr1ScrollY & 0xFF); break; case 0x12: /* REG_SCR2_X */ retVal |= (byte)(scr2ScrollX & 0xFF); break; case 0x13: /* REG_SCR2_Y */ retVal |= (byte)(scr2ScrollY & 0xFF); break; case 0x15: /* REG_LCD_ICON */ ChangeBit(ref retVal, 0, iconSleep); ChangeBit(ref retVal, 1, iconVertical); ChangeBit(ref retVal, 2, iconHorizontal); ChangeBit(ref retVal, 3, iconAux1); ChangeBit(ref retVal, 4, iconAux2); ChangeBit(ref retVal, 5, iconAux3); break; case 0x16: /* REG_LCD_VTOTAL */ retVal |= (byte)(vtotal & 0xFF); break; case 0x17: /* REG_LCD_VSYNC */ retVal |= (byte)(vsync & 0xFF); break; case 0x1C: case 0x1D: case 0x1E: case 0x1F: /* REG_PALMONO_POOL_x */ retVal |= (byte)(palMonoPools[((port & 0b11) << 1) | 0] << 0); retVal |= (byte)(palMonoPools[((port & 0b11) << 1) | 1] << 4); break; case ushort _ when port >= 0x20 && port <= 0x3F: /* REG_PALMONO_x */ retVal |= (byte)(palMonoData[(port >> 1) & 0b1111][((port & 0b1) << 1) | 0] << 0); retVal |= (byte)(palMonoData[(port >> 1) & 0b1111][((port & 0b1) << 1) | 1] << 4); break; case 0xA2: /* REG_TMR_CTRL */ ChangeBit(ref retVal, 0, hBlankTimer.Enable); ChangeBit(ref retVal, 1, hBlankTimer.Repeating); ChangeBit(ref retVal, 2, vBlankTimer.Enable); ChangeBit(ref retVal, 3, vBlankTimer.Repeating); break; case 0xA4: case 0xA5: /* REG_HTMR_FREQ */ retVal |= (byte)((hBlankTimer.Frequency >> ((port & 0b1) * 8)) & 0xFF); break; case 0xA6: case 0xA7: /* REG_VTMR_FREQ */ retVal |= (byte)((vBlankTimer.Frequency >> ((port & 0b1) * 8)) & 0xFF); break; case 0xA8: case 0xA9: /* REG_HTMR_CTR */ retVal |= (byte)((hBlankTimer.Counter >> ((port & 0b1) * 8)) & 0xFF); break; case 0xAA: case 0xAB: /* REG_VTMR_CTR */ retVal |= (byte)((vBlankTimer.Counter >> ((port & 0b1) * 8)) & 0xFF); break; } return retVal; } public virtual void WritePort(ushort port, byte value) { switch (port) { case 0x00: /* REG_DISP_CTRL */ scr1Enable = IsBitSet(value, 0); scr2Enable = IsBitSet(value, 1); sprEnable = IsBitSet(value, 2); sprWindowEnable = IsBitSet(value, 3); scr2WindowDisplayOutside = IsBitSet(value, 4); scr2WindowEnable = IsBitSet(value, 5); break; case 0x03: /* REG_LINE_CMP */ lineCompare = (byte)(value & 0xFF); break; case 0x05: /* REG_SPR_FIRST */ sprFirst = (byte)(value & 0x7F); break; case 0x06: /* REG_SPR_COUNT */ sprCount = (byte)(value & 0xFF); break; case 0x08: /* REG_SCR2_WIN_X0 */ scr2WinX0 = (byte)(value & 0xFF); break; case 0x09: /* REG_SCR2_WIN_Y0 */ scr2WinY0 = (byte)(value & 0xFF); break; case 0x0A: /* REG_SCR2_WIN_X1 */ scr2WinX1 = (byte)(value & 0xFF); break; case 0x0B: /* REG_SCR2_WIN_Y1 */ scr2WinY1 = (byte)(value & 0xFF); break; case 0x0C: /* REG_SPR_WIN_X0 */ sprWinX0 = (byte)(value & 0xFF); break; case 0x0D: /* REG_SPR_WIN_Y0 */ sprWinY0 = (byte)(value & 0xFF); break; case 0x0E: /* REG_SPR_WIN_X1 */ sprWinX1 = (byte)(value & 0xFF); break; case 0x0F: /* REG_SPR_WIN_Y1 */ sprWinY1 = (byte)(value & 0xFF); break; case 0x10: /* REG_SCR1_X */ scr1ScrollX = (byte)(value & 0xFF); break; case 0x11: /* REG_SCR1_Y */ scr1ScrollY = (byte)(value & 0xFF); break; case 0x12: /* REG_SCR2_X */ scr2ScrollX = (byte)(value & 0xFF); break; case 0x13: /* REG_SCR2_Y */ scr2ScrollY = (byte)(value & 0xFF); break; case 0x15: /* REG_LCD_ICON */ iconSleep = IsBitSet(value, 0); iconVertical = IsBitSet(value, 1); iconHorizontal = IsBitSet(value, 2); iconAux1 = IsBitSet(value, 3); iconAux2 = IsBitSet(value, 4); iconAux3 = IsBitSet(value, 5); break; case 0x16: /* REG_LCD_VTOTAL */ vtotal = (byte)(value & 0xFF); break; case 0x17: /* REG_LCD_VSYNC */ vsync = (byte)(value & 0xFF); break; case 0x1C: case 0x1D: case 0x1E: case 0x1F: /* REG_PALMONO_POOL_x */ palMonoPools[((port & 0b11) << 1) | 0] = (byte)((value >> 0) & 0b1111); palMonoPools[((port & 0b11) << 1) | 1] = (byte)((value >> 4) & 0b1111); break; case ushort _ when port >= 0x20 && port <= 0x3F: /* REG_PALMONO_x */ palMonoData[(port >> 1) & 0b1111][((port & 0b1) << 1) | 0] = (byte)((value >> 0) & 0b111); palMonoData[(port >> 1) & 0b1111][((port & 0b1) << 1) | 1] = (byte)((value >> 4) & 0b111); break; case 0xA2: /* REG_TMR_CTRL */ hBlankTimer.Enable = IsBitSet(value, 0); hBlankTimer.Repeating = IsBitSet(value, 1); vBlankTimer.Enable = IsBitSet(value, 2); vBlankTimer.Repeating = IsBitSet(value, 3); break; case 0xA4: /* REG_HTMR_FREQ (low) */ hBlankTimer.Frequency = (ushort)((hBlankTimer.Frequency & 0xFF00) | value); hBlankTimer.Counter = (ushort)((hBlankTimer.Counter & 0xFF00) | value); break; case 0xA5: /* REG_HTMR_FREQ (high) */ hBlankTimer.Frequency = (ushort)((hBlankTimer.Frequency & 0x00FF) | (value << 8)); hBlankTimer.Counter = (ushort)((hBlankTimer.Counter & 0x00FF) | (value << 8)); break; case 0xA6: /* REG_VTMR_FREQ (low) */ vBlankTimer.Frequency = (ushort)((vBlankTimer.Frequency & 0xFF00) | value); vBlankTimer.Counter = (ushort)((vBlankTimer.Counter & 0xFF00) | value); break; case 0xA7: /* REG_VTMR_FREQ (high) */ vBlankTimer.Frequency = (ushort)((vBlankTimer.Frequency & 0x00FF) | (value << 8)); vBlankTimer.Counter = (ushort)((vBlankTimer.Counter & 0x00FF) | (value << 8)); break; } } [Port("REG_DISP_CTRL", 0x000)] [BitDescription("SCR1 enable", 0)] public bool Scr1Enable => scr1Enable; [Port("REG_DISP_CTRL", 0x000)] [BitDescription("SCR2 enable", 1)] public bool Scr2Enable => scr2Enable; [Port("REG_DISP_CTRL", 0x000)] [BitDescription("SPR enable", 2)] public bool SprEnable => sprEnable; [Port("REG_DISP_CTRL", 0x000)] [BitDescription("SPR window enable", 3)] public bool SprWindowEnable => sprWindowEnable; [Port("REG_DISP_CTRL", 0x000)] [BitDescription("SCR2 window mode; display outside?", 4)] public bool Scr2WindowDisplayOutside => scr2WindowDisplayOutside; [Port("REG_DISP_CTRL", 0x000)] [BitDescription("SCR2 window enable", 5)] public bool Scr2WindowEnable => scr2WindowEnable; [Port("REG_BACK_COLOR", 0x001)] [BitDescription("Background color pool index", 0, 2)] public virtual byte BackColorIndex => backColorIndex; [Port("REG_LINE_CUR", 0x002)] [BitDescription("Current line being drawn")] public int LineCurrent => lineCurrent; [Port("REG_LINE_CMP", 0x003)] [BitDescription("Line compare interrupt line")] public int LineCompare => lineCompare; [Port("REG_SPR_BASE", 0x004)] [BitDescription("Sprite table base address", 0, 4)] [Format("X4", 9)] public virtual int SprBase => sprBase; [Port("REG_SPR_FIRST", 0x005)] [BitDescription("First sprite to draw", 0, 6)] public int SprFirst => sprFirst; [Port("REG_SPR_COUNT", 0x006)] [BitDescription("Number of sprites to draw")] public int SprCount => sprCount; [Port("REG_MAP_BASE", 0x007)] [BitDescription("SCR1 base address", 0, 2)] [Format("X4", 11)] public virtual int Scr1Base => scr1Base; [Port("REG_MAP_BASE", 0x007)] [BitDescription("SCR2 base address", 4, 6)] [Format("X4", 11)] public virtual int Scr2Base => scr2Base; [Port("REG_SCR2_WIN_X0", 0x008)] [BitDescription("Top-left X of SCR2 window")] public int Scr2WinX0 => scr2WinX0; [Port("REG_SCR2_WIN_Y0", 0x009)] [BitDescription("Top-left Y of SCR2 window")] public int Scr2WinY0 => scr2WinY0; [Port("REG_SCR2_WIN_X1", 0x00A)] [BitDescription("Bottom-right X of SCR2 window")] public int Scr2WinX1 => scr2WinX1; [Port("REG_SCR2_WIN_Y1", 0x00B)] [BitDescription("Bottom-right Y of SCR2 window")] public int Scr2WinY1 => scr2WinY1; [Port("REG_SPR_WIN_X0", 0x00C)] [BitDescription("Top-left X of SPR window")] public int SprWinX0 => sprWinX0; [Port("REG_SPR_WIN_Y0", 0x00D)] [BitDescription("Top-left Y of SPR window")] public int SprWinY0 => sprWinY0; [Port("REG_SPR_WIN_X1", 0x00E)] [BitDescription("Bottom-right X of SPR window")] public int SprWinX1 => sprWinX1; [Port("REG_SPR_WIN_Y1", 0x00F)] [BitDescription("Bottom-right Y of SPR window")] public int SprWinY1 => sprWinY1; [Port("REG_SCR1_X", 0x010)] [BitDescription("SCR1 X scroll")] public int Scr1ScrollX => scr1ScrollX; [Port("REG_SCR1_Y", 0x011)] [BitDescription("SCR1 Y scroll")] public int Scr1ScrollY => scr1ScrollY; [Port("REG_SCR2_X", 0x012)] [BitDescription("SCR2 X scroll")] public int Scr2ScrollX => scr2ScrollX; [Port("REG_SCR2_Y", 0x013)] [BitDescription("SCR2 Y scroll")] public int Scr2ScrollY => scr2ScrollY; [Port("REG_LCD_CTRL", 0x014)] [BitDescription("LCD sleep mode; is LCD active?", 0)] public bool LcdActive => lcdActive; [Port("REG_LCD_ICON", 0x015)] [BitDescription("Sleep indicator", 0)] public bool IconSleep => iconSleep; [Port("REG_LCD_ICON", 0x015)] [BitDescription("Vertical orientation indicator", 1)] public bool IconVertical => iconVertical; [Port("REG_LCD_ICON", 0x015)] [BitDescription("Horizontal orientation indicator", 2)] public bool IconHorizontal => iconHorizontal; [Port("REG_LCD_ICON", 0x015)] [BitDescription("Auxiliary 1 (Small circle)", 3)] public bool IconAux1 => iconAux1; [Port("REG_LCD_ICON", 0x015)] [BitDescription("Auxiliary 2 (Medium circle)", 4)] public bool IconAux2 => iconAux2; [Port("REG_LCD_ICON", 0x015)] [BitDescription("Auxiliary 3 (Big circle)", 5)] public bool IconAux3 => iconAux3; [Port("REG_LCD_VTOTAL", 0x016)] [BitDescription("Display VTOTAL")] public int VTotal => vtotal; [Port("REG_LCD_VSYNC", 0x017)] [BitDescription("VSYNC line position")] public int VSync => vsync; [Port("REG_TMR_CTRL", 0x0A2)] [BitDescription("H-blank timer enable", 0)] public bool HBlankTimerEnable => hBlankTimer.Enable; [Port("REG_TMR_CTRL", 0x0A2)] [BitDescription("H-blank timer mode; is repeating?", 1)] public bool HBlankTimerRepeating => hBlankTimer.Repeating; [Port("REG_TMR_CTRL", 0x0A2)] [BitDescription("V-blank timer enable", 2)] public bool VBlankTimerEnable => vBlankTimer.Enable; [Port("REG_TMR_CTRL", 0x0A2)] [BitDescription("V-blank timer mode; is repeating?", 3)] public bool VBlankTimerRepeating => vBlankTimer.Repeating; [Port("REG_HTMR_FREQ", 0x0A4, 0x0A5)] [BitDescription("H-blank timer frequency")] public ushort HBlankTimerFrequency => hBlankTimer.Frequency; [Port("REG_VTMR_FREQ", 0x0A6, 0x0A7)] [BitDescription("V-blank timer frequency")] public ushort VBlankTimerFrequency => vBlankTimer.Frequency; [Port("REG_HTMR_CTR", 0x0A8, 0x0A9)] [BitDescription("H-blank timer counter")] public ushort HBlankTimerCounter => hBlankTimer.Counter; [Port("REG_VTMR_CTR", 0x0AA, 0x0AB)] [BitDescription("V-blank timer counter")] public ushort VBlankTimerCounter => vBlankTimer.Counter; // TODO: reorganize palmono stuff & add attributes public byte[] PalMonoPools => palMonoPools; public byte[][] PalMonoData => palMonoData; } }