using BizHawk.Common; using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { /// /// Render pixels to the screen /// public class CRTDevice : IVideoProvider { #region Devices private CPCBase _machine; private CRCT_6845 CRCT => _machine.CRCT; private AmstradGateArray GateArray => _machine.GateArray; #endregion #region Construction public CRTDevice(CPCBase machine) { _machine = machine; CurrentLine = new ScanLine(this); CRCT.AttachHSYNCCallback(OnHSYNC); CRCT.AttachVSYNCCallback(OnVSYNC); } #endregion #region Palettes /// /// The standard CPC Pallete (ordered by firmware #) /// http://www.cpcwiki.eu/index.php/CPC_Palette /// public static readonly int[] CPCFirmwarePalette = { Colors.ARGB(0x00, 0x00, 0x00), // Black Colors.ARGB(0x00, 0x00, 0x80), // Blue Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue Colors.ARGB(0x80, 0x00, 0x00), // Red Colors.ARGB(0x80, 0x00, 0x80), // Magenta Colors.ARGB(0x80, 0x00, 0xFF), // Mauve Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red Colors.ARGB(0xFF, 0x00, 0x80), // Purple Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta Colors.ARGB(0x00, 0x80, 0x00), // Green Colors.ARGB(0x00, 0x80, 0x80), // Cyan Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue Colors.ARGB(0x80, 0x80, 0x00), // Yellow Colors.ARGB(0x80, 0x80, 0x80), // White Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue Colors.ARGB(0xFF, 0x80, 0x00), // Orange Colors.ARGB(0xFF, 0x80, 0x80), // Pink Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan Colors.ARGB(0x80, 0xFF, 0x00), // Lime Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White }; /// /// The standard CPC Pallete (ordered by hardware #) /// http://www.cpcwiki.eu/index.php/CPC_Palette /// public static readonly int[] CPCHardwarePalette = { Colors.ARGB(0x80, 0x80, 0x80), // White Colors.ARGB(0x80, 0x80, 0x80), // White (duplicate) Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow Colors.ARGB(0x00, 0x00, 0x80), // Blue Colors.ARGB(0xFF, 0x00, 0x80), // Purple Colors.ARGB(0x00, 0x80, 0x80), // Cyan Colors.ARGB(0xFF, 0x80, 0x80), // Pink Colors.ARGB(0xFF, 0x00, 0x80), // Purple (duplicate) Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow (duplicate) Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta Colors.ARGB(0xFF, 0x80, 0x00), // Orange Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta Colors.ARGB(0x00, 0x00, 0x80), // Blue (duplicate) Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green (duplicate) Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan Colors.ARGB(0x00, 0x00, 0x00), // Black Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue Colors.ARGB(0x00, 0x80, 0x00), // Green Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue Colors.ARGB(0x80, 0x00, 0x80), // Magenta Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green Colors.ARGB(0x80, 0xFF, 0x00), // Lime Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan Colors.ARGB(0x80, 0x00, 0x00), // Red Colors.ARGB(0x80, 0x00, 0xFF), // Mauve Colors.ARGB(0x80, 0x80, 0x00), // Yellow Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue }; #endregion #region Public Stuff /// /// The current scanline that is being added to /// (will be processed and committed to the screen buffer every HSYNC) /// public ScanLine CurrentLine; /// /// The number of top border scanlines to ommit when rendering /// public int TopLinesToTrim = 20; /// /// Count of rendered scanlines this frame /// public int ScanlineCounter = 0; /// /// Video buffer processing /// public int[] ProcessVideoBuffer() { return ScreenBuffer; } /// /// Sets up buffers and the like at the start of a frame /// public void SetupVideo() { if (BufferHeight == 576) return; BufferWidth = 800; BufferHeight = 576; VirtualWidth = BufferWidth / 2; VirtualHeight = BufferHeight / 2; ScreenBuffer = new int[BufferWidth * BufferHeight]; } /// /// Fired when the CRCT flags HSYNC /// public void OnHSYNC() { } /// /// Fired when the CRCT flags VSYNC /// public void OnVSYNC() { } #endregion #region IVideoProvider /// /// Video output buffer /// public int[] ScreenBuffer; private int _virtualWidth; private int _virtualHeight; private int _bufferWidth; private int _bufferHeight; public int BackgroundColor { get { return CPCHardwarePalette[0]; } } public int VirtualWidth { get { return _virtualWidth; } set { _virtualWidth = value; } } public int VirtualHeight { get { return _virtualHeight; } set { _virtualHeight = value; } } public int BufferWidth { get { return _bufferWidth; } set { _bufferWidth = value; } } public int BufferHeight { get { return _bufferHeight; } set { _bufferHeight = value; } } public int VsyncNumerator { get { return GateArray.Z80ClockSpeed * 50; } set { } } public int VsyncDenominator { get { return GateArray.Z80ClockSpeed; } } public int[] GetVideoBuffer() { return ProcessVideoBuffer(); } public void SetupScreenSize() { BufferWidth = 1024; // 512; BufferHeight = 768; VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; ScreenBuffer = new int[BufferWidth * BufferHeight]; croppedBuffer = ScreenBuffer; } protected int[] croppedBuffer; #endregion #region Serialization public void SyncState(Serializer ser) { ser.BeginSection("CRT"); ser.Sync("BufferWidth", ref _bufferWidth); ser.Sync("BufferHeight", ref _bufferHeight); ser.Sync("VirtualHeight", ref _virtualHeight); ser.Sync("VirtualWidth", ref _virtualWidth); ser.Sync(nameof(ScreenBuffer), ref ScreenBuffer, false); ser.Sync(nameof(ScanlineCounter), ref ScanlineCounter); ser.EndSection(); } #endregion } /// /// Represents a single scanline buffer /// public class ScanLine { /// /// Array of character information /// public Character[] Characters; /// /// The screenmode that was set at the start of this scanline /// public int ScreenMode = 1; /// /// The scanline number (0 based) /// public int LineIndex; /// /// The calling CRT device /// private CRTDevice CRT; public ScanLine(CRTDevice crt) { Reset(); CRT = crt; } // To be run after scanline has been fully processed public void InitScanline(int screenMode, int lineIndex) { Reset(); ScreenMode = screenMode; LineIndex = lineIndex; } /// /// Adds a single scanline character into the matrix /// public void AddScanlineCharacter(int index, RenderPhase phase, byte vid1, byte vid2, int[] pens) { if (index >= 64) { return; } switch (phase) { case RenderPhase.BORDER: AddBorderValue(index, CRTDevice.CPCHardwarePalette[pens[16]]); break; case RenderPhase.DISPLAY: AddDisplayValue(index, vid1, vid2, pens); break; default: AddSyncValue(index, phase); break; } } /// /// Adds a HSYNC, VSYNC or HSYNC+VSYNC character into the scanline /// private void AddSyncValue(int charIndex, RenderPhase phase) { Characters[charIndex].Phase = phase; Characters[charIndex].Pixels = new int[0]; } /// /// Adds a border character into the scanline /// private void AddBorderValue(int charIndex, int colourValue) { Characters[charIndex].Phase = RenderPhase.BORDER; switch (ScreenMode) { case 0: Characters[charIndex].Pixels = new int[4]; break; case 1: Characters[charIndex].Pixels = new int[8]; break; case 2: Characters[charIndex].Pixels = new int[16]; break; case 3: Characters[charIndex].Pixels = new int[8]; break; } for (int i = 0; i < Characters[charIndex].Pixels.Length; i++) { Characters[charIndex].Pixels[i] = colourValue; } } /// /// Adds a display character into the scanline /// Pixel matrix is calculated based on the current ScreenMode /// public void AddDisplayValue(int charIndex, byte vid1, byte vid2, int[] pens) { Characters[charIndex].Phase = RenderPhase.DISPLAY; // generate pixels based on screen mode switch (ScreenMode) { // 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels) // RECT case 0: Characters[charIndex].Pixels = new int[16]; int m0Count = 0; int pix = vid1 & 0xaa; pix = ((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02 << 2)); Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; pix = vid1 & 0x55; pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01 << 3))); Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; pix = vid2 & 0xaa; pix = ((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02 << 2)); Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; pix = vid2 & 0x55; pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01 << 3))); Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; /* int m0B0P0i = vid1 & 0xaa; int m0B0P0 = ((m0B0P0i & 0x80) >> 7) | ((m0B0P0i & 0x08) >> 2) | ((m0B0P0i & 0x20) >> 3) | ((m0B0P0i & 0x02 << 2)); int m0B0P1i = vid1 & 85; int m0B0P1 = ((m0B0P1i & 0x40) >> 6) | ((m0B0P1i & 0x04) >> 1) | ((m0B0P1i & 0x10) >> 2) | ((m0B0P1i & 0x01 << 3)); Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P0]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P0]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P1]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P1]]; int m0B1P0i = vid2 & 170; int m0B1P0 = ((m0B1P0i & 0x80) >> 7) | ((m0B1P0i & 0x08) >> 2) | ((m0B1P0i & 0x20) >> 3) | ((m0B1P0i & 0x02 << 2)); int m0B1P1i = vid2 & 85; int m0B1P1 = ((m0B1P1i & 0x40) >> 6) | ((m0B1P1i & 0x04) >> 1) | ((m0B1P1i & 0x10) >> 2) | ((m0B1P1i & 0x01 << 3)); Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P0]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P0]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]]; */ break; // 2 bits per pixel - 2 bytes - 8 pixels (16 CRT pixels) // SQUARE case 1: Characters[charIndex].Pixels = new int[8]; int m1Count = 0; int m1B0P0 = (((vid1 & 0x80) >> 7) | ((vid1 & 0x08) >> 2)); int m1B0P1 = (((vid1 & 0x40) >> 6) | ((vid1 & 0x04) >> 1)); int m1B0P2 = (((vid1 & 0x20) >> 5) | ((vid1 & 0x02))); int m1B0P3 = (((vid1 & 0x10) >> 4) | ((vid1 & 0x01) << 1)); Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P0]]; Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P1]]; Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P2]]; Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P3]]; int m1B1P0 = (((vid2 & 0x80) >> 7) | ((vid2 & 0x08) >> 2)); int m1B1P1 = (((vid2 & 0x40) >> 6) | ((vid2 & 0x04) >> 1)); int m1B1P2 = (((vid2 & 0x20) >> 5) | ((vid2 & 0x02))); int m1B1P3 = (((vid2 & 0x10) >> 4) | ((vid2 & 0x01) << 1)); Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P0]]; Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P1]]; Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P2]]; Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P3]]; break; // 1 bit per pixel - 2 bytes - 16 pixels (16 CRT pixels) // RECT case 2: Characters[charIndex].Pixels = new int[16]; int m2Count = 0; int[] pixBuff = new int[16]; for (int bit = 7; bit >= 0; bit--) { int val = vid1.Bit(bit) ? 1 : 0; Characters[charIndex].Pixels[m2Count++] = CRTDevice.CPCHardwarePalette[pens[val]]; } for (int bit = 7; bit >= 0; bit--) { int val = vid2.Bit(bit) ? 1 : 0; Characters[charIndex].Pixels[m2Count++] = CRTDevice.CPCHardwarePalette[pens[val]]; } break; // 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels) // RECT case 3: Characters[charIndex].Pixels = new int[4]; int m3Count = 0; int m3B0P0i = vid1 & 170; int m3B0P0 = ((m3B0P0i & 0x80) >> 7) | ((m3B0P0i & 0x08) >> 2) | ((m3B0P0i & 0x20) >> 3) | ((m3B0P0i & 0x02 << 2)); int m3B0P1i = vid1 & 85; int m3B0P1 = ((m3B0P1i & 0x40) >> 6) | ((m3B0P1i & 0x04) >> 1) | ((m3B0P1i & 0x10) >> 2) | ((m3B0P1i & 0x01 << 3)); Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B0P0]]; Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B0P1]]; int m3B1P0i = vid1 & 170; int m3B1P0 = ((m3B1P0i & 0x80) >> 7) | ((m3B1P0i & 0x08) >> 2) | ((m3B1P0i & 0x20) >> 3) | ((m3B1P0i & 0x02 << 2)); int m3B1P1i = vid1 & 85; int m3B1P1 = ((m3B1P1i & 0x40) >> 6) | ((m3B1P1i & 0x04) >> 1) | ((m3B1P1i & 0x10) >> 2) | ((m3B1P1i & 0x01 << 3)); Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B1P0]]; Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B1P1]]; break; } } /// /// Returns the number of pixels decoded in this scanline (border and display) /// private int GetPixelCount() { int cnt = 0; foreach (var c in Characters) { if (c.Pixels != null) cnt += c.Pixels.Length; } return cnt; } /// /// Called at the start of HSYNC /// Processes and adds the scanline to the Screen Buffer /// public void CommitScanline() { int hScale = 1; int vScale = 1; switch (ScreenMode) { case 0: hScale = 1; vScale = 2; break; case 1: case 3: hScale = 2; vScale = 2; break; case 2: hScale = 1; vScale = 2; break; } int hPix = GetPixelCount() * hScale; //int hPix = GetPixelCount() * 2; int leftOver = CRT.BufferWidth - hPix; int lPad = leftOver / 2; int rPad = lPad; int rem = leftOver % 2; if (rem != 0) rPad += rem; if (LineIndex < CRT.TopLinesToTrim) { return; } // render out the scanline int pCount = (LineIndex - CRT.TopLinesToTrim) * vScale * CRT.BufferWidth; // vScale for (int s = 0; s < vScale; s++) { // left padding for (int lP = 0; lP < lPad; lP++) { CRT.ScreenBuffer[pCount++] = 0; } // border and display foreach (var c in Characters) { if (c.Pixels == null || c.Pixels.Length == 0) continue; for (int p = 0; p < c.Pixels.Length; p++) { // hScale for (int h = 0; h < hScale; h++) { CRT.ScreenBuffer[pCount++] = c.Pixels[p]; } //CRT.ScreenBuffer[pCount++] = c.Pixels[p]; } } // right padding for (int rP = 0; rP < rPad; rP++) { CRT.ScreenBuffer[pCount++] = 0; } if (pCount != hPix) { } CRT.ScanlineCounter++; } } public void Reset() { ScreenMode = 1; Characters = new Character[64]; for (int i = 0; i < Characters.Length; i++) { Characters[i] = new Character(); } } } /// /// Contains data relating to one character written on one scanline /// public class Character { /// /// Array of pixels generated for this character /// public int[] Pixels; /// /// The type (NONE/BORDER/DISPLAY/HSYNC/VSYNC/HSYNC+VSYNC /// public RenderPhase Phase = RenderPhase.NONE; public Character() { Pixels = new int[0]; } } [Flags] public enum RenderPhase : int { /// /// Nothing /// NONE = 0, /// /// Border is being rendered /// BORDER = 1, /// /// Display rendered from video RAM /// DISPLAY = 2, /// /// HSYNC in progress /// HSYNC = 3, /// /// VSYNC in process /// VSYNC = 4, /// /// HSYNC occurs within a VSYNC /// HSYNCandVSYNC = 5 } }