BizHawk/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULA.cs

1069 lines
39 KiB
C#

using BizHawk.Common;
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Uncommitted logic array implementation (ULA)
/// </summary>
public abstract class ULA : IVideoProvider
{
#region Other Devices
/// <summary>
/// The emulated spectrum
/// </summary>
protected SpectrumBase _machine;
/// <summary>
/// The CPU monitor class
/// </summary>
protected CPUMonitor CPUMon;
#endregion
#region Construction & Initialisation
public ULA (SpectrumBase machine)
{
_machine = machine;
CPUMon = _machine.CPUMon;
borderType = _machine.Spectrum.SyncSettings.BorderType;
}
#endregion
#region Palettes
/// <summary>
/// The standard ULA palette
/// </summary>
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
/// <summary>
/// The CPU speed
/// </summary>
public int ClockSpeed;
/// <summary>
/// Length of frame in T-State cycles
/// </summary>
public int FrameCycleLength;
/// <summary>
/// The T-State at which the interrupt should be raised within the frame
/// </summary>
public int InterruptStartTime;
/// <summary>
/// The period for which the interrupt should he held
/// (simulated /INT pin held low)
/// </summary>
public int InterruptLength;
/// <summary>
/// Contention offset
/// </summary>
public int ContentionOffset;
/// <summary>
/// Arbitrary offset for render table generation
/// </summary>
public int RenderTableOffset;
/// <summary>
/// The offset when return floating bus bytes
/// </summary>
public int FloatingBusOffset;
/// <summary>
/// The time in T-States for one scanline to complete
/// </summary>
public int ScanlineTime;
/// <summary>
/// T-States at the left border
/// </summary>
public int BorderLeftTime;
/// <summary>
/// T-States at the right border
/// </summary>
public int BorderRightTime;
public int FirstPaperLine;
public int FirstPaperTState;
public bool Border4T;
public int Border4TStage;
#endregion
#region Interrupt Generation
/// <summary>
/// Signs that an interrupt has been raised in this frame.
/// </summary>
protected bool InterruptRaised;
public long ULACycleCounter;
public long LastULATick;
public bool FrameEnd;
/// <summary>
/// Cycles the ULA clock
/// Handles interrupt generation
/// </summary>
/// <param name="currentCycle"></param>
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();
}
}
}
}
/// <summary>
/// Flash processing
/// </summary>
public void CalcFlashCounter()
{
flashCounter++;
if (flashCounter > 15)
{
flashOn = !flashOn;
flashCounter = 0;
}
}
#endregion
#region Screen Layout
/// <summary>
/// Total pixels in one display row
/// </summary>
protected int ScreenWidth;
/// <summary>
/// Total pixels in one display column
/// </summary>
protected int ScreenHeight;
/// <summary>
/// Total pixels in top border
/// </summary>
protected int BorderTopHeight;
/// <summary>
/// Total pixels in bottom border
/// </summary>
protected int BorderBottomHeight;
/// <summary>
/// Total pixels in left border width
/// </summary>
protected int BorderLeftWidth;
/// <summary>
/// Total pixels in right border width
/// </summary>
protected int BorderRightWidth;
/// <summary>
/// Total pixels in one scanline
/// </summary>
protected int ScanLineWidth;
#endregion
#region State
/// <summary>
/// The last T-State cycle at which the screen was rendered
/// </summary>
public int LastTState;
/// <summary>
/// Flash state
/// </summary>
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
/// <summary>
/// Holds all information regarding rendering the screen based on the current T-State
/// </summary>
public RenderTable RenderingTable;
/// <summary>
/// Holds all information regarding rendering the screen based on the current T-State
/// </summary>
public class RenderTable
{
/// <summary>
/// The ULA device
/// </summary>
private ULA _ula;
/// <summary>
/// Array of rendercycle entries
/// Starting from the interrupt
/// </summary>
public RenderCycle[] Renderer;
/// <summary>
/// The emulated machine
/// </summary>
public MachineType _machineType;
public int Offset;
/// <summary>
/// Constructor
/// </summary>
/// <param name="contPattern"></param>
public RenderTable(ULA ula, MachineType machineType)
{
_ula = ula;
_machineType = machineType;
Renderer = new RenderCycle[_ula.FrameCycleLength];
InitRenderer(machineType);
}
/// <summary>
/// Initializes the renderer
/// </summary>
/// <param name="machineType"></param>
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);
}
/// <summary>
/// Render/contention information for a single T-State
/// </summary>
public class RenderCycle
{
/// <summary>
/// The ULA render action at this T-State
/// </summary>
public RenderAction RAction;
/// <summary>
/// The contention value at this T-State
/// </summary>
public int ContentionValue;
/// <summary>
/// The screen byte address at this T-State
/// </summary>
public ushort ByteAddress;
/// <summary>
/// The screen attribute address at this T-State
/// </summary>
public ushort AttributeAddress;
/// <summary>
/// The byte address returned by the floating bus at this T-State
/// </summary>
public ushort FloatingBusAddress;
/// <summary>
/// The offset
/// </summary>
public int LineOffset;
}
public enum RenderAction
{
None,
Border,
BorderAndFetchByte1,
BorderAndFetchAttribute1,
Shift1AndFetchByte2,
Shift1AndFetchAttribute2,
Shift1,
Shift1Last,
Shift2,
Shift2Last,
Shift2AndFetchByte1,
Shift2AndFetchAttribute1
}
}
#endregion
#region Render Methods
/// <summary>
/// Renders to the screen buffer based on the current cycle
/// </summary>
/// <param name="toCycle"></param>
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;
}
}
/// <summary>
/// Generates the port lookup table for +2a/+3 allowed floating bus ports
/// </summary>
public void GenerateP3PortTable()
{
List<ushort> table = new List<ushort>();
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];
/// <summary>
/// Returns floating bus value (if available)
/// </summary>
/// <param name="tstate"></param>
/// <returns></returns>
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
/// <summary>
/// Returns the contention value for the current t-state
/// </summary>
/// <returns></returns>
public int GetContentionValue()
{
return GetContentionValue((int)_machine.CurrentFrameCycle);
}
/// <summary>
/// Returns the contention value for the supplied t-state
/// </summary>
/// <returns></returns>
public int GetContentionValue(int tstate)
{
//tstate += MemoryContentionOffset;
if (tstate >= FrameCycleLength)
tstate -= FrameCycleLength;
if (tstate < 0)
tstate += FrameCycleLength;
return RenderingTable.Renderer[tstate].ContentionValue;
}
/// <summary>
/// Returns the contention value for the supplied t-state
/// </summary>
/// <returns></returns>
public int GetPortContentionValue(int tstate)
{
//tstate += PortContentionOffset;
if (tstate >= FrameCycleLength)
tstate -= FrameCycleLength;
if (tstate < 0)
tstate += FrameCycleLength;
return RenderingTable.Renderer[tstate].ContentionValue;
}
#endregion
#region IVideoProvider
/// <summary>
/// Video output buffer
/// </summary>
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 24
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("ULA");
if (ScreenBuffer != null)
ser.Sync("ScreenBuffer", ref ScreenBuffer, false);
ser.Sync("BorderColor", ref BorderColor);
ser.Sync("LastTState", ref LastTState);
ser.Sync("flashOn", ref flashOn);
ser.Sync("fetchB1", ref fetchB1);
ser.Sync("fetchA1", ref fetchA1);
ser.Sync("fetchB2", ref fetchB2);
ser.Sync("fetchA2", ref fetchA2);
ser.Sync("ink", ref ink);
ser.Sync("paper", ref paper);
ser.Sync("fetchBorder", ref fetchBorder);
ser.Sync("bright", ref bright);
ser.Sync("flash", ref flash);
ser.Sync("palPaper", ref palPaper);
ser.Sync("palInk", ref palInk);
ser.Sync("LastULATick", ref LastULATick);
ser.Sync("ULACycleCounter", ref ULACycleCounter);
ser.Sync("FrameEnd", ref FrameEnd);
ser.Sync("InterruptRaised", ref InterruptRaised);
ser.EndSection();
}
#endregion
}
}