using BizHawk.Common; using BizHawk.Emulation.Common; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /* * Much of the SCREEN implementation has been taken from: https://github.com/Dotneteer/spectnetide * * MIT License Copyright (c) 2017 Istvan Novak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ /// /// The abstract class that all emulated models will inherit from /// * Screen * /// public abstract partial class SpectrumBase : IVideoProvider { #region State /// /// The main screen buffer /// protected int[] _frameBuffer; /// /// Pixel and attribute info stored while rendering the screen /// protected byte _pixelByte1; protected byte _pixelByte2; protected byte _attrByte1; protected byte _attrByte2; protected int _xPos; protected int _yPos; protected int[] _flashOffColors; protected int[] _flashOnColors; protected ScreenRenderingCycle[] _renderingCycleTable; protected bool _flashPhase; #endregion #region Statics /// /// 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 ScreenConfig /// /// The number of displayed pixels in a display row /// protected int DisplayWidth = 256; /// /// Number of display lines /// protected int DisplayLines = 192; /// /// The number of frames after the flash is toggled /// protected int FlashToggleFrames = 25; /// /// Number of lines used for vertical sync /// protected int VerticalSyncLines = 8; /// /// The number of top border lines that are not visible /// when rendering the screen /// protected int NonVisibleBorderTopLines = 8; /// /// The number of border lines before the display /// protected int BorderTopLines = 48; /// /// The number of border lines after the display /// protected int BorderBottomLines = 48; /// /// The number of bottom border lines that are not visible /// when rendering the screen /// protected int NonVisibleBorderBottomLines = 8; /// /// The total number of lines in the screen /// protected int ScreenLines; /// /// The first screen line that contains the top left display pixel /// protected int FirstDisplayLine; /// /// The last screen line that contains the bottom right display pixel /// protected int LastDisplayLine; /// /// The number of border pixels to the left of the display /// protected int BorderLeftPixels = 48; /// /// The number of border pixels to the right of the display /// protected int BorderRightPixels = 48; /// /// The total width of the screen in pixels /// protected int ScreenWidth; /// /// Horizontal blanking time (HSync+blanking). /// Given in Z80 clock cycles. /// protected int HorizontalBlankingTime = 40; /// /// The time of displaying left part of the border. /// Given in Z80 clock cycles. /// protected int BorderLeftTime = 24; /// /// The time of displaying a pixel row. /// Given in Z80 clock cycles. /// protected int DisplayLineTime = 128; /// /// The time of displaying right part of the border. /// Given in Z80 clock cycles. /// protected int BorderRightTime = 24; /// /// The time used to render the nonvisible right part of the border. /// Given in Z80 clock cycles. /// protected int NonVisibleBorderRightTime = 8; /// /// The time of displaying a full screen line. /// Given in Z80 clock cycles. /// protected int ScreenLineTime; /// /// The time the data of a particular pixel should be prefetched /// before displaying it. /// Given in Z80 clock cycles. /// protected int PixelDataPrefetchTime = 2; /// /// The time the data of a particular pixel attribute should be prefetched /// before displaying it. /// Given in Z80 clock cycles. /// protected int AttributeDataPrefetchTime = 1; /// /// The tact within the line that should display the first pixel. /// Given in Z80 clock cycles. /// protected int FirstPixelCycleInLine; /// /// The tact in which the top left pixel should be displayed. /// Given in Z80 clock cycles. /// protected int FirstDisplayPixelCycle; /// /// The tact in which the top left screen pixel (border) should be displayed /// protected int FirstScreenPixelCycle; /// /// Defines the number of Z80 clock cycles used for the full rendering /// of the screen. /// public int UlaFrameCycleCount; /// /// The last rendered ULA cycle /// public int LastRenderedULACycle; /// /// This structure defines information related to a particular T-State /// (cycle) of ULA screen rendering /// [StructLayout(LayoutKind.Explicit)] public struct ScreenRenderingCycle { /// /// Tha rendering phase to be applied for the particular tact /// [FieldOffset(0)] public ScreenRenderingPhase Phase; /// /// Display memory contention delay /// [FieldOffset(1)] public byte ContentionDelay; /// /// Display memory address used in the particular tact /// [FieldOffset(2)] public ushort PixelByteToFetchAddress; /// /// Display memory address used in the particular tact /// [FieldOffset(4)] public ushort AttributeToFetchAddress; /// /// Pixel X coordinate /// [FieldOffset(6)] public ushort XPos; /// /// Pixel Y coordinate /// [FieldOffset(8)] public ushort YPos; } /// /// This enumeration defines the particular phases of ULA rendering /// public enum ScreenRenderingPhase : byte { /// /// The ULA does not do any rendering /// None, /// /// The ULA simple sets the border color to display the current pixel. /// Border, /// /// The ULA sets the border color to display the current pixel. It /// prepares to display the fist pixel in the row with prefetching the /// corresponding byte from the display memory. /// BorderAndFetchPixelByte, /// /// The ULA sets the border color to display the current pixel. It has /// already fetched the 8 pixel bits to display. It carries on /// preparing to display the fist pixel in the row with prefetching the /// corresponding attribute byte from the display memory. /// BorderAndFetchPixelAttribute, /// /// The ULA displays the next two pixels of Byte1 sequentially during a /// single Z80 clock cycle. /// DisplayByte1, /// /// The ULA displays the next two pixels of Byte1 sequentially during a /// single Z80 clock cycle. It prepares to display the pixels of the next /// byte in the row with prefetching the corresponding byte from the /// display memory. /// DisplayByte1AndFetchByte2, /// /// The ULA displays the next two pixels of Byte1 sequentially during a /// single Z80 clock cycle. It prepares to display the pixels of the next /// byte in the row with prefetching the corresponding attribute from the /// display memory. /// DisplayByte1AndFetchAttribute2, /// /// The ULA displays the next two pixels of Byte2 sequentially during a /// single Z80 clock cycle. /// DisplayByte2, /// /// The ULA displays the next two pixels of Byte2 sequentially during a /// single Z80 clock cycle. It prepares to display the pixels of the next /// byte in the row with prefetching the corresponding byte from the /// display memory. /// DisplayByte2AndFetchByte1, /// /// The ULA displays the next two pixels of Byte2 sequentially during a /// single Z80 clock cycle. It prepares to display the pixels of the next /// byte in the row with prefetching the corresponding attribute from the /// display memory. /// DisplayByte2AndFetchAttribute1 } #endregion #region Border private int _borderColour; /// /// Gets or sets the ULA border color /// public int BorderColour { get { return _borderColour; } set { _borderColour = value & 0x07; } } protected virtual void ResetBorder() { BorderColour = 0; } #endregion #region Screen Methods /// /// ULA renders the screen between two specified T-States (cycles) /// /// /// public void RenderScreen(int fromCycle, int toCycle) { // Adjust cycle boundaries fromCycle = fromCycle % UlaFrameCycleCount; toCycle = toCycle % UlaFrameCycleCount; // Do rendering action for cycles based on the rendering phase for (int curr = fromCycle; curr <= toCycle; curr++) { var ulaCycle = _renderingCycleTable[curr]; _xPos = ulaCycle.XPos; _yPos = ulaCycle.YPos; switch (ulaCycle.Phase) { case ScreenRenderingPhase.None: // --- Invisible screen area, nothing to do break; case ScreenRenderingPhase.Border: // --- Fetch the border color from ULA and set the corresponding border pixels SetPixels(BorderColour, BorderColour); break; case ScreenRenderingPhase.BorderAndFetchPixelByte: // --- Fetch the border color from ULA and set the corresponding border pixels SetPixels(BorderColour, BorderColour); // --- Obtain the future pixel byte _pixelByte1 = FetchScreenMemory(ulaCycle.PixelByteToFetchAddress); break; case ScreenRenderingPhase.BorderAndFetchPixelAttribute: // --- Fetch the border color from ULA and set the corresponding border pixels SetPixels(BorderColour, BorderColour); // --- Obtain the future attribute byte _attrByte1 = FetchScreenMemory(ulaCycle.AttributeToFetchAddress); break; case ScreenRenderingPhase.DisplayByte1: // --- Display bit 7 and 6 according to the corresponding color SetPixels( GetColor(_pixelByte1 & 0x80, _attrByte1), GetColor(_pixelByte1 & 0x40, _attrByte1)); // --- Shift in the subsequent bits _pixelByte1 <<= 2; break; case ScreenRenderingPhase.DisplayByte1AndFetchByte2: // --- Display bit 7 and 6 according to the corresponding color SetPixels( GetColor(_pixelByte1 & 0x80, _attrByte1), GetColor(_pixelByte1 & 0x40, _attrByte1)); // --- Shift in the subsequent bits _pixelByte1 <<= 2; // --- Obtain the next pixel byte _pixelByte2 = FetchScreenMemory(ulaCycle.PixelByteToFetchAddress); break; case ScreenRenderingPhase.DisplayByte1AndFetchAttribute2: // --- Display bit 7 and 6 according to the corresponding color SetPixels( GetColor(_pixelByte1 & 0x80, _attrByte1), GetColor(_pixelByte1 & 0x40, _attrByte1)); // --- Shift in the subsequent bits _pixelByte1 <<= 2; // --- Obtain the next attribute _attrByte2 = FetchScreenMemory(ulaCycle.AttributeToFetchAddress); break; case ScreenRenderingPhase.DisplayByte2: // --- Display bit 7 and 6 according to the corresponding color SetPixels( GetColor(_pixelByte2 & 0x80, _attrByte2), GetColor(_pixelByte2 & 0x40, _attrByte2)); // --- Shift in the subsequent bits _pixelByte2 <<= 2; break; case ScreenRenderingPhase.DisplayByte2AndFetchByte1: // --- Display bit 7 and 6 according to the corresponding color SetPixels( GetColor(_pixelByte2 & 0x80, _attrByte2), GetColor(_pixelByte2 & 0x40, _attrByte2)); // --- Shift in the subsequent bits _pixelByte2 <<= 2; // --- Obtain the next pixel byte _pixelByte1 = FetchScreenMemory(ulaCycle.PixelByteToFetchAddress); break; case ScreenRenderingPhase.DisplayByte2AndFetchAttribute1: // --- Display bit 7 and 6 according to the corresponding color SetPixels( GetColor(_pixelByte2 & 0x80, _attrByte2), GetColor(_pixelByte2 & 0x40, _attrByte2)); // --- Shift in the subsequent bits _pixelByte2 <<= 2; // --- Obtain the next attribute _attrByte1 = FetchScreenMemory(ulaCycle.AttributeToFetchAddress); break; } } } /// /// Tests whether the specified cycle is in the visible area of the screen. /// /// Line index /// Tacts index within the line /// /// True, if the tact is visible on the screen; otherwise, false /// public virtual bool IsCycleVisible(int line, int cycleInLine) { var firstVisibleLine = VerticalSyncLines + NonVisibleBorderTopLines; var lastVisibleLine = firstVisibleLine + BorderTopLines + DisplayLines + BorderBottomLines; return line >= firstVisibleLine && line < lastVisibleLine && cycleInLine >= HorizontalBlankingTime && cycleInLine < ScreenLineTime - NonVisibleBorderRightTime; } /// /// Tests whether the cycle is in the display area of the screen. /// /// Line index /// Tacts index within the line /// /// True, if the tact is within the display area of the screen; otherwise, false. /// public virtual bool IsCycleInDisplayArea(int line, int cycleInLine) { return line >= FirstDisplayLine && line <= LastDisplayLine && cycleInLine >= FirstPixelCycleInLine && cycleInLine < FirstPixelCycleInLine + DisplayLineTime; } /// /// Sets the two adjacent screen pixels belonging to the specified cycle to the given /// color /// /// Color index of the first pixel /// Color index of the second pixel protected virtual void SetPixels(int colorIndex1, int colorIndex2) { var pos = _yPos * ScreenWidth + _xPos; _frameBuffer[pos++] = ULAPalette[colorIndex1]; _frameBuffer[pos] = ULAPalette[colorIndex2]; } /// /// Gets the color index for the specified pixel value according /// to the given color attribute /// /// 0 for paper pixel, non-zero for ink pixel /// Color attribute /// protected virtual int GetColor(int pixelValue, byte attr) { var offset = (pixelValue == 0 ? 0 : 0x100) + attr; return _flashPhase ? _flashOnColors[offset] : _flashOffColors[offset]; } /// /// Resets the ULA cycle to start screen rendering from the beginning /// protected virtual void ResetULACycle() { LastRenderedULACycle = -1; } /// /// Initializes the ULA cycle table /// protected virtual void InitULACycleTable() { _renderingCycleTable = new ScreenRenderingCycle[UlaFrameCycleCount]; // loop through every cycle for (var cycle = 0; cycle < UlaFrameCycleCount; cycle++) { var line = cycle / ScreenLineTime; var cycleInLine = cycle % ScreenLineTime; var cycleItem = new ScreenRenderingCycle { Phase = ScreenRenderingPhase.None, ContentionDelay = 0 }; if (IsCycleVisible(line, cycleInLine)) { // calculate pixel positions cycleItem.XPos = (ushort)((cycleInLine - HorizontalBlankingTime) * 2); cycleItem.YPos = (ushort)(line - VerticalSyncLines - NonVisibleBorderTopLines); if (!IsCycleInDisplayArea(line, cycleInLine)) { // we are in the border cycleItem.Phase = ScreenRenderingPhase.Border; // set the border colour if (line >= FirstDisplayLine && line <= LastDisplayLine) { if (cycleInLine == FirstPixelCycleInLine - PixelDataPrefetchTime) { // left or right border beside the display area // fetch the first pixel data byte of the current line (2 cycles away) cycleItem.Phase = ScreenRenderingPhase.BorderAndFetchPixelByte; cycleItem.PixelByteToFetchAddress = CalculatePixelByteAddress(line, cycleInLine + 2); cycleItem.ContentionDelay = 6; } else if (cycleInLine == FirstPixelCycleInLine - AttributeDataPrefetchTime) { // fetch the first attribute data byte of the current line (1 cycle away) cycleItem.Phase = ScreenRenderingPhase.BorderAndFetchPixelAttribute; cycleItem.AttributeToFetchAddress = CalculateAttributeAddress(line, cycleInLine + 1); cycleItem.ContentionDelay = 5; } } } else { var pixelCycle = cycleInLine - FirstPixelCycleInLine; // the ULA will perform a different action based on the current cycle (T-State) switch (pixelCycle & 7) { case 0: // Display the current cycle pixels cycleItem.Phase = ScreenRenderingPhase.DisplayByte1; cycleItem.ContentionDelay = 4; break; case 1: // Display the current cycle pixels cycleItem.Phase = ScreenRenderingPhase.DisplayByte1; cycleItem.ContentionDelay = 3; break; case 2: // While displaying the current cycle pixels, we need to prefetch the // pixel data byte 2 cycles away cycleItem.Phase = ScreenRenderingPhase.DisplayByte1AndFetchByte2; cycleItem.PixelByteToFetchAddress = CalculatePixelByteAddress(line, cycleInLine + 2); cycleItem.ContentionDelay = 2; break; case 3: // While displaying the current cycle pixels, we need to prefetch the // attribute data byte 1 cycle away cycleItem.Phase = ScreenRenderingPhase.DisplayByte1AndFetchAttribute2; cycleItem.AttributeToFetchAddress = CalculateAttributeAddress(line, cycleInLine + 1); cycleItem.ContentionDelay = 1; break; case 4: case 5: // Display the current cycle pixels cycleItem.Phase = ScreenRenderingPhase.DisplayByte2; break; case 6: if (cycleInLine < FirstPixelCycleInLine + DisplayLineTime - 2) { // There are still more bytes to display in this line. // While displaying the current cycle pixels, we need to prefetch the // pixel data byte 2 cycles away cycleItem.Phase = ScreenRenderingPhase.DisplayByte2AndFetchByte1; cycleItem.PixelByteToFetchAddress = CalculatePixelByteAddress(line, cycleInLine + 2); cycleItem.ContentionDelay = 6; } else { // Last byte in this line. // Display the current cycle pixels cycleItem.Phase = ScreenRenderingPhase.DisplayByte2; } break; case 7: if (cycleInLine < FirstPixelCycleInLine + DisplayLineTime - 1) { // There are still more bytes to display in this line. // While displaying the current cycle pixels, we need to prefetch the // attribute data byte 1 cycle away cycleItem.Phase = ScreenRenderingPhase.DisplayByte2AndFetchAttribute1; cycleItem.AttributeToFetchAddress = CalculateAttributeAddress(line, cycleInLine + 1); cycleItem.ContentionDelay = 5; } else { // Last byte in this line. // Display the current cycle pixels cycleItem.Phase = ScreenRenderingPhase.DisplayByte2; } break; } } } // Store the calulated cycle item _renderingCycleTable[cycle] = cycleItem; } } /// /// Calculates the pixel address for the specified line and tact within /// the line /// /// Line index /// Tacts index within the line /// ZX spectrum screen memory address /// /// Memory address bits: /// C0-C2: pixel count within a byte -- not used in address calculation /// C3-C7: pixel byte within a line /// V0-V7: pixel line address /// /// Direct Pixel Address (da) /// ================================================================= /// |A15|A14|A13|A12|A11|A10|A9 |A8 |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 | /// ================================================================= /// | 0 | 0 | 0 |V7 |V6 |V5 |V4 |V3 |V2 |V1 |V0 |C7 |C6 |C5 |C4 |C3 | /// ================================================================= /// | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 0xF81F /// ================================================================= /// | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0x0700 /// ================================================================= /// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0x00E0 /// ================================================================= /// /// Spectrum Pixel Address /// ================================================================= /// |A15|A14|A13|A12|A11|A10|A9 |A8 |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 | /// ================================================================= /// | 0 | 0 | 0 |V7 |V6 |V2 |V1 |V0 |V5 |V4 |V3 |C7 |C6 |C5 |C4 |C3 | /// ================================================================= /// protected virtual ushort CalculatePixelByteAddress(int line, int cycleInLine) { var row = line - FirstDisplayLine; var column = 2 * (cycleInLine - (HorizontalBlankingTime + BorderLeftTime)); var da = 0x4000 | (column >> 3) | (row << 5); return (ushort)((da & 0xF81F) // --- Reset V5, V4, V3, V2, V1 | ((da & 0x0700) >> 3) // --- Keep V5, V4, V3 only | ((da & 0x00E0) << 3)); // --- Exchange the V2, V1, V0 bit // --- group with V5, V4, V3 } /// /// Calculates the pixel attribute address for the specified line and /// tact within the line /// /// Line index /// Tacts index within the line /// ZX spectrum screen memory address /// /// Memory address bits: /// C0-C2: pixel count within a byte -- not used in address calculation /// C3-C7: pixel byte within a line /// V0-V7: pixel line address /// /// Spectrum Attribute Address /// ================================================================= /// |A15|A14|A13|A12|A11|A10|A9 |A8 |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 | /// ================================================================= /// | 0 | 1 | 0 | 1 | 1 | 0 |V7 |V6 |V5 |V4 |V3 |C7 |C6 |C5 |C4 |C3 | /// ================================================================= /// protected virtual ushort CalculateAttributeAddress(int line, int cycleInLine) { var row = line - FirstDisplayLine; var column = 2 * (cycleInLine - (HorizontalBlankingTime + BorderLeftTime)); var da = (column >> 3) | ((row >> 3) << 5); return (ushort)(0x5800 + da); } #endregion #region Initialisation /// /// Initialises the screen configuration calculations /// protected virtual void InitScreenConfig() { ScreenLines = BorderTopLines + DisplayLines + BorderBottomLines; FirstDisplayLine = VerticalSyncLines + NonVisibleBorderTopLines + BorderTopLines; LastDisplayLine = FirstDisplayLine + DisplayLines - 1; ScreenWidth = BorderLeftPixels + DisplayWidth + BorderRightPixels; FirstPixelCycleInLine = HorizontalBlankingTime + BorderLeftTime; ScreenLineTime = FirstPixelCycleInLine + DisplayLineTime + BorderRightTime + NonVisibleBorderRightTime; UlaFrameCycleCount = (FirstDisplayLine + DisplayLines + BorderBottomLines + NonVisibleBorderTopLines) * ScreenLineTime; FirstScreenPixelCycle = (VerticalSyncLines + NonVisibleBorderTopLines) * ScreenLineTime + HorizontalBlankingTime; } /// /// Inits the screen /// protected virtual void InitScreen() { //BorderDevice.Reset(); _flashPhase = false; _frameBuffer = new int[ScreenWidth * ScreenLines]; InitULACycleTable(); // --- Calculate color conversion table _flashOffColors = new int[0x200]; _flashOnColors = new int[0x200]; for (var attr = 0; attr < 0x100; attr++) { var ink = (attr & 0x07) | ((attr & 0x40) >> 3); var paper = ((attr & 0x38) >> 3) | ((attr & 0x40) >> 3); _flashOffColors[attr] = paper; _flashOffColors[0x100 + attr] = ink; _flashOnColors[attr] = (attr & 0x80) != 0 ? ink : paper; _flashOnColors[0x100 + attr] = (attr & 0x80) != 0 ? paper : ink; } FrameCount = 0; } #endregion #region VBLANK Interrupt /// /// The longest instruction cycle count /// protected const int LONGEST_OP_CYCLES = 23; /// /// The ULA cycle to raise the interrupt at /// protected int InterruptCycle = 32; /// /// Signs that an interrupt has been raised in this frame. /// protected bool InterruptRaised; /// /// Signs that the interrupt signal has been revoked /// protected bool InterruptRevoked; /// /// Resets the interrupt - this should happen every frame in order to raise /// the VBLANK interrupt in the proceding frame /// public virtual void ResetInterrupt() { InterruptRaised = false; InterruptRevoked = false; } /// /// Generates an interrupt in the current phase if needed /// /// protected virtual void CheckForInterrupt(int currentCycle) { if (InterruptRevoked) { // interrupt has already been handled return; } if (currentCycle < InterruptCycle) { // interrupt does not need to be raised yet return; } if (currentCycle > InterruptCycle + LONGEST_OP_CYCLES) { // interrupt should have already been raised and the cpu may or // may not have caught it. The time has passed so revoke the signal InterruptRevoked = true; //CPU.IFF1 = true; CPU.FlagI = false; //CPU.NonMaskableInterruptPending = true; } if (InterruptRaised) { // INT is raised but not yet revoked // CPU has NOT handled it yet return; } // if CPU is masking the interrupt do not raise it //if (!CPU.IFF1) //return; // Raise the interrupt InterruptRaised = true; //CPU.IFF1 = false; //CPU.IFF2 = false; CPU.FlagI = true; FrameCount++; } #endregion #region IVideoProvider public int VirtualWidth => ScreenWidth; public int VirtualHeight => ScreenLines; public int BufferWidth => ScreenWidth; public int BufferHeight => ScreenLines; public int BackgroundColor => ULAPalette[BorderColour]; public int VsyncNumerator { get { return 3500000; } set { } } public int VsyncDenominator { get { return UlaFrameCycleCount; } } /* public int VsyncNumerator => NullVideo.DefaultVsyncNum; public int VsyncDenominator => NullVideo.DefaultVsyncDen; */ public int[] GetVideoBuffer() { return _frameBuffer; // convert the generated _framebuffer into ARGB colours via the ULAPalette int[] trans = new int[_frameBuffer.Length]; for (int i = 0; i < _frameBuffer.Length; i++) trans[i] = ULAPalette[_frameBuffer[i]]; return trans; //_frameBuffer; } #endregion } }