using System; using System.IO; using Jellyfish.Virtu.Services; namespace Jellyfish.Virtu { [Flags] public enum ScannerOptions { None = 0x0, AppleII = 0x1, Pal = 0x2 } // defaults to AppleIIe, Ntsc public sealed partial class Video : MachineComponent { public Video() { } public Video(Machine machine) : base(machine) { _flushRowEvent = FlushRowEvent; // cache delegates; avoids garbage _inverseTextEvent = InverseTextEvent; _leaveVBlankEvent = LeaveVBlankEvent; _resetVSyncEvent = ResetVSyncEvent; FlushRowMode = new Action[ModeCount] { FlushRowMode0, FlushRowMode1, FlushRowMode2, FlushRowMode3, FlushRowMode4, FlushRowMode5, FlushRowMode6, FlushRowMode7, FlushRowMode8, FlushRowMode9, FlushRowModeA, FlushRowModeB, FlushRowModeC, FlushRowModeD, FlushRowModeE, FlushRowModeF }; } [System.Runtime.Serialization.OnDeserialized] public void OnDeserialized(System.Runtime.Serialization.StreamingContext context) { // the videoservice forgets all of its information on loadstate DirtyScreen(); } public override void Initialize() { _memory = Machine.Memory; VideoService = new Services.VideoService(); //#if SILVERLIGHT || WPF unchecked { _colorBlack = (int)0xFF000000; // BGRA _colorDarkBlue = (int)0xFF000099; _colorDarkGreen = (int)0xFF117722; _colorMediumBlue = (int)0xFF0000FF; _colorBrown = (int)0xFF885500; _colorLightGrey = (int)0xFF99AAAA; _colorGreen = (int)0xFF00EE11; _colorAquamarine = (int)0xFF55FFAA; _colorDeepRed = (int)0xFFFF1111; _colorPurple = (int)0xFFDD00DD; _colorDarkGrey = (int)0xFF445555; _colorLightBlue = (int)0xFF33AAFF; _colorOrange = (int)0xFFFF4411; _colorPink = (int)0xFFFF9988; _colorYellow = (int)0xFFFFFF11; _colorWhite = (int)0xFFFFFFFF; _colorMonochrome = (int)0xFF00AA00; } //#else // _colorBlack = 0xFF000000; // RGBA // _colorDarkBlue = 0xFF990000; // _colorDarkGreen = 0xFF227711; // _colorMediumBlue = 0xFFFF0000; // _colorBrown = 0xFF005588; // _colorLightGrey = 0xFFAAAA99; // _colorGreen = 0xFF11EE00; // _colorAquamarine = 0xFFAAFF55; // _colorDeepRed = 0xFF1111FF; // _colorPurple = 0xFFDD00DD; // _colorDarkGrey = 0xFF555544; // _colorLightBlue = 0xFFFFAA33; // _colorOrange = 0xFF1144FF; // _colorPink = 0xFF8899FF; // _colorYellow = 0xFF11FFFF; // _colorWhite = 0xFFFFFFFF; // _colorMonochrome = 0xFF00AA00; //#endif SetPalette(); IsMonochrome = false; ScannerOptions = ScannerOptions.None; IsVBlank = true; Machine.Events.AddEvent(_cyclesPerVBlankPreset, _leaveVBlankEvent); // align flush events with scanner; assumes vcount preset at start of frame [3-15, 3-16] Machine.Events.AddEvent(_cyclesPerVSync, _resetVSyncEvent); Machine.Events.AddEvent(_cyclesPerFlash, _inverseTextEvent); } public override void Reset() { SetCharSet(); DirtyScreen(); FlushScreen(); } public void DirtyCell(int addressOffset) { _isCellDirty[CellIndex[addressOffset]] = true; } public void DirtyCellMixed(int addressOffset) { int cellIndex = CellIndex[addressOffset]; if (cellIndex < MixedCellIndex) { _isCellDirty[cellIndex] = true; } } public void DirtyCellMixedText(int addressOffset) { int cellIndex = CellIndex[addressOffset]; if (cellIndex >= MixedCellIndex) { _isCellDirty[cellIndex] = true; } } public void DirtyScreen() { for (int i = 0; i < Height * CellColumns; i++) { _isCellDirty[i] = true; } } public void DirtyScreenText() { if (_memory.IsText) { for (int i = 0; i < MixedHeight * CellColumns; i++) { _isCellDirty[i] = true; } } if (_memory.IsText || _memory.IsMixed) { for (int i = MixedHeight * CellColumns; i < Height * CellColumns; i++) { _isCellDirty[i] = true; } } } public int ReadFloatingBus() // [5-40] { // derive scanner counters from current cycles into frame; assumes hcount and vcount preset at start of frame [3-13, 3-15, 3-16] int cycles = _cyclesPerVSync - Machine.Events.FindEvent(_resetVSyncEvent); int hClock = cycles % CyclesPerHSync; int hCount = (hClock != 0) ? HCountPreset + hClock - 1 : 0; int vLine = cycles / CyclesPerHSync; int vCount = _vCountPreset + vLine; // derive scanner address [5-8] int address = ((vCount << 4) & 0x0380) | ((0x0068 + (hCount & 0x0038) + (((vCount >> 1) & 0x0060) | ((vCount >> 3) & 0x0018))) & 0x0078) | (hCount & 0x0007); if (_memory.IsHires && !(_memory.IsMixed && ((vCount & 0xA0) == 0xA0))) // hires but not actively mixed [5-13, 5-19] { address |= (_memory.IsVideoPage2 ? 0x4000 : 0x2000) | ((vCount << 10) & 0x1C00); } else { address |= _memory.IsVideoPage2 ? 0x0800 : 0x0400; if (((_scannerOptions & ScannerOptions.AppleII) != 0) && (hCount < HCountLeaveHBlank)) { address |= 0x1000; } } return _memory.Read(address); } public void SetCharSet() { _charSet = !_memory.IsCharSetAlternate ? CharSetPrimary : (_memory.Monitor == MonitorType.Standard) ? CharSetSecondaryStandard : CharSetSecondaryEnhanced; DirtyScreenText(); } #region Draw Methods private void DrawText40(int data, int x, int y) { int color = IsMonochrome ? ColorMono00 : ColorWhite00; int index = _charSet[data] * CharBitmapBytes; int inverseMask = (_isTextInversed && !_memory.IsCharSetAlternate && (0x40 <= data) && (data <= 0x7F)) ? 0x7F : 0x00; for (int i = 0; i < TextHeight; i++, y++) { data = CharBitmap[index + i] ^ inverseMask; SetPixel(x + 0, y, color | (data & 0x01)); SetPixel(x + 1, y, color | (data & 0x01)); SetPixel(x + 2, y, color | (data & 0x02)); SetPixel(x + 3, y, color | (data & 0x02)); SetPixel(x + 4, y, color | (data & 0x04)); SetPixel(x + 5, y, color | (data & 0x04)); SetPixel(x + 6, y, color | (data & 0x08)); SetPixel(x + 7, y, color | (data & 0x08)); SetPixel(x + 8, y, color | (data & 0x10)); SetPixel(x + 9, y, color | (data & 0x10)); SetPixel(x + 10, y, color | (data & 0x20)); SetPixel(x + 11, y, color | (data & 0x20)); SetPixel(x + 12, y, color | (data & 0x40)); SetPixel(x + 13, y, color | (data & 0x40)); } } private void DrawText80(int data, int x, int y) { int color = IsMonochrome ? ColorMono00 : ColorWhite00; int index = _charSet[data] * CharBitmapBytes; int mask = (_isTextInversed && !_memory.IsCharSetAlternate && (0x40 <= data) && (data <= 0x7F)) ? 0x7F : 0x00; for (int i = 0; i < TextHeight; i++, y++) { data = CharBitmap[index + i] ^ mask; SetPixel(x + 0, y, color | (data & 0x01)); SetPixel(x + 1, y, color | (data & 0x02)); SetPixel(x + 2, y, color | (data & 0x04)); SetPixel(x + 3, y, color | (data & 0x08)); SetPixel(x + 4, y, color | (data & 0x10)); SetPixel(x + 5, y, color | (data & 0x20)); SetPixel(x + 6, y, color | (data & 0x40)); } } private void DrawLores(int data, int x, int y) { if (IsMonochrome) { if ((x & 0x02) == 0x02) // odd cell { data = ((data << 2) & 0xCC) | ((data >> 2) & 0x33); } for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, data & 0x01); SetPixel(x + 1, y, data & 0x02); SetPixel(x + 2, y, data & 0x04); SetPixel(x + 3, y, data & 0x08); SetPixel(x + 4, y, data & 0x01); SetPixel(x + 5, y, data & 0x02); SetPixel(x + 6, y, data & 0x04); SetPixel(x + 7, y, data & 0x08); SetPixel(x + 8, y, data & 0x01); SetPixel(x + 9, y, data & 0x02); SetPixel(x + 10, y, data & 0x04); SetPixel(x + 11, y, data & 0x08); SetPixel(x + 12, y, data & 0x01); SetPixel(x + 13, y, data & 0x02); } for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, data & 0x10); SetPixel(x + 1, y, data & 0x20); SetPixel(x + 2, y, data & 0x40); SetPixel(x + 3, y, data & 0x80); SetPixel(x + 4, y, data & 0x10); SetPixel(x + 5, y, data & 0x20); SetPixel(x + 6, y, data & 0x40); SetPixel(x + 7, y, data & 0x80); SetPixel(x + 8, y, data & 0x10); SetPixel(x + 9, y, data & 0x20); SetPixel(x + 10, y, data & 0x40); SetPixel(x + 11, y, data & 0x80); SetPixel(x + 12, y, data & 0x10); SetPixel(x + 13, y, data & 0x20); } } else { int color = ColorLores[data & 0x0F]; for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, color); SetPixel(x + 1, y, color); SetPixel(x + 2, y, color); SetPixel(x + 3, y, color); SetPixel(x + 4, y, color); SetPixel(x + 5, y, color); SetPixel(x + 6, y, color); SetPixel(x + 7, y, color); SetPixel(x + 8, y, color); SetPixel(x + 9, y, color); SetPixel(x + 10, y, color); SetPixel(x + 11, y, color); SetPixel(x + 12, y, color); SetPixel(x + 13, y, color); } color = ColorLores[data >> 4]; for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, color); SetPixel(x + 1, y, color); SetPixel(x + 2, y, color); SetPixel(x + 3, y, color); SetPixel(x + 4, y, color); SetPixel(x + 5, y, color); SetPixel(x + 6, y, color); SetPixel(x + 7, y, color); SetPixel(x + 8, y, color); SetPixel(x + 9, y, color); SetPixel(x + 10, y, color); SetPixel(x + 11, y, color); SetPixel(x + 12, y, color); SetPixel(x + 13, y, color); } } } private void Draw7MLores(int data, int x, int y) { if (IsMonochrome) { if ((x & 0x02) == 0x02) // odd cell { data = ((data << 2) & 0xCC) | ((data >> 2) & 0x33); } for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, data & 0x01); SetPixel(x + 1, y, data & 0x01); SetPixel(x + 2, y, data & 0x02); SetPixel(x + 3, y, data & 0x02); SetPixel(x + 4, y, data & 0x04); SetPixel(x + 5, y, data & 0x04); SetPixel(x + 6, y, data & 0x08); SetPixel(x + 7, y, data & 0x08); SetPixel(x + 8, y, data & 0x01); SetPixel(x + 9, y, data & 0x01); SetPixel(x + 10, y, data & 0x02); SetPixel(x + 11, y, data & 0x02); SetPixel(x + 12, y, data & 0x04); SetPixel(x + 13, y, data & 0x04); } for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, data & 0x10); SetPixel(x + 1, y, data & 0x10); SetPixel(x + 2, y, data & 0x20); SetPixel(x + 3, y, data & 0x20); SetPixel(x + 4, y, data & 0x40); SetPixel(x + 5, y, data & 0x40); SetPixel(x + 6, y, data & 0x80); SetPixel(x + 7, y, data & 0x80); SetPixel(x + 8, y, data & 0x10); SetPixel(x + 9, y, data & 0x10); SetPixel(x + 10, y, data & 0x20); SetPixel(x + 11, y, data & 0x20); SetPixel(x + 12, y, data & 0x40); SetPixel(x + 13, y, data & 0x40); } } else { int color = Color7MLores[((x & 0x02) << 3) | (data & 0x0F)]; for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, color); SetPixel(x + 1, y, color); SetPixel(x + 2, y, color); SetPixel(x + 3, y, color); SetPixel(x + 4, y, color); SetPixel(x + 5, y, color); SetPixel(x + 6, y, color); SetPixel(x + 7, y, color); SetPixel(x + 8, y, color); SetPixel(x + 9, y, color); SetPixel(x + 10, y, color); SetPixel(x + 11, y, color); SetPixel(x + 12, y, color); SetPixel(x + 13, y, color); } color = Color7MLores[((x & 0x02) << 3) | (data >> 4)]; for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, color); SetPixel(x + 1, y, color); SetPixel(x + 2, y, color); SetPixel(x + 3, y, color); SetPixel(x + 4, y, color); SetPixel(x + 5, y, color); SetPixel(x + 6, y, color); SetPixel(x + 7, y, color); SetPixel(x + 8, y, color); SetPixel(x + 9, y, color); SetPixel(x + 10, y, color); SetPixel(x + 11, y, color); SetPixel(x + 12, y, color); SetPixel(x + 13, y, color); } } } private void DrawDLores(int data, int x, int y) { if (IsMonochrome) { if ((x & 0x01) == 0x00) // even half cell { data = ((data << 1) & 0xEE) | ((data >> 3) & 0x11); } for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, data & 0x01); SetPixel(x + 1, y, data & 0x02); SetPixel(x + 2, y, data & 0x04); SetPixel(x + 3, y, data & 0x08); SetPixel(x + 4, y, data & 0x01); SetPixel(x + 5, y, data & 0x02); SetPixel(x + 6, y, data & 0x04); } for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, data & 0x10); SetPixel(x + 1, y, data & 0x20); SetPixel(x + 2, y, data & 0x40); SetPixel(x + 3, y, data & 0x80); SetPixel(x + 4, y, data & 0x10); SetPixel(x + 5, y, data & 0x20); SetPixel(x + 6, y, data & 0x40); } } else { int color = ColorDLores[((x & 0x01) << 4) | (data & 0x0F)]; for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, color); SetPixel(x + 1, y, color); SetPixel(x + 2, y, color); SetPixel(x + 3, y, color); SetPixel(x + 4, y, color); SetPixel(x + 5, y, color); SetPixel(x + 6, y, color); } color = ColorDLores[((x & 0x01) << 4) | (data >> 4)]; for (int i = 0; i < LoresHeight; i++, y++) { SetPixel(x + 0, y, color); SetPixel(x + 1, y, color); SetPixel(x + 2, y, color); SetPixel(x + 3, y, color); SetPixel(x + 4, y, color); SetPixel(x + 5, y, color); SetPixel(x + 6, y, color); } } } private void DrawHires(int address, int x, int y) { if (IsMonochrome) { int data = _memory.ReadRamMainRegion02BF(address); SetPixel(x + 0, y, data & 0x01); SetPixel(x + 1, y, data & 0x01); SetPixel(x + 2, y, data & 0x02); SetPixel(x + 3, y, data & 0x02); SetPixel(x + 4, y, data & 0x04); SetPixel(x + 5, y, data & 0x04); SetPixel(x + 6, y, data & 0x08); SetPixel(x + 7, y, data & 0x08); SetPixel(x + 8, y, data & 0x10); SetPixel(x + 9, y, data & 0x10); SetPixel(x + 10, y, data & 0x20); SetPixel(x + 11, y, data & 0x20); SetPixel(x + 12, y, data & 0x40); SetPixel(x + 13, y, data & 0x40); } else { // 3 2 1 0 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // // - - - - - - - - 0 0 0 0 0 0 0 0 + + + + + + + + // H 1 0 H 6 5 4 3 2 1 0 H 1 0 int data = _memory.ReadRamMainRegion02BF(address) << 8; if (x < Width - CellWidth) { data |= _memory.ReadRamMainRegion02BF(address + 1); SetPixel(x + 14, y, ColorHires[((~x & 0x02) << 3) | ((data >> 4) & 0x08) | ((data << 1) & 0x06) | ((data >> 14) & 0x01)]); SetPixel(x + 15, y, ColorHires[((~x & 0x02) << 3) | ((data >> 4) & 0x08) | ((data << 1) & 0x06) | ((data >> 14) & 0x01)]); } if (x > 0) { data |= _memory.ReadRamMainRegion02BF(address - 1) << 16; SetPixel(x - 2, y, ColorHires[((~x & 0x02) << 3) | ((data >> 20) & 0x08) | ((data >> 6) & 0x04) | ((data >> 21) & 0x03)]); SetPixel(x - 1, y, ColorHires[((~x & 0x02) << 3) | ((data >> 20) & 0x08) | ((data >> 6) & 0x04) | ((data >> 21) & 0x03)]); } SetPixel(x + 0, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 7) & 0x06) | ((data >> 22) & 0x01)]); SetPixel(x + 1, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 7) & 0x06) | ((data >> 22) & 0x01)]); SetPixel(x + 2, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 8) & 0x07)]); SetPixel(x + 3, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 8) & 0x07)]); SetPixel(x + 4, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 9) & 0x07)]); SetPixel(x + 5, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 9) & 0x07)]); SetPixel(x + 6, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 10) & 0x07)]); SetPixel(x + 7, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 10) & 0x07)]); SetPixel(x + 8, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 11) & 0x07)]); SetPixel(x + 9, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data >> 11) & 0x07)]); SetPixel(x + 10, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x0F)]); SetPixel(x + 11, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x0F)]); SetPixel(x + 12, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data << 2) & 0x04) | ((data >> 13) & 0x03)]); SetPixel(x + 13, y, ColorHires[((x & 0x02) << 3) | ((data >> 12) & 0x08) | ((data << 2) & 0x04) | ((data >> 13) & 0x03)]); } } private void DrawNDHires(int address, int x, int y) { if (IsMonochrome) { int data = _memory.ReadRamMainRegion02BF(address); SetPixel(x + 0, y, data & 0x01); SetPixel(x + 1, y, data & 0x01); SetPixel(x + 2, y, data & 0x02); SetPixel(x + 3, y, data & 0x02); SetPixel(x + 4, y, data & 0x04); SetPixel(x + 5, y, data & 0x04); SetPixel(x + 6, y, data & 0x08); SetPixel(x + 7, y, data & 0x08); SetPixel(x + 8, y, data & 0x10); SetPixel(x + 9, y, data & 0x10); SetPixel(x + 10, y, data & 0x20); SetPixel(x + 11, y, data & 0x20); SetPixel(x + 12, y, data & 0x40); SetPixel(x + 13, y, data & 0x40); } else { // 3 2 1 0 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // // - - - - - - - - 0 0 0 0 0 0 0 0 + + + + + + + + // X 1 0 X 6 5 4 3 2 1 0 X 1 0 int data = _memory.ReadRamMainRegion02BF(address) << 8; if (x < Width - CellWidth) { data |= _memory.ReadRamMainRegion02BF(address + 1); SetPixel(x + 14, y, ColorHires[((~x & 0x02) << 3) | ((data << 1) & 0x06) | ((data >> 14) & 0x01)]); SetPixel(x + 15, y, ColorHires[((~x & 0x02) << 3) | ((data << 1) & 0x06) | ((data >> 14) & 0x01)]); } if (x > 0) { data |= _memory.ReadRamMainRegion02BF(address - 1) << 16; SetPixel(x - 2, y, ColorHires[((~x & 0x02) << 3) | ((data >> 6) & 0x04) | ((data >> 21) & 0x03)]); SetPixel(x - 1, y, ColorHires[((~x & 0x02) << 3) | ((data >> 6) & 0x04) | ((data >> 21) & 0x03)]); } SetPixel(x + 0, y, ColorHires[((x & 0x02) << 3) | ((data >> 7) & 0x06) | ((data >> 22) & 0x01)]); SetPixel(x + 1, y, ColorHires[((x & 0x02) << 3) | ((data >> 7) & 0x06) | ((data >> 22) & 0x01)]); SetPixel(x + 2, y, ColorHires[((~x & 0x02) << 3) | ((data >> 8) & 0x07)]); SetPixel(x + 3, y, ColorHires[((~x & 0x02) << 3) | ((data >> 8) & 0x07)]); SetPixel(x + 4, y, ColorHires[((x & 0x02) << 3) | ((data >> 9) & 0x07)]); SetPixel(x + 5, y, ColorHires[((x & 0x02) << 3) | ((data >> 9) & 0x07)]); SetPixel(x + 6, y, ColorHires[((~x & 0x02) << 3) | ((data >> 10) & 0x07)]); SetPixel(x + 7, y, ColorHires[((~x & 0x02) << 3) | ((data >> 10) & 0x07)]); SetPixel(x + 8, y, ColorHires[((x & 0x02) << 3) | ((data >> 11) & 0x07)]); SetPixel(x + 9, y, ColorHires[((x & 0x02) << 3) | ((data >> 11) & 0x07)]); SetPixel(x + 10, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x07)]); SetPixel(x + 11, y, ColorHires[((~x & 0x02) << 3) | ((data >> 12) & 0x07)]); SetPixel(x + 12, y, ColorHires[((x & 0x02) << 3) | ((data << 2) & 0x04) | ((data >> 13) & 0x03)]); SetPixel(x + 13, y, ColorHires[((x & 0x02) << 3) | ((data << 2) & 0x04) | ((data >> 13) & 0x03)]); } } private void DrawDHiresA(int address, int x, int y) { if (IsMonochrome) { if ((x & 0x2) == 0x00) // even cell { int data = ((_memory.ReadRamMainRegion02BF(address) << 7) & 0x80) | (_memory.ReadRamAuxRegion02BF(address) & 0x7F); SetPixel(x + 0, y, data & 0x01); SetPixel(x + 1, y, data & 0x02); SetPixel(x + 2, y, data & 0x04); SetPixel(x + 3, y, data & 0x08); SetPixel(x + 4, y, data & 0x10); SetPixel(x + 5, y, data & 0x20); SetPixel(x + 6, y, data & 0x40); SetPixel(x + 7, y, data & 0x80); } else { int data = ((_memory.ReadRamMainRegion02BF(address) << 9) & 0xE00) | ((_memory.ReadRamAuxRegion02BF(address) << 2) & 0x1FC) | ((_memory.ReadRamMainRegion02BF(address - 1) >> 5) & 0x003); SetPixel(x - 2, y, data & 0x01); SetPixel(x - 1, y, data & 0x02); SetPixel(x + 0, y, data & 0x04); SetPixel(x + 1, y, data & 0x08); SetPixel(x + 2, y, data & 0x10); SetPixel(x + 3, y, data & 0x20); SetPixel(x + 4, y, data & 0x40); SetPixel(x + 5, y, data & 0x80); SetPixel(x + 6, y, (data >> 8) & 0x01); SetPixel(x + 7, y, (data >> 8) & 0x02); SetPixel(x + 8, y, (data >> 8) & 0x04); SetPixel(x + 9, y, (data >> 8) & 0x08); } } else { if ((x & 0x2) == 0x00) // even cell { int data = ((_memory.ReadRamMainRegion02BF(address) << 7) & 0x80) | (_memory.ReadRamAuxRegion02BF(address) & 0x7F); SetPixel(x + 0, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 1, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 2, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 3, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 4, y, ColorDHires0 | (data >> 4)); SetPixel(x + 5, y, ColorDHires0 | (data >> 4)); SetPixel(x + 6, y, ColorDHires0 | (data >> 4)); SetPixel(x + 7, y, ColorDHires0 | (data >> 4)); } else { int data = ((_memory.ReadRamMainRegion02BF(address) << 9) & 0xE00) | ((_memory.ReadRamAuxRegion02BF(address) << 2) & 0x1FC) | ((_memory.ReadRamMainRegion02BF(address - 1) >> 5) & 0x003); SetPixel(x - 2, y, ColorDHires0 | (data & 0x0F)); SetPixel(x - 1, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 0, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 1, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 2, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 3, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 4, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 5, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 6, y, ColorDHires0 | (data >> 8)); SetPixel(x + 7, y, ColorDHires0 | (data >> 8)); SetPixel(x + 8, y, ColorDHires0 | (data >> 8)); SetPixel(x + 9, y, ColorDHires0 | (data >> 8)); } } } private void DrawDHiresM(int address, int x, int y) { if (IsMonochrome) { if ((x & 0x2) == 0x02) // odd cell { int data = ((_memory.ReadRamMainRegion02BF(address) << 1) & 0xFE) | ((_memory.ReadRamAuxRegion02BF(address) >> 6) & 0x01); SetPixel(x + 6, y, data & 0x01); SetPixel(x + 7, y, data & 0x02); SetPixel(x + 8, y, data & 0x04); SetPixel(x + 9, y, data & 0x08); SetPixel(x + 10, y, data & 0x10); SetPixel(x + 11, y, data & 0x20); SetPixel(x + 12, y, data & 0x40); SetPixel(x + 13, y, data & 0x80); } else { int data = ((_memory.ReadRamAuxRegion02BF(address + 1) << 10) & 0xC00) | ((_memory.ReadRamMainRegion02BF(address) << 3) & 0x3F8) | ((_memory.ReadRamAuxRegion02BF(address) >> 4) & 0x007); SetPixel(x + 4, y, data & 0x01); SetPixel(x + 5, y, data & 0x02); SetPixel(x + 6, y, data & 0x04); SetPixel(x + 7, y, data & 0x08); SetPixel(x + 8, y, data & 0x10); SetPixel(x + 9, y, data & 0x20); SetPixel(x + 10, y, data & 0x40); SetPixel(x + 11, y, data & 0x80); SetPixel(x + 12, y, (data >> 8) & 0x01); SetPixel(x + 13, y, (data >> 8) & 0x02); SetPixel(x + 14, y, (data >> 8) & 0x04); SetPixel(x + 15, y, (data >> 8) & 0x08); } } else { if ((x & 0x2) == 0x02) // odd cell { int data = ((_memory.ReadRamMainRegion02BF(address) << 1) & 0xFE) | ((_memory.ReadRamAuxRegion02BF(address) >> 6) & 0x01); SetPixel(x + 6, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 7, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 8, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 9, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 10, y, ColorDHires0 | (data >> 4)); SetPixel(x + 11, y, ColorDHires0 | (data >> 4)); SetPixel(x + 12, y, ColorDHires0 | (data >> 4)); SetPixel(x + 13, y, ColorDHires0 | (data >> 4)); } else { int data = ((_memory.ReadRamAuxRegion02BF(address + 1) << 10) & 0xC00) | ((_memory.ReadRamMainRegion02BF(address) << 3) & 0x3F8) | ((_memory.ReadRamAuxRegion02BF(address) >> 4) & 0x007); SetPixel(x + 4, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 5, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 6, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 7, y, ColorDHires0 | (data & 0x0F)); SetPixel(x + 8, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 9, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 10, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 11, y, ColorDHires0 | ((data >> 4) & 0x0F)); SetPixel(x + 12, y, ColorDHires0 | (data >> 8)); SetPixel(x + 13, y, ColorDHires0 | (data >> 8)); SetPixel(x + 14, y, ColorDHires0 | (data >> 8)); SetPixel(x + 15, y, ColorDHires0 | (data >> 8)); } } } #endregion #region Flush Methods private void FlushRowMode0(int y) { int address = (_memory.IsVideoPage2 ? 0x0800 : 0x0400) + AddressOffset[y]; for (int x = 0; x < CellColumns; x++) { if (_isCellDirty[CellColumns * y + x]) { _isCellDirty[CellColumns * y + x] = false; DrawLores(_memory.ReadRamMainRegion02BF(address + x), CellWidth * x, y); // lores } } } private void FlushRowMode1(int y) { int address = (_memory.IsVideoPage2 ? 0x0800 : 0x0400) + AddressOffset[y]; for (int x = 0; x < CellColumns; x++) { if (_isCellDirty[CellColumns * y + x]) { _isCellDirty[CellColumns * y + x] = false; DrawText40(_memory.ReadRamMainRegion02BF(address + x), CellWidth * x, y); // text40 } } } private void FlushRowMode2(int y) { int address = (_memory.IsVideoPage2 ? 0x0800 : 0x0400) + AddressOffset[y]; for (int x = 0; x < 2 * CellColumns; x += 2) { if (_isCellDirty[CellColumns * y + x / 2]) { _isCellDirty[CellColumns * y + x / 2] = false; DrawText80(_memory.ReadRamAuxRegion02BF(address + x / 2), CellWidth / 2 * (x + 0), y); // text80 DrawText80(_memory.ReadRamMainRegion02BF(address + x / 2), CellWidth / 2 * (x + 1), y); } } } private void FlushRowMode3(int y) { if (y < MixedHeight) { FlushRowMode0(y); // lores } else { FlushRowMode1(y); // text40 } } private void FlushRowMode4(int y) { if (y < MixedHeight) { FlushRowMode0(y); // lores } else { FlushRowMode2(y); // text80 } } private void FlushRowMode5(int y) { int address = _memory.IsVideoPage2 ? 0x4000 : 0x2000; for (int i = 0; i < CellHeight; i++, y++) { for (int x = 0; x < CellColumns; x++) { if (_isCellDirty[CellColumns * y + x]) { _isCellDirty[CellColumns * y + x] = false; DrawHires(address + AddressOffset[y] + x, CellWidth * x, y); // hires } } } } private void FlushRowMode6(int y) { if (y < MixedHeight) { FlushRowMode5(y); // hires } else { FlushRowMode1(y); // text40 } } private void FlushRowMode7(int y) { if (y < MixedHeight) { FlushRowMode5(y); // hires } else { FlushRowMode2(y); // text80 } } private void FlushRowMode8(int y) { int address = (_memory.IsVideoPage2 ? 0x0800 : 0x0400) + AddressOffset[y]; for (int x = 0; x < CellColumns; x++) { if (_isCellDirty[CellColumns * y + x]) { _isCellDirty[CellColumns * y + x] = false; Draw7MLores(_memory.ReadRamMainRegion02BF(address + x), CellWidth * x, y); // 7mlores } } } private void FlushRowMode9(int y) { int address = (_memory.IsVideoPage2 ? 0x0800 : 0x0400) + AddressOffset[y]; for (int x = 0; x < 2 * CellColumns; x += 2) { if (_isCellDirty[CellColumns * y + x / 2]) { _isCellDirty[CellColumns * y + x / 2] = false; DrawDLores(_memory.ReadRamAuxRegion02BF(address + x / 2), CellWidth / 2 * (x + 0), y); // dlores DrawDLores(_memory.ReadRamMainRegion02BF(address + x / 2), CellWidth / 2 * (x + 1), y); } } } private void FlushRowModeA(int y) { if (y < MixedHeight) { FlushRowMode8(y); // 7mlores } else { FlushRowMode1(y); // text40 } } private void FlushRowModeB(int y) { if (y < MixedHeight) { FlushRowMode9(y); // dlores } else { FlushRowMode2(y); // text80 } } private void FlushRowModeC(int y) { int address = _memory.IsVideoPage2 ? 0x4000 : 0x2000; for (int i = 0; i < CellHeight; i++, y++) { for (int x = 0; x < CellColumns; x++) { if (_isCellDirty[CellColumns * y + x]) { _isCellDirty[CellColumns * y + x] = false; DrawNDHires(address + AddressOffset[y] + x, CellWidth * x, y); // ndhires } } } } private void FlushRowModeD(int y) { int address = _memory.IsVideoPage2 ? 0x4000 : 0x2000; for (int i = 0; i < CellHeight; i++, y++) { for (int x = 0; x < CellColumns; x++) { if (_isCellDirty[CellColumns * y + x]) { _isCellDirty[CellColumns * y + x] = false; DrawDHiresA(address + AddressOffset[y] + x, CellWidth * x, y); // dhires DrawDHiresM(address + AddressOffset[y] + x, CellWidth * x, y); } } } } private void FlushRowModeE(int y) { if (y < MixedHeight) { FlushRowModeC(y); // ndhires } else { FlushRowMode1(y); // text40 } } private void FlushRowModeF(int y) { if (y < MixedHeight) { FlushRowModeD(y); // dhires } else { FlushRowMode2(y); // text80 } } #endregion private void FlushRowEvent() { int y = (_cyclesPerVSync - _cyclesPerVBlankPreset - Machine.Events.FindEvent(_resetVSyncEvent)) / CyclesPerHSync; FlushRowMode[_memory.VideoMode](y - CellHeight); // in arrears if (y < Height) { Machine.Events.AddEvent(CyclesPerFlush, _flushRowEvent); } else { IsVBlank = true; Machine.Events.AddEvent(_cyclesPerVBlank, _leaveVBlankEvent); } } private void FlushScreen() { var flushRowMode = FlushRowMode[_memory.VideoMode]; for (int y = 0; y < Height; y += CellHeight) { flushRowMode(y); } } private void InverseTextEvent() { _isTextInversed = !_isTextInversed; DirtyScreenText(); Machine.Events.AddEvent(_cyclesPerFlash, _inverseTextEvent); } private void LeaveVBlankEvent() { IsVBlank = false; Machine.Events.AddEvent(CyclesPerFlush, _flushRowEvent); } private void ResetVSyncEvent() { Machine.Events.AddEvent(_cyclesPerVSync, _resetVSyncEvent); } private void SetPalette() { _colorPalette[ColorMono00] = _colorBlack; _colorPalette[ColorMono01] = _colorMonochrome; _colorPalette[ColorMono02] = _colorMonochrome; _colorPalette[ColorMono04] = _colorMonochrome; _colorPalette[ColorMono08] = _colorMonochrome; _colorPalette[ColorMono10] = _colorMonochrome; _colorPalette[ColorMono20] = _colorMonochrome; _colorPalette[ColorMono40] = _colorMonochrome; _colorPalette[ColorMono80] = _colorMonochrome; _colorPalette[ColorWhite00] = _colorBlack; _colorPalette[ColorWhite01] = _colorWhite; _colorPalette[ColorWhite02] = _colorWhite; _colorPalette[ColorWhite04] = _colorWhite; _colorPalette[ColorWhite08] = _colorWhite; _colorPalette[ColorWhite10] = _colorWhite; _colorPalette[ColorWhite20] = _colorWhite; _colorPalette[ColorWhite40] = _colorWhite; _colorPalette[ColorWhite80] = _colorWhite; _colorPalette[ColorDHires0] = _colorBlack; _colorPalette[ColorDHires1] = _colorDarkBlue; _colorPalette[ColorDHires2] = _colorDarkGreen; _colorPalette[ColorDHires3] = _colorMediumBlue; _colorPalette[ColorDHires4] = _colorBrown; _colorPalette[ColorDHires5] = _colorLightGrey; _colorPalette[ColorDHires6] = _colorGreen; _colorPalette[ColorDHires7] = _colorAquamarine; _colorPalette[ColorDHires8] = _colorDeepRed; _colorPalette[ColorDHires9] = _colorPurple; _colorPalette[ColorDHiresA] = _colorDarkGrey; _colorPalette[ColorDHiresB] = _colorLightBlue; _colorPalette[ColorDHiresC] = _colorOrange; _colorPalette[ColorDHiresD] = _colorPink; _colorPalette[ColorDHiresE] = _colorYellow; _colorPalette[ColorDHiresF] = _colorWhite; DirtyScreen(); } private void SetPixel(int x, int y, int color) { VideoService.SetPixel(x, 2 * y, _colorPalette[color]); } private void SetScanner() { if ((_scannerOptions & ScannerOptions.Pal) != 0) { _vCountPreset = VCountPresetPal; _vLineLeaveVBlank = VLineLeaveVBlankPal; } else { _vCountPreset = VCountPresetNtsc; _vLineLeaveVBlank = VLineLeaveVBlankNtsc; } _cyclesPerVBlank = (_vLineLeaveVBlank - VLineEnterVBlank) * CyclesPerHSync; _cyclesPerVBlankPreset = (_vLineLeaveVBlank - VLineTriggerPreset) * CyclesPerHSync; // cycles during vblank after vcount preset [3-15, 3-16] _cyclesPerVSync = _vLineLeaveVBlank * CyclesPerHSync; _cyclesPerFlash = VSyncsPerFlash * _cyclesPerVSync; } [Newtonsoft.Json.JsonIgnore] public int ColorBlack { get { return _colorBlack; } set { _colorBlack = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorDarkBlue { get { return _colorDarkBlue; } set { _colorDarkBlue = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorDarkGreen { get { return _colorDarkGreen; } set { _colorDarkGreen = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorMediumBlue { get { return _colorMediumBlue; } set { _colorMediumBlue = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorBrown { get { return _colorBrown; } set { _colorBrown = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorLightGrey { get { return _colorLightGrey; } set { _colorLightGrey = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorGreen { get { return _colorGreen; } set { _colorGreen = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorAquamarine { get { return _colorAquamarine; } set { _colorAquamarine = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorDeepRed { get { return _colorDeepRed; } set { _colorDeepRed = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorPurple { get { return _colorPurple; } set { _colorPurple = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorDarkGrey { get { return _colorDarkGrey; } set { _colorDarkGrey = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorLightBlue { get { return _colorLightBlue; } set { _colorLightBlue = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorOrange { get { return _colorOrange; } set { _colorOrange = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorPink { get { return _colorPink; } set { _colorPink = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorYellow { get { return _colorYellow; } set { _colorYellow = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorWhite { get { return _colorWhite; } set { _colorWhite = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public int ColorMonochrome { get { return _colorMonochrome; } set { _colorMonochrome = value; SetPalette(); } } [Newtonsoft.Json.JsonIgnore] public bool IsMonochrome { get { return _isMonochrome; } set { _isMonochrome = value; DirtyScreen(); } } public ScannerOptions ScannerOptions { get { return _scannerOptions; } set { _scannerOptions = value; SetScanner(); } } public bool IsVBlank { get; private set; } private Action _flushRowEvent; private Action _inverseTextEvent; private Action _leaveVBlankEvent; private Action _resetVSyncEvent; private Memory _memory; public VideoService VideoService { get; private set; } private int _colorBlack; private int _colorDarkBlue; private int _colorDarkGreen; private int _colorMediumBlue; private int _colorBrown; private int _colorLightGrey; private int _colorGreen; private int _colorAquamarine; private int _colorDeepRed; private int _colorPurple; private int _colorDarkGrey; private int _colorLightBlue; private int _colorOrange; private int _colorPink; private int _colorYellow; private int _colorWhite; private int _colorMonochrome; [Newtonsoft.Json.JsonIgnore] // not sync relevant private bool _isMonochrome; private bool _isTextInversed; private ScannerOptions _scannerOptions; private int _cyclesPerVBlank; private int _cyclesPerVBlankPreset; private int _cyclesPerVSync; private int _cyclesPerFlash; private int _vCountPreset; private int _vLineLeaveVBlank; private ushort[] _charSet; private int[] _colorPalette = new int[ColorPaletteCount]; [Newtonsoft.Json.JsonIgnore] // everything is automatically dirtied on load, so no need to save private bool[] _isCellDirty = new bool[Height * CellColumns + 1]; // includes sentinel } }