StoicGoose.Unity/Assets/Plugins/StoicGooseUnity/StoicGoose.Core/Display/DisplayControllerCommon.cs

774 lines
29 KiB
C#

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<byte[]> SendFramebuffer { get; set; } = default;
public Action<IntPtr, long> 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<byte>(outputFramebuffer, 255);
for (var i = 0; i < outputFramebufferLength; i++)
outputFramebuffer[i] = 255;
Array.Fill(isUsedBySCR2, false);
Array.Fill<uint>(spriteData, 0);
Array.Fill<uint>(spriteDataNextFrame, 0);
Array.Fill<uint>(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<byte>(palMonoPools, 0);
for (var i = 0; i < palMonoData.GetLength(0); i++) Array.Fill<byte>(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<uint>(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;
}
}