using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
///
/// The abstract class that all emulated models will inherit from
/// * Amstrad Gate Array *
/// https://web.archive.org/web/20170612081209/http://www.grimware.org/doku.php/documentations/devices/gatearray
///
public abstract class GateArrayBase : IVideoProvider
{
public int Z80ClockSpeed = 4000000;
public int FrameLength = 79872;
#region Devices
private CPCBase _machine;
private Z80A CPU => _machine.CPU;
private CRCT_6845 CRCT => _machine.CRCT;
private IPSG PSG => _machine.AYDevice;
#endregion
#region Constants
///
/// CRTC Register constants
///
public const int HOR_TOTAL = 0;
public const int HOR_DISPLAYED = 1;
public const int HOR_SYNC_POS = 2;
public const int HOR_AND_VER_SYNC_WIDTHS = 3;
public const int VER_TOTAL = 4;
public const int VER_TOTAL_ADJUST = 5;
public const int VER_DISPLAYED = 6;
public const int VER_SYNC_POS = 7;
public const int INTERLACE_SKEW = 8;
public const int MAX_RASTER_ADDR = 9;
public const int CUR_START_RASTER = 10;
public const int CUR_END_RASTER = 11;
public const int DISP_START_ADDR_H = 12;
public const int DISP_START_ADDR_L = 13;
public const int CUR_ADDR_H = 14;
public const int CUR_ADDR_L = 15;
public const int LPEN_ADDR_H = 16;
public const int LPEN_ADDR_L = 17;
#endregion
#region Palletes
///
/// The standard CPC Pallete (ordered by firmware #)
/// http://www.cpcwiki.eu/index.php/CPC_Palette
///
private 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
///
private 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 Construction
public GateArrayBase(CPCBase machine)
{
_machine = machine;
PenColours = new int[17];
SetupScreenSize();
Reset();
}
///
/// Inits the pen lookup table
///
public void SetupScreenMapping()
{
for (int m = 0; m < 4; m++)
{
Lookup[m] = new int[256 * 8];
int pos = 0;
for (int b = 0; b < 256; b++)
{
switch (m)
{
case 1:
int pc = (((b & 0x80) >> 7) | ((b & 0x80) >> 2));
Lookup[m][pos++] = pc;
Lookup[m][pos++] = pc;
pc = (((b & 0x40) >> 6) | ((b & 0x04) >> 1));
Lookup[m][pos++] = pc;
Lookup[m][pos++] = pc;
pc = (((b & 0x20) >> 5) | (b & 0x02));
Lookup[m][pos++] = pc;
Lookup[m][pos++] = pc;
pc = (((b & 0x10) >> 4) | ((b & 0x01) << 1));
break;
case 2:
for (int i = 7; i >= 0; i--)
{
bool pixel_on = ((b & (1 << i)) != 0);
Lookup[m][pos++] = pixel_on ? 1 : 0;
}
break;
case 3:
case 0:
int pc2 = (b & 0xAA);
pc2 = (
((pc2 & 0x80) >> 7) |
((pc2 & 0x08) >> 2) |
((pc2 & 0x20) >> 3) |
((pc2 & 0x02) << 2));
Lookup[m][pos++] = pc2;
Lookup[m][pos++] = pc2;
Lookup[m][pos++] = pc2;
Lookup[m][pos++] = pc2;
pc2 = (b & 0x55);
pc2 = (
((pc2 & 0x40) >> 6) |
((pc2 & 0x04) >> 1) |
((pc2 & 0x10) >> 2) |
((pc2 & 0x01) << 3));
Lookup[m][pos++] = pc2;
Lookup[m][pos++] = pc2;
Lookup[m][pos++] = pc2;
Lookup[m][pos++] = pc2;
break;
}
}
}
}
#endregion
#region State
private int[] PenColours;
private int CurrentPen;
private int ScreenMode;
private int INTScanlineCnt;
//private int VSYNCDelyCnt;
private int[][] Lookup = new int[4][];
//private bool DoModeUpdate;
//private int LatchedMode;
//private int buffPos;
public bool FrameEnd;
public bool WaitLine;
#endregion
#region Clock Operations
///
/// The gatearray runs on a 16Mhz clock
/// (for the purposes of emulation, we will use a 4Mhz clock)
/// From this it generates:
/// 1Mhz clock for the CRTC chip
/// 1Mhz clock for the AY-3-8912 PSG
/// 4Mhz clock for the Z80 CPU
///
public void ClockCycle()
{
// 4-phase clock
for (int i = 1; i < 5; i++)
{
switch (i)
{
// Phase 1
case 1:
CRCT.ClockCycle();
CPU.ExecuteOne();
break;
// Phase 2
case 2:
CPU.ExecuteOne();
break;
// Phase 3
case 3:
// video fetch
break;
// Phase 4
case 4:
// video fetch
break;
}
}
}
#endregion
#region Internal Methods
///
/// Selects the pen
///
public virtual void SetPen(BitArray bi)
{
if (bi[4])
{
// border select
CurrentPen = 16;
}
else
{
// pen select
byte[] b = new byte[1];
bi.CopyTo(b, 0);
CurrentPen = b[0] & 0x0f;
}
}
///
/// Selects colour for the currently selected pen
///
public virtual void SetPenColour(BitArray bi)
{
byte[] b = new byte[1];
bi.CopyTo(b, 0);
var colour = b[0] & 0x1f;
PenColours[CurrentPen] = colour;
}
///
/// Returns the actual ARGB pen colour value
///
public virtual int GetPenColour(int idx)
{
return CPCHardwarePalette[PenColours[idx]];
}
///
/// Screen mode and ROM config
///
public virtual void SetReg2(BitArray bi)
{
byte[] b = new byte[1];
bi.CopyTo(b, 0);
// screen mode
var mode = b[0] & 0x03;
ScreenMode = mode;
// ROM
// upper
if ((b[0] & 0x08) != 0)
{
_machine.UpperROMPaged = false;
}
else
{
_machine.UpperROMPaged = true;
}
// lower
if ((b[0] & 0x04) != 0)
{
_machine.LowerROMPaged = false;
}
else
{
_machine.LowerROMPaged = true;
}
// INT delay
if ((b[0] & 0x10) != 0)
{
INTScanlineCnt = 0;
}
}
///
/// Only available on machines with a 64KB memory expansion
/// Default assume we don't have this
///
public virtual void SetRAM(BitArray bi)
{
return;
}
public void InterruptACK()
{
INTScanlineCnt &= 0x01f;
}
#endregion
#region Reset
public void Reset()
{
CurrentPen = 0;
ScreenMode = 1;
for (int i = 0; i < 17; i++)
PenColours[i] = 0;
INTScanlineCnt = 0;
//VSYNCDelyCnt = 0;
}
#endregion
#region IPortIODevice
///
/// Device responds to an IN instruction
///
public bool ReadPort(ushort port, ref int result)
{
// gate array is OUT only
return false;
}
///
/// Device responds to an OUT instruction
///
public bool WritePort(ushort port, int result)
{
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result));
// The gate array responds to port 0x7F
bool accessed = !portBits[15];
if (!accessed)
return false;
// Bit 9 and 8 of the data byte define the function to access
if (!dataBits[6] && !dataBits[7])
{
// select pen
SetPen(dataBits);
}
if (dataBits[6] && !dataBits[7])
{
// select colour for selected pen
SetPenColour(dataBits);
}
if (!dataBits[6] && dataBits[7])
{
// select screen mode, ROM configuration and interrupt control
SetReg2(dataBits);
}
if (dataBits[6] && dataBits[7])
{
// RAM memory management
SetRAM(dataBits);
}
return true;
}
#endregion
#region IVideoProvider
///
/// Video output buffer
///
public int[] ScreenBuffer;
private int _virtualWidth;
private int _virtualHeight;
private int _bufferWidth;
private int _bufferHeight;
public int BackgroundColor => CPCHardwarePalette[16];
public int VirtualWidth
{
get => _virtualWidth;
set => _virtualWidth = value;
}
public int VirtualHeight
{
get => _virtualHeight;
set => _virtualHeight = value;
}
public int BufferWidth
{
get => _bufferWidth;
set => _bufferWidth = value;
}
public int BufferHeight
{
get => _bufferHeight;
set => _bufferHeight = value;
}
public int VsyncNumerator
{
get => Z80ClockSpeed * 50;
set { }
}
public int VsyncDenominator => Z80ClockSpeed;
public int[] GetVideoBuffer() => ScreenBuffer;
protected void SetupScreenSize()
{
/*
* Rect Pixels: Mode 0: 160×200 pixels with 16 colors (4 bpp)
Sqaure Pixels: Mode 1: 320×200 pixels with 4 colors (2 bpp)
Rect Pixels: Mode 2: 640×200 pixels with 2 colors (1 bpp)
Rect Pixels: Mode 3: 160×200 pixels with 4 colors (2bpp) (this is not an official mode, but rather a side-effect of the hardware)
*
* */
// define maximum screen buffer size
// to fit all possible resolutions, 640x400 should do it
// therefore a scanline will take two buffer rows
// and buffer columns will be:
// Mode 1: 2 pixels
// Mode 2: 1 pixel
// Mode 0: 4 pixels
// Mode 3: 4 pixels
BufferWidth = 640;
BufferHeight = 400;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
ScreenBuffer = new int[BufferWidth * BufferHeight];
croppedBuffer = ScreenBuffer;
}
protected int[] croppedBuffer;
#endregion
}
}