using BizHawk.Common; using BizHawk.Emulation.Common; using System; using System.Collections.Generic; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// /// Uncommitted logic array implementation (ULA) /// public abstract class ULA : IVideoProvider { #region Other Devices /// /// The emulated spectrum /// protected SpectrumBase _machine; /// /// The CPU monitor class /// protected CPUMonitor CPUMon; #endregion #region Construction & Initialisation public ULA(SpectrumBase machine) { _machine = machine; CPUMon = _machine.CPUMon; borderType = _machine.Spectrum.SyncSettings.BorderType; } #endregion #region Palettes /// /// The standard ULA palette /// private static readonly int[] ULAPalette = { Colors.ARGB(0x00, 0x00, 0x00), // Black Colors.ARGB(0x00, 0x00, 0xD7), // Blue Colors.ARGB(0xD7, 0x00, 0x00), // Red Colors.ARGB(0xD7, 0x00, 0xD7), // Magenta Colors.ARGB(0x00, 0xD7, 0x00), // Green Colors.ARGB(0x00, 0xD7, 0xD7), // Cyan Colors.ARGB(0xD7, 0xD7, 0x00), // Yellow Colors.ARGB(0xD7, 0xD7, 0xD7), // White Colors.ARGB(0x00, 0x00, 0x00), // Bright Black Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White }; #endregion #region Timing /// /// The CPU speed /// public int ClockSpeed; /// /// Length of frame in T-State cycles /// public int FrameCycleLength; /// /// The T-State at which the interrupt should be raised within the frame /// public int InterruptStartTime; /// /// The period for which the interrupt should he held /// (simulated /INT pin held low) /// public int InterruptLength; /// /// Contention offset /// public int ContentionOffset; /// /// Arbitrary offset for render table generation /// public int RenderTableOffset; /// /// The offset when return floating bus bytes /// public int FloatingBusOffset; /// /// The time in T-States for one scanline to complete /// public int ScanlineTime; /// /// T-States at the left border /// public int BorderLeftTime; /// /// T-States at the right border /// public int BorderRightTime; public int FirstPaperLine; public int FirstPaperTState; public bool Border4T; public int Border4TStage; #endregion #region Interrupt Generation /// /// Signs that an interrupt has been raised in this frame. /// protected bool InterruptRaised; public long ULACycleCounter; public long LastULATick; public bool FrameEnd; /// /// Cycles the ULA clock /// Handles interrupt generation /// public virtual void CycleClock(long totalCycles) { // render the screen if (_machine._render) RenderScreen((int)_machine.CurrentFrameCycle); // has more than one cycle past since this last ran // (this can be true if contention has taken place) var ticksToProcess = totalCycles - LastULATick; // store the current cycle LastULATick = totalCycles; // process the cycles past as well as the upcoming one for (int i = 0; i < ticksToProcess; i++) { ULACycleCounter++; if (InterruptRaised) { // /INT pin is currently being held low if (ULACycleCounter < InterruptLength + InterruptStartTime) { // ULA should still hold the /INT pin low _machine.CPU.FlagI = true; } else { // its time (or past time) to stop holding the /INT pin low _machine.CPU.FlagI = false; InterruptRaised = false; } } else { // interrupt is currently not raised if (ULACycleCounter == FrameLength + InterruptStartTime) { // time to raise the interrupt InterruptRaised = true; _machine.CPU.FlagI = true; FrameEnd = true; ULACycleCounter = InterruptStartTime; CalcFlashCounter(); } } } } /// /// Flash processing /// public void CalcFlashCounter() { flashCounter++; if (flashCounter > 15) { flashOn = !flashOn; flashCounter = 0; } } #endregion #region Screen Layout /// /// Total pixels in one display row /// protected int ScreenWidth; /// /// Total pixels in one display column /// protected int ScreenHeight; /// /// Total pixels in top border /// protected int BorderTopHeight; /// /// Total pixels in bottom border /// protected int BorderBottomHeight; /// /// Total pixels in left border width /// protected int BorderLeftWidth; /// /// Total pixels in right border width /// protected int BorderRightWidth; /// /// Total pixels in one scanline /// protected int ScanLineWidth; #endregion #region State /// /// The last T-State cycle at which the screen was rendered /// public int LastTState; /// /// Flash state /// public bool flashOn; private int flashCounter; protected byte fetchB1; protected byte fetchA1; protected byte fetchB2; protected byte fetchA2; protected int ink; protected int paper; protected int fetchBorder; protected int bright; protected int flash; public int palPaper; public int palInk; public int BorderColor = 7; #endregion #region Conversions public int FrameLength => FrameCycleLength; #endregion #region Rendering Configuration /// /// Holds all information regarding rendering the screen based on the current T-State /// public RenderTable RenderingTable; /// /// Holds all information regarding rendering the screen based on the current T-State /// public class RenderTable { /// /// The ULA device /// private ULA _ula; /// /// Array of rendercycle entries /// Starting from the interrupt /// public RenderCycle[] Renderer; /// /// The emulated machine /// public MachineType _machineType; public int Offset; /// /// Constructor /// public RenderTable(ULA ula, MachineType machineType) { _ula = ula; _machineType = machineType; Renderer = new RenderCycle[_ula.FrameCycleLength]; InitRenderer(machineType); } /// /// Initializes the renderer /// private void InitRenderer(MachineType machineType) { for (var t = 0; t < _ula.FrameCycleLength; t++) { var tStateScreen = t + _ula.RenderTableOffset;// + _ula.InterruptStartTime; if (tStateScreen < 0) tStateScreen += _ula.FrameCycleLength; else if (tStateScreen >= _ula.FrameCycleLength) tStateScreen -= _ula.FrameCycleLength; CalculateRenderItem(t, tStateScreen / _ula.ScanlineTime, tStateScreen % _ula.ScanlineTime); } CreateContention(machineType); } private void CalculateRenderItem(int item, int line, int pix) { Renderer[item] = new RenderCycle(); Renderer[item].RAction = RenderAction.None; int pitchWidth = _ula.ScreenWidth + _ula.BorderRightWidth + _ula.BorderLeftWidth; int scrPix = pix - _ula.FirstPaperTState; int scrLin = line - _ula.FirstPaperLine; if ((line >= (_ula.FirstPaperLine - _ula.BorderTopHeight)) && (line < (_ula.FirstPaperLine + 192 + _ula.BorderBottomHeight)) && (pix >= (_ula.FirstPaperTState - _ula.BorderLeftTime)) && (pix < (_ula.FirstPaperTState + 128 + _ula.BorderRightTime))) { // visibleArea (vertical) if ((line >= _ula.FirstPaperLine) && (line < (_ula.FirstPaperLine + 192)) && (pix >= _ula.FirstPaperTState) && (pix < (_ula.FirstPaperTState + 128))) { // pixel area switch (scrPix & 7) { case 0: Renderer[item].RAction = RenderAction.Shift1AndFetchByte2; // shift 1 + fetch B2 // +4 = prefetch! Renderer[item].ByteAddress = CalculateByteAddress(scrPix + 4, scrLin); break; case 1: Renderer[item].RAction = RenderAction.Shift1AndFetchAttribute2; // shift 1 + fetch A2 // +3 = prefetch! Renderer[item].AttributeAddress = CalculateAttributeAddress(scrPix + 3, scrLin); break; case 2: Renderer[item].RAction = RenderAction.Shift1; // shift 1 break; case 3: Renderer[item].RAction = RenderAction.Shift1Last; // shift 1 (last) break; case 4: Renderer[item].RAction = RenderAction.Shift2; // shift 2 break; case 5: Renderer[item].RAction = RenderAction.Shift2; // shift 2 break; case 6: if (pix < (_ula.FirstPaperTState + 128 - 2)) { Renderer[item].RAction = RenderAction.Shift2AndFetchByte1; // shift 2 + fetch B2 } else { Renderer[item].RAction = RenderAction.Shift2; // shift 2 } // +2 = prefetch! Renderer[item].ByteAddress = CalculateByteAddress(scrPix + 2, scrLin); break; case 7: if (pix < (_ula.FirstPaperTState + 128 - 2)) { //??? Renderer[item].RAction = RenderAction.Shift2AndFetchAttribute1; // shift 2 + fetch A2 } else { Renderer[item].RAction = RenderAction.Shift2; // shift 2 } // +1 = prefetch! Renderer[item].AttributeAddress = CalculateAttributeAddress(scrPix + 1, scrLin); break; } } else if ((line >= _ula.FirstPaperLine) && (line < (_ula.FirstPaperLine + 192)) && (pix == (_ula.FirstPaperTState - 2))) // border & fetch B1 { Renderer[item].RAction = RenderAction.BorderAndFetchByte1; // border & fetch B1 // +2 = prefetch! Renderer[item].ByteAddress = CalculateByteAddress(scrPix + 2, scrLin); } else if ((line >= _ula.FirstPaperLine) && (line < (_ula.FirstPaperLine + 192)) && (pix == (_ula.FirstPaperTState - 1))) // border & fetch A1 { Renderer[item].RAction = RenderAction.BorderAndFetchAttribute1; // border & fetch A1 // +1 = prefetch! Renderer[item].AttributeAddress = CalculateAttributeAddress(scrPix + 1, scrLin); } else { Renderer[item].RAction = RenderAction.Border; // border } int wy = line - (_ula.FirstPaperLine - _ula.BorderTopHeight); int wx = (pix - (_ula.FirstPaperTState - _ula.BorderLeftTime)) * 2; Renderer[item].LineOffset = wy * pitchWidth + wx; } } private void CreateContention(MachineType machineType) { int[] conPattern = new int[8]; switch (machineType) { case MachineType.ZXSpectrum16: case MachineType.ZXSpectrum48: case MachineType.ZXSpectrum128: case MachineType.ZXSpectrum128Plus2: conPattern = new int[] { 6, 5, 4, 3, 2, 1, 0, 0 }; break; case MachineType.ZXSpectrum128Plus2a: case MachineType.ZXSpectrum128Plus3: conPattern = new int[] { 1, 0, 7, 6, 5, 4, 3, 2 }; break; } // calculate contention values for (int t = 0; t < _ula.FrameCycleLength; t++) { int shifted = t + _ula.RenderTableOffset + _ula.ContentionOffset; // _ula.InterruptStartTime; if (shifted < 0) shifted += _ula.FrameCycleLength; shifted %= _ula.FrameCycleLength; Renderer[t].ContentionValue = 0; int line = shifted / _ula.ScanlineTime; int pix = shifted % _ula.ScanlineTime; if (line < _ula.FirstPaperLine || line >= (_ula.FirstPaperLine + 192)) { Renderer[t].ContentionValue = 0; continue; } int scrPix = pix - _ula.FirstPaperTState; if (scrPix < 0 || scrPix >= 128) { Renderer[t].ContentionValue = 0; continue; } int pixByte = scrPix % 8; Renderer[t].ContentionValue = conPattern[pixByte]; } } private ushort CalculateByteAddress(int x, int y) { x >>= 2; var vp = x | (y << 5); return (ushort)((vp & 0x181F) | ((vp & 0x0700) >> 3) | ((vp & 0x00E0) << 3)); } private ushort CalculateAttributeAddress(int x, int y) { x >>= 2; var ap = x | ((y >> 3) << 5); return (ushort)(6144 + ap); } /// /// Render/contention information for a single T-State /// public class RenderCycle { /// /// The ULA render action at this T-State /// public RenderAction RAction; /// /// The contention value at this T-State /// public int ContentionValue; /// /// The screen byte address at this T-State /// public ushort ByteAddress; /// /// The screen attribute address at this T-State /// public ushort AttributeAddress; /// /// The byte address returned by the floating bus at this T-State /// public ushort FloatingBusAddress; /// /// The offset /// public int LineOffset; } public enum RenderAction { None, Border, BorderAndFetchByte1, BorderAndFetchAttribute1, Shift1AndFetchByte2, Shift1AndFetchAttribute2, Shift1, Shift1Last, Shift2, Shift2Last, Shift2AndFetchByte1, Shift2AndFetchAttribute1 } } #endregion #region Render Methods /// /// Renders to the screen buffer based on the current cycle /// public void RenderScreen(int toCycle) { // check boundaries if (toCycle > FrameCycleLength) toCycle = FrameCycleLength; // render the required number of cycles for (int t = LastTState; t < toCycle; t++) { if (!Border4T || (t & 3) == Border4TStage) { fetchBorder = BorderColor; } else { } //fetchBorder = BorderColor; // get the table entry var item = RenderingTable.Renderer[t]; switch (item.RAction) { case RenderTable.RenderAction.None: break; case RenderTable.RenderAction.Border: ScreenBuffer[item.LineOffset] = ULAPalette[fetchBorder]; ScreenBuffer[item.LineOffset + 1] = ULAPalette[fetchBorder]; break; case RenderTable.RenderAction.BorderAndFetchByte1: ScreenBuffer[item.LineOffset] = ULAPalette[fetchBorder]; ScreenBuffer[item.LineOffset + 1] = ULAPalette[fetchBorder]; fetchB1 = _machine.FetchScreenMemory(item.ByteAddress); break; case RenderTable.RenderAction.BorderAndFetchAttribute1: ScreenBuffer[item.LineOffset] = ULAPalette[fetchBorder]; ScreenBuffer[item.LineOffset + 1] = ULAPalette[fetchBorder]; fetchA1 = _machine.FetchScreenMemory(item.AttributeAddress); ProcessInkPaper(fetchA1); break; case RenderTable.RenderAction.Shift1AndFetchByte2: ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper; fetchB1 <<= 2; fetchB2 = _machine.FetchScreenMemory(item.ByteAddress); break; case RenderTable.RenderAction.Shift1AndFetchAttribute2: ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper; fetchB1 <<= 2; fetchA2 = _machine.FetchScreenMemory(item.AttributeAddress); break; case RenderTable.RenderAction.Shift1: ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper; fetchB1 <<= 2; break; case RenderTable.RenderAction.Shift1Last: ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper; fetchB1 <<= 2; ProcessInkPaper(fetchA2); break; case RenderTable.RenderAction.Shift2: ScreenBuffer[item.LineOffset] = ((fetchB2 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB2 & 0x40) != 0) ? palInk : palPaper; fetchB2 <<= 2; break; case RenderTable.RenderAction.Shift2AndFetchByte1: ScreenBuffer[item.LineOffset] = ((fetchB2 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB2 & 0x40) != 0) ? palInk : palPaper; fetchB2 <<= 2; fetchB1 = _machine.FetchScreenMemory(item.ByteAddress); break; case RenderTable.RenderAction.Shift2AndFetchAttribute1: ScreenBuffer[item.LineOffset] = ((fetchB2 & 0x80) != 0) ? palInk : palPaper; ScreenBuffer[item.LineOffset + 1] = ((fetchB2 & 0x40) != 0) ? palInk : palPaper; fetchB2 <<= 2; fetchA1 = _machine.FetchScreenMemory(item.AttributeAddress); ProcessInkPaper(fetchA1); break; } } LastTState = toCycle; } private void ProcessInkPaper(byte attrData) { bright = (attrData & 0x40) >> 3; flash = (attrData & 0x80) >> 7; ink = (attrData & 0x07); paper = ((attrData >> 3) & 0x7); palInk = ULAPalette[ink + bright]; palPaper = ULAPalette[paper + bright]; // swap paper and ink when flash is on if (flashOn && (flash != 0)) { int temp = palInk; palInk = palPaper; palPaper = temp; } } /// /// Generates the port lookup table for +2a/+3 allowed floating bus ports /// public void GenerateP3PortTable() { List table = new List(); for (int i = 0; i < 0x1000; i++) { ushort r = (ushort)(1 + (4 * i)); if (r > 4093) break; table.Add(r); } Plus3FBPortTable = table.ToArray(); } private ushort[] Plus3FBPortTable = new ushort[1]; /// /// Returns floating bus value (if available) /// public void ReadFloatingBus(int tstate, ref int result, ushort port) { tstate += FloatingBusOffset; if (tstate >= RenderingTable.Renderer.Length) tstate -= RenderingTable.Renderer.Length; if (tstate < 0) tstate += RenderingTable.Renderer.Length; var item = RenderingTable.Renderer[tstate]; switch (RenderingTable._machineType) { case MachineType.ZXSpectrum16: case MachineType.ZXSpectrum48: case MachineType.ZXSpectrum128: case MachineType.ZXSpectrum128Plus2: switch (item.RAction) { case RenderTable.RenderAction.BorderAndFetchByte1: case RenderTable.RenderAction.Shift1AndFetchByte2: case RenderTable.RenderAction.Shift2AndFetchByte1: result = _machine.FetchScreenMemory(item.ByteAddress); break; case RenderTable.RenderAction.BorderAndFetchAttribute1: case RenderTable.RenderAction.Shift1AndFetchAttribute2: case RenderTable.RenderAction.Shift2AndFetchAttribute1: result = _machine.FetchScreenMemory(item.AttributeAddress); break; default: break; } break; case MachineType.ZXSpectrum128Plus2a: case MachineType.ZXSpectrum128Plus3: // http://sky.relative-path.com/zx/floating_bus.html if (_machine.PagingDisabled) { result = 0xff; break; } // check whether fb is found on this port ushort pLook = Array.Find(Plus3FBPortTable, s => s == port); if (pLook == 0) { result = 0xff; break; } // floating bus on +2a/+3 always returns a byte with Bit0 set switch (item.RAction) { case RenderTable.RenderAction.BorderAndFetchByte1: case RenderTable.RenderAction.Shift1AndFetchByte2: case RenderTable.RenderAction.Shift2AndFetchByte1: result = (byte)(_machine.FetchScreenMemory(item.ByteAddress) | 0x01); break; case RenderTable.RenderAction.BorderAndFetchAttribute1: case RenderTable.RenderAction.Shift1AndFetchAttribute2: case RenderTable.RenderAction.Shift2AndFetchAttribute1: result = (byte)(_machine.FetchScreenMemory(item.AttributeAddress) | 0x01); break; default: result = (byte)(_machine.LastContendedReadByte | 0x01); break; } break; } } #endregion #region Contention /// /// Returns the contention value for the current t-state /// public int GetContentionValue() { return GetContentionValue((int)_machine.CurrentFrameCycle); } /// /// Returns the contention value for the supplied t-state /// public int GetContentionValue(int tstate) { if (tstate >= FrameCycleLength) tstate -= FrameCycleLength; if (tstate < 0) tstate += FrameCycleLength; return RenderingTable.Renderer[tstate].ContentionValue; } /// /// Returns the contention value for the supplied t-state /// public int GetPortContentionValue(int tstate) { if (tstate >= FrameCycleLength) tstate -= FrameCycleLength; if (tstate < 0) tstate += FrameCycleLength; return RenderingTable.Renderer[tstate].ContentionValue; } #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 { var settings = _machine.Spectrum.GetSettings(); var color = settings.BackgroundColor; if (!settings.UseCoreBorderForBackground) return color; else return ULAPalette[fetchBorder]; } } 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 ClockSpeed * 50; }// ClockSpeed; } set { } } public int VsyncDenominator { get { return ClockSpeed; }//FrameLength; } } public int[] GetVideoBuffer() { switch (borderType) { // Full side borders, no top or bottom border (giving *almost* 16:9 output) case ZXSpectrum.BorderType.Widescreen: // we are cropping out the top and bottom borders var startPixelsToCrop = ScanLineWidth * BorderTopHeight; var endPixelsToCrop = ScanLineWidth * BorderBottomHeight; int index = 0; for (int i = startPixelsToCrop; i < ScreenBuffer.Length - endPixelsToCrop; i++) { croppedBuffer[index++] = ScreenBuffer[i]; } return croppedBuffer; // The full spectrum border case ZXSpectrum.BorderType.Full: return ScreenBuffer; case ZXSpectrum.BorderType.Medium: // all border sizes now 24 var lR = BorderLeftWidth - 24; var rR = BorderRightWidth - 24; var tR = BorderTopHeight - 24; var bR = BorderBottomHeight - 24; var startP = ScanLineWidth * tR; var endP = ScanLineWidth * bR; int index2 = 0; // line by line for (int i = startP; i < ScreenBuffer.Length - endP; i += ScreenWidth + BorderLeftWidth + BorderRightWidth) { // each pixel in each line for (int p = lR; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR; p++) { if (index2 == croppedBuffer.Length) break; croppedBuffer[index2++] = ScreenBuffer[i + p]; } } return croppedBuffer; case ZXSpectrum.BorderType.Small: // all border sizes now 24 var lR_ = BorderLeftWidth - 10; var rR_ = BorderRightWidth - 10; var tR_ = BorderTopHeight - 10; var bR_ = BorderBottomHeight - 10; var startP_ = ScanLineWidth * tR_; var endP_ = ScanLineWidth * bR_; int index2_ = 0; // line by line for (int i = startP_; i < ScreenBuffer.Length - endP_; i += ScreenWidth + BorderLeftWidth + BorderRightWidth) { // each pixel in each line for (int p = lR_; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR_; p++) { if (index2_ == croppedBuffer.Length) break; croppedBuffer[index2_++] = ScreenBuffer[i + p]; } } return croppedBuffer; case ZXSpectrum.BorderType.None: // all border sizes now 0 var lR__ = BorderLeftWidth; var rR__ = BorderRightWidth; var tR__ = BorderTopHeight; var bR__ = BorderBottomHeight; var startP__ = ScanLineWidth * tR__; var endP__ = ScanLineWidth * bR__; int index2__ = 0; // line by line for (int i = startP__; i < ScreenBuffer.Length - endP__; i += ScreenWidth + BorderLeftWidth + BorderRightWidth) { // each pixel in each line for (int p = lR__; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR__; p++) { if (index2__ == croppedBuffer.Length) break; croppedBuffer[index2__++] = ScreenBuffer[i + p]; } } return croppedBuffer; } return ScreenBuffer; } protected void SetupScreenSize() { BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; ScreenBuffer = new int[BufferWidth * BufferHeight]; switch (borderType) { case ZXSpectrum.BorderType.Full: BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; ScreenBuffer = new int[BufferWidth * BufferHeight]; break; case ZXSpectrum.BorderType.Widescreen: BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; BufferHeight = ScreenHeight; VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; croppedBuffer = new int[BufferWidth * BufferHeight]; break; case ZXSpectrum.BorderType.Medium: BufferWidth = ScreenWidth + (24) + (24); BufferHeight = ScreenHeight + (24) + (24); VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; croppedBuffer = new int[BufferWidth * BufferHeight]; break; case ZXSpectrum.BorderType.Small: BufferWidth = ScreenWidth + (10) + (10); BufferHeight = ScreenHeight + (10) + (10); VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; croppedBuffer = new int[BufferWidth * BufferHeight]; break; case ZXSpectrum.BorderType.None: BufferWidth = ScreenWidth; BufferHeight = ScreenHeight; VirtualHeight = BufferHeight; VirtualWidth = BufferWidth; croppedBuffer = new int[BufferWidth * BufferHeight]; break; } } protected int[] croppedBuffer; private ZXSpectrum.BorderType _borderType; public ZXSpectrum.BorderType borderType { get { return _borderType; } set { _borderType = value; } } #endregion #region Serialization public void SyncState(Serializer ser) { ser.BeginSection(nameof(ULA)); if (ScreenBuffer != null) ser.Sync(nameof(ScreenBuffer), ref ScreenBuffer, false); ser.Sync(nameof(BorderColor), ref BorderColor); ser.Sync(nameof(LastTState), ref LastTState); ser.Sync(nameof(flashOn), ref flashOn); ser.Sync(nameof(fetchB1), ref fetchB1); ser.Sync(nameof(fetchA1), ref fetchA1); ser.Sync(nameof(fetchB2), ref fetchB2); ser.Sync(nameof(fetchA2), ref fetchA2); ser.Sync(nameof(ink), ref ink); ser.Sync(nameof(paper), ref paper); ser.Sync(nameof(fetchBorder), ref fetchBorder); ser.Sync(nameof(bright), ref bright); ser.Sync(nameof(flash), ref flash); ser.Sync(nameof(palPaper), ref palPaper); ser.Sync(nameof(palInk), ref palInk); ser.Sync(nameof(LastULATick), ref LastULATick); ser.Sync(nameof(ULACycleCounter), ref ULACycleCounter); ser.Sync(nameof(FrameEnd), ref FrameEnd); ser.Sync(nameof(InterruptRaised), ref InterruptRaised); ser.EndSection(); } #endregion } }