BizHawk/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs

1400 lines
38 KiB
C#

using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// * Amstrad Gate Array *
/// http://www.cpcwiki.eu/index.php/Gate_Array
/// https://web.archive.org/web/20170612081209/http://www.grimware.org/doku.php/documentations/devices/gatearray
/// </summary>
public class AmstradGateArray : IPortIODevice, IVideoProvider
{
#region Devices
private CPCBase _machine;
private Z80A CPU => _machine.CPU;
private CRCT_6845 CRCT => _machine.CRCT;
//private CRTDevice CRT => _machine.CRT;
private IPSG PSG => _machine.AYDevice;
private NECUPD765 FDC => _machine.UPDDiskDevice;
private DatacorderDevice DATACORDER => _machine.TapeDevice;
private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
public const ushort PCh = 1;
private GateArrayType ChipType;
#endregion
#region Palettes
/// <summary>
/// The standard CPC Pallete (ordered by firmware #)
/// http://www.cpcwiki.eu/index.php/CPC_Palette
/// </summary>
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
};
/// <summary>
/// The standard CPC Pallete (ordered by hardware #)
/// http://www.cpcwiki.eu/index.php/CPC_Palette
/// </summary>
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 Clocks and Timing
/// <summary>
/// The Gate Array Clock Speed
/// </summary>
public int GAClockSpeed = 16000000;
/// <summary>
/// The CPU Clock Speed
/// </summary>
public int Z80ClockSpeed = 4000000;
/// <summary>
/// CRCT Clock Speed
/// </summary>
public int CRCTClockSpeed = 1000000;
/// <summary>
/// AY-3-8912 Clock Speed
/// </summary>
public int PSGClockSpeed = 1000000;
/// <summary>
/// Number of CPU cycles in one frame
/// </summary>
public int FrameLength = 79872;
/// <summary>
/// Number of Gate Array cycles within one frame
/// </summary>
public int GAFrameLength = 319488;
#endregion
#region Construction
public AmstradGateArray(CPCBase machine, GateArrayType chipType)
{
_machine = machine;
ChipType = chipType;
//PenColours = new int[17];
borderType = _machine.CPC.SyncSettings.BorderType;
SetupScreenSize();
//Reset();
CRCT.AttachHSYNCCallback(OnHSYNC);
CRCT.AttachVSYNCCallback(OnVSYNC);
CurrentLine = new CharacterLine();
InitByteLookup();
CalculateNextScreenMemory();
}
#endregion
#region Registers and Internal State
/// <summary>
/// PENR (register 0) - Pen Selection
/// This register can be used to select one of the 17 color-registers (pen 0 to 15 or the border).
/// It will remain selected until another PENR command is executed.
/// PENR Index
/// 7 6 5 4 3 2 1 0 color register selected
/// 0 0 0 0 n n n n pen n from 0 to 15 (4bits)
/// 0 0 0 1 x x x x border
///
/// x can be 0 or 1, it doesn't matter
/// </summary>
private byte _PENR;
public byte PENR
{
get { return _PENR; }
set
{
_PENR = value;
if (_PENR.Bit(4))
{
// border select
CurrentPen = 16;
}
else
{
// pen select
CurrentPen = _PENR & 0x0f;
}
}
}
/// <summary>
/// 0-15: Pen Registers
/// 16: Border Colour
/// </summary>
public int[] ColourRegisters = new int[17];
/// <summary>
/// The currently selected Pen
/// </summary>
private int CurrentPen;
/// <summary>
/// INKR (register 1) - Colour Selection
/// This register takes a 5bits parameter which is a color-code. This color-code range from 0 to 31 but there's only 27 differents colors
/// (because the Gate Array use a 3-states logic on the R,G and B signals, thus 3x3x3=27).
/// INKR Color
/// 7 6 5 4 3 2 1 0
/// 0 1 0 n n n n n where n is a color code (5 bits)
///
/// The PEN affected by the INKR command is updated (almost) immediatly
/// </summary>
private byte _INKR;
public byte INKR
{
get { return _INKR; }
set
{
_INKR = value;
ColourRegisters[CurrentPen] = _INKR & 0x1f;
}
}
/// <summary>
/// RMR (register 2) - Select screen mode and ROM configuration
/// This register control the interrupt counter (reset), the upper and lower ROM paging and the video mode.
/// RMR Commands
/// 7 6 5 4 3 2 1 0
/// 1 0 0 I UR LR VM-->
///
/// I : if set (1), this will reset the interrupt counter
/// UR : Enable (0) or Disable (1) the upper ROM paging (&amp;C000 to &amp;FFFF). You can select which upper ROM with the I/O address &amp;DF00
/// LR : Enable (0) or Disable (1) the lower ROM paging
/// VM : Select the video mode 0,1,2 or 3 (it will take effect after the next HSync)
/// </summary>
private byte _RMR;
public byte RMR
{
get { return _RMR; }
set
{
_RMR = value;
//ScreenMode = _RMR & 0x03;
var sm = _RMR & 0x03;
if (sm != 1)
{
}
if ((_RMR & 0x08) != 0)
_machine.UpperROMPaged = false;
else
_machine.UpperROMPaged = true;
if ((_RMR & 0x04) != 0)
_machine.LowerROMPaged = false;
else
_machine.LowerROMPaged = true;
if (_RMR.Bit(4))
{
// reset interrupt counter
InterruptCounter = 0;
}
}
}
/// <summary>
/// RAMR (register 3) - RAM Banking
/// This register exists only in CPCs with 128K RAM (like the CPC 6128, or CPCs with Standard Memory Expansions)
/// Note: In the CPC 6128, the register is a separate PAL that assists the Gate Array chip
///
/// Bit Value Function
/// 7 1 Gate Array function 3
/// 6 1
/// 5 b 64K bank number(0..7); always 0 on an unexpanded CPC6128, 0-7 on Standard Memory Expansions
/// 4 b
/// 3 b
/// 2 x RAM Config(0..7)
/// 1 x ""
/// 0 x ""
///
/// The 3bit RAM Config value is used to access the second 64K of the total 128K RAM that is built into the CPC 6128 or the additional 64K-512K of standard memory expansions.
/// These contain up to eight 64K ram banks, which are selected with bit 3-5. A standard CPC 6128 only contains bank 0. Normally the register is set to 0, so that only the
/// first 64K RAM are used (identical to the CPC 464 and 664 models). The register can be used to select between the following eight predefined configurations only:
///
/// -Address- 0 1 2 3 4 5 6 7
/// 0000-3FFF RAM_0 RAM_0 RAM_4 RAM_0 RAM_0 RAM_0 RAM_0 RAM_0
/// 4000-7FFF RAM_1 RAM_1 RAM_5 RAM_3 RAM_4 RAM_5 RAM_6 RAM_7
/// 8000-BFFF RAM_2 RAM_2 RAM_6 RAM_2 RAM_2 RAM_2 RAM_2 RAM_2
/// C000-FFFF RAM_3 RAM_7 RAM_7 RAM_7 RAM_3 RAM_3 RAM_3 RAM_3
///
/// The Video RAM is always located in the first 64K, VRAM is in no way affected by this register
/// </summary>
private byte _RAMR;
/// <summary>
/// This is actually implemented outside of here. These values do nothing.
/// </summary>
public byte RAMR
{
get { return _RAMR; }
set
{
_RAMR = value;
}
}
/// <summary>
/// The selected screen mode (updated after every HSYNC)
/// </summary>
private int ScreenMode;
/// <summary>
/// Simulates the internal 6bit INT counter
/// </summary>
private int _interruptCounter;
public int InterruptCounter
{
get { return _interruptCounter; }
set { _interruptCounter = value; }
}
/// <summary>
/// Interrupts are delayed when a VSYNC occurs
/// </summary>
private int VSYNCDelay;
/// <summary>
/// Signals that the frame end has been reached
/// </summary>
public bool FrameEnd;
/// <summary>
/// Internal phase clock
/// </summary>
private int ClockCounter;
/// <summary>
/// Master frame clock counter
/// </summary>
public int FrameClock;
/// <summary>
/// Simulates the gate array memory /WAIT line
/// </summary>
private bool WaitLine;
/// <summary>
/// 16-bit address - read from the CRCT
/// </summary>
private short _MA;
private short MA
{
get
{
_MA = CRCT.MA;
return _MA;
}
}
/// <summary>
/// Set when the HSYNC signal is detected from the CRCT
/// </summary>
private bool HSYNC;
// /// <summary>
// /// Is set when an initial HSYNC is seen from the CRCT
// /// On real hardware interrupt generation is based on the falling edge of the HSYNC signal
// /// So in this emulation, once the falling edge is detected, interrupt processing happens
// /// </summary>
// private bool HSYNC_falling;
/// <summary>
/// Used to count HSYNCs during a VSYNC
/// </summary>
private int HSYNC_counter;
/// <summary>
/// Set when the VSYNC signal is detected from the CRCT
/// </summary>
private bool VSYNC;
/// <summary>
/// TRUE when the /INT pin is held low
/// </summary>
private bool InterruptRaised;
/// <summary>
/// Counts the GA cycles that the /INT pin should be held low
/// </summary>
private int InterruptHoldCounter;
/// <summary>
/// Set at the start of a new frame
/// </summary>
public bool IsNewFrame;
/// <summary>
/// Set when a new line is beginning
/// </summary>
public bool IsNewLine;
/// <summary>
/// Horizontal Character Counter
/// </summary>
private int HCC;
/// <summary>
/// Vertical Line Counter
/// </summary>
private int VLC;
/// <summary>
/// The first video byte fetched
/// </summary>
private byte VideoByte1;
/// <summary>
/// The second video byte fetched
/// </summary>
private byte VideoByte2;
#endregion
#region Clock Business
/// <summary>
/// Called every CPU cycle
/// In reality the GA is clocked at 16Mhz (4 times the frequency of the CPU)
/// Therefore this method has to take care of:
/// 4 GA cycles
/// 1 CRCT cycle every 4 calls
/// 1 PSG cycle every 4 calls
/// 1 CPU cycle (uncontended)
/// </summary>
public void ClockCycle()
{
// gatearray uses 4-phase clock to supply clocks to other devices
switch (ClockCounter)
{
case 0:
CRCT.ClockCycle();
WaitLine = false;
break;
case 1:
WaitLine = true;
// detect new scanline and upcoming new frame on next render cycle
//FrameDetector();
break;
case 2:
// video fetch
WaitLine = true;
//FetchByte(1);
break;
case 3:
// video fetch and render
WaitLine = true;
//FetchByte(2);
GACharacterCycle();
//PixelGenerator();
break;
}
if (!HSYNC && CRCT.HSYNC)
{
HSYNC = true;
}
// run the interrupt generator routine
InterruptGenerator();
if (!CRCT.HSYNC)
{
HSYNC = false;
}
// conditional CPU cycle
DoConditionalCPUCycle();
AdvanceClock();
}
/// <summary>
/// Increments the internal clock counters by one
/// </summary>
private void AdvanceClock()
{
FrameClock++;
ClockCounter++;
if (ClockCounter == 4)
ClockCounter = 0;
// check for frame end
if (FrameClock == FrameLength)
{
FrameEnd = true;
}
}
/// <summary>
/// Runs a 4 Mhz CPU cycle if neccessary
/// /WAIT line status is a factor here
/// </summary>
private void DoConditionalCPUCycle()
{
if (!WaitLine)
{
// /WAIT line is NOT active
CPU.ExecuteOne();
return;
}
// /WAIT line is active
switch (ClockCounter)
{
case 2:
case 3:
// gate array video fetch is occuring
// check for memory access
if (BUSRQ > 0)
{
// memory action upcoming - CPU clock is halted
CPU.TotalExecutedCycles++;
}
break;
case 1:
// CPU accesses RAM if it's performing a non-opcode read or write
// assume for now that an opcode fetch is always looking at PC
if (BUSRQ == PCh)
{
// opcode fetch memory action upcoming - CPU clock is halted
CPU.TotalExecutedCycles++;
}
else
{
// no fetch, or non-opcode fetch
CPU.ExecuteOne();
}
break;
}
}
#endregion
#region Frame & Interrupt Handling
/// <summary>
/// The CRCT builds the picture in a strange way, so that the top left of the display area is the first pixel from
/// video RAM. The borders come either side of the HSYNC and VSYNCs later on:
/// https://web.archive.org/web/20170501112330im_/http://www.grimware.org/lib/exe/fetch.php/documentations/devices/crtc.6845/crtc.standard.video.frame.png?w=800&amp;h=500
/// Therefore when the gate array initialises, we will attempt end the frame early in order to
/// sync up at the point where VSYNC is active and HSYNC just begins. This is roughly how a CRT monitor would display the picture.
/// The CRT would start a new line at the point where an HSYNC is detected.
/// </summary>
private void FrameDetector()
{
if (CRCT.HSYNC && !IsNewLine)
{
// start of a new line on the next render cycle
IsNewLine = true;
// process scanline
//CRT.CurrentLine.CommitScanline();
// check for end of frame
if (CRCT.VSYNC && !IsNewFrame)
{
// start of a new frame on the next render cycle
IsNewFrame = true;
//FrameEnd = true;
VLC = 0;
}
else if (!CRCT.VSYNC)
{
// increment line counter
VLC++;
IsNewFrame = false;
}
HCC = 0;
// update screenmode
//ScreenMode = RMR & 0x03;
//CRT.CurrentLine.InitScanline(ScreenMode, VLC);
}
else if (!CRCT.HSYNC)
{
// reset the flags
IsNewLine = false;
IsNewFrame = false;
}
}
/// <summary>
/// Handles interrupt generation
/// </summary>
private void InterruptGenerator()
{
if (HSYNC && !CRCT.HSYNC)
{
// falling edge of the HSYNC detected
InterruptCounter++;
if (CRCT.VSYNC)
{
if (HSYNC_counter >= 2)
{
// x2 HSYNC have happened during VSYNC
if (InterruptCounter >= 32)
{
// no interrupt
InterruptCounter = 0;
}
else if (InterruptCounter < 32)
{
// interrupt
InterruptRaised = true;
InterruptCounter = 0;
}
HSYNC_counter = 0;
}
else
{
HSYNC_counter++;
}
}
if (InterruptCounter == 52)
{
// gatearray should raise an interrupt
InterruptRaised = true;
InterruptCounter = 0;
}
}
if (InterruptRaised)
{
// interrupt should been raised
CPU.FlagI = true;
InterruptHoldCounter++;
// the INT signal should be held low for 1.4us.
// in gatearray cycles, this equates to 22.4
// we will round down to 22 for emulation purposes
if (InterruptHoldCounter >= 22)
{
CPU.FlagI = false;
InterruptRaised = false;
InterruptHoldCounter = 0;
}
}
}
#endregion
#region Rendering Business
/// <summary>
/// Builds up current scanline character information
/// Ther GA modifies HSYNC and VSYNC signals before they are sent to the monitor
/// This is handled here
/// Runs at 1Mhz
/// </summary>
private void GACharacterCycle()
{
if (CRCT.VSYNC && CRCT.HSYNC)
{
// both hsync and vsync active
CurrentLine.AddCharacter(Phase.HSYNCandVSYNC);
}
else if (CRCT.VSYNC)
{
// vsync is active but hsync is not
CurrentLine.AddCharacter(Phase.VSYNC);
}
else if (CRCT.HSYNC)
{
// hsync is active but vsync is not
CurrentLine.AddCharacter(Phase.HSYNC);
}
else if (!CRCT.DISPTMG)
{
// border generation
CurrentLine.AddCharacter(Phase.BORDER);
}
else if (CRCT.DISPTMG)
{
// pixels generated from video RAM
CurrentLine.AddCharacter(Phase.DISPLAY);
}
}
/// <summary>
/// Holds the upcoming video RAM addresses for the next scanline
/// Firmware default size is 80 (40 characters - 2 bytes per character)
/// </summary>
private ushort[] NextVidRamLine = new ushort[40 * 2];
/// <summary>
/// The current character line we are working from
/// </summary>
private CharacterLine CurrentLine;
/// <summary>
/// List of screen lines as they are built up
/// </summary>
private List<CharacterLine> ScreenLines = new List<CharacterLine>();
/// <summary>
/// Pixel value lookups for every scanline byte value
/// Based on the lookup at https://github.com/gavinpugh/xnacpc
/// </summary>
private int[][] ByteLookup = new int[4][];
private void InitByteLookup()
{
int pix;
for (int m = 0; m < 4; m++)
{
int pos = 0;
ByteLookup[m] = new int[256 * 8];
for (int b = 0; b < 256; b++)
{
switch (m)
{
case 0:
pix = b & 0xaa;
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
for (int c = 0; c < 4; c++)
ByteLookup[m][pos++] = pix;
pix = b & 0x55;
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
for (int c = 0; c < 4; c++)
ByteLookup[m][pos++] = pix;
break;
case 1:
pix = (((b & 0x80) >> 7) | ((b & 0x08) >> 2));
ByteLookup[m][pos++] = pix;
ByteLookup[m][pos++] = pix;
pix = (((b & 0x40) >> 6) | ((b & 0x04) >> 1));
ByteLookup[m][pos++] = pix;
ByteLookup[m][pos++] = pix;
pix = (((b & 0x20) >> 5) | (b & 0x02));
ByteLookup[m][pos++] = pix;
ByteLookup[m][pos++] = pix;
pix = (((b & 0x10) >> 4) | ((b & 0x01) << 1));
ByteLookup[m][pos++] = pix;
ByteLookup[m][pos++] = pix;
break;
case 2:
for (int i = 7; i >= 0; i--)
ByteLookup[m][pos++] = ((b & (1 << i)) != 0) ? 1 : 0;
break;
case 3:
pix = b & 0xaa;
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
for (int c = 0; c < 4; c++)
ByteLookup[m][pos++] = pix;
pix = b & 0x55;
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
for (int c = 0; c < 4; c++)
ByteLookup[m][pos++] = pix;
break;
}
}
}
}
/// <summary>
/// Runs at HSYNC *AFTER* the scanline has been commmitted
/// Sets up the upcoming memory addresses for the next scanline
/// </summary>
private void CalculateNextScreenMemory()
{
var verCharCount = CRCT.VCC;
var verRasCount = CRCT.VLC;
var screenWidthByteCount = CRCT.DisplayWidth * 2;
NextVidRamLine = new ushort[screenWidthByteCount * 2];
var screenHeightCharCount = CRCT.DisplayHeightInChars;
var screenAddress = CRCT.MA;
int baseAddress = ((screenAddress << 2) & 0xf000);
int offset = (screenAddress * 2) & 0x7ff;
int x = offset + ((verCharCount * screenWidthByteCount) & 0x7ff);
int y = baseAddress + (verRasCount * 0x800);
for (int b = 0; b < screenWidthByteCount; b++)
{
NextVidRamLine[b] = (ushort)(y + x);
x++;
x &= 0x7ff;
}
}
/// <summary>
/// Called at the start of HSYNC, this renders the currently built-up scanline
/// </summary>
private void RenderScanline()
{
// memory addresses
int cRow = CRCT.VCC;
int cRas = CRCT.VLC;
int screenByteWidth = CRCT.DisplayWidth * 2;
var screenHeightCharCount = CRCT.DisplayHeightInChars;
//CalculateNextScreenMemory();
var crctAddr = CRCT.DStartHigh << 8;
crctAddr |= CRCT.DStartLow;
var baseAddr = ((crctAddr << 2) & (0xF000)); //CRCT.VideoPageBase;//
var baseOffset = (crctAddr * 2) & 0x7FF; //CRCT.VideoRAMOffset * 2; //
var xA = baseOffset + ((cRow * screenByteWidth) & 0x7ff);
var yA = baseAddr + (cRas * 2048);
// border and display
int pix = 0;
int scrByte = 0;
for (int i = 0; i < CurrentLine.PhaseCount; i++)
{
// every character renders 8 pixels
switch (CurrentLine.Phases[i])
{
case Phase.NONE:
break;
case Phase.HSYNC:
break;
case Phase.HSYNCandVSYNC:
break;
case Phase.VSYNC:
break;
case Phase.BORDER:
// output current border colour
for (pix = 0; pix < 16; pix++)
{
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[16]]);
}
break;
case Phase.DISPLAY:
// each character references 2 bytes in video RAM
byte data;
for (int by = 0; by < 2; by++)
{
ushort addr = (ushort)(yA + xA);
data = _machine.FetchScreenMemory(addr);
scrByte++;
switch (CurrentLine.ScreenMode)
{
case 0:
pix = data & 0xaa;
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
for (int c = 0; c < 4; c++)
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
pix = data & 0x55;
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
for (int c = 0; c < 4; c++)
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
break;
case 1:
pix = (((data & 0x80) >> 7) | ((data & 0x08) >> 2));
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
pix = (((data & 0x40) >> 6) | ((data & 0x04) >> 1));
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
pix = (((data & 0x20) >> 5) | (data & 0x02));
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
pix = (((data & 0x10) >> 4) | ((data & 0x01) << 1));
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
break;
case 2:
pix = data;
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(7) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(6) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(5) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(4) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(3) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(2) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(1) ? 1 : 0]]);
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(0) ? 1 : 0]]);
break;
case 3:
pix = data & 0xaa;
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
for (int c = 0; c < 4; c++)
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
pix = data & 0x55;
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
for (int c = 0; c < 4; c++)
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
break;
}
xA++;
xA &= 0x7ff;
}
break;
}
}
// add to the list
ScreenLines.Add(new CharacterLine
{
ScreenMode = CurrentLine.ScreenMode,
Phases = CurrentLine.Phases.ToList(),
Pixels = CurrentLine.Pixels.ToList()
});
}
#endregion
#region Public Methods
/// <summary>
/// Called when the Z80 acknowledges an interrupt
/// </summary>
public void IORQA()
{
// bit 5 of the interrupt counter is reset
InterruptCounter &= ~(1 << 5);
}
private int slCounter = 0;
private int slBackup = 0;
/// <summary>
/// Fired when the CRCT flags HSYNC
/// </summary>
public void OnHSYNC()
{
HSYNC = true;
slCounter++;
// commit the scanline
RenderScanline();
// setup vid memory for next scanline
CalculateNextScreenMemory();
if (CRCT.VLC == 0)
{
// update screenmode
ScreenMode = _RMR & 0x03;
}
// setup scanline for next
CurrentLine.Clear(ScreenMode);
}
/// <summary>
/// Fired when the CRCT flags VSYNC
/// </summary>
public void OnVSYNC()
{
FrameEnd = true;
slBackup = slCounter;
slCounter = 0;
}
#endregion
#region IVideoProvider
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 SysBufferWidth;
public int SysBufferHeight;
public int VsyncNumerator
{
get { return 200000000; }
set { }
}
public int VsyncDenominator
{
get { return Z80ClockSpeed; }
}
public int[] GetVideoBuffer()
{
// get only lines that have pixel data
var lines = ScreenLines.Where(a => a.Pixels.Count > 0);
int pos = 0;
int lCount = 0;
foreach (var l in lines)
{
var lCop = l.Pixels.ToList();
var len = l.Pixels.Count;
if (l.Phases.Contains(Phase.VSYNC) && l.Phases.Contains(Phase.BORDER))
continue;
if (len < 320)
continue;
var pad = BufferWidth - len;
if (pad < 0)
{
// trim the left and right
var padPos = pad * -1;
var excessL = padPos / 2;
var excessR = excessL + (padPos % 2);
for (int i = 0; i < excessL; i++)
{
var lThing = lCop.First();
lCop.Remove(lThing);
}
for (int i = 0; i < excessL; i++)
{
var lThing = lCop.Last();
lCop.Remove(lThing);
}
}
var lPad = pad / 2;
var rPad = lPad + (pad % 2);
for (int i = 0; i < 2; i++)
{
lCount++;
for (int pL = 0; pL < lPad; pL++)
{
ScreenBuffer[pos++] = 0;
}
for (int pix = 0; pix < lCop.Count; pix++)
{
ScreenBuffer[pos++] = lCop[pix];
}
for (int pR = 0; pR < rPad; pR++)
{
ScreenBuffer[pos++] = 0;
}
}
if (lCount >= BufferHeight - 2)
{
break;
}
}
ScreenLines.Clear();
return ScreenBuffer;
/*
switch (borderType)
{
// crop to 768x272 (544)
// borders 64px - 64 scanlines
case AmstradCPC.BorderType.Uniform:
/*
var slSize = 64;
var dispLines = (24 * 8) * 2;
var origTopBorder = (7 * 8) * 2;
var origBotBorder = (5 * 8) * 2;
var lR = 16;
var rR = 16;
var trimTop = origTopBorder - slSize;
var startP = SysBufferWidth * (origTopBorder - 64);
var index1 = 0;
// line by line
int cnt = 0;
for (int line = startP; line < ScreenBuffer.Length; line += SysBufferWidth)
{
cnt++;
// pixels in line
for (int p = lR; p < SysBufferWidth - rR; p++)
{
if (index1 == croppedBuffer.Length)
break;
croppedBuffer[index1++] = ScreenBuffer[line + p];
}
}
return croppedBuffer;
*/
/*
var slWidth = BufferWidth;
return ScreenBuffer;
break;
}
*/
//return ScreenBuffer;
}
public void SetupScreenSize()
{
SysBufferWidth = 800;
SysBufferHeight = 600;
BufferHeight = SysBufferHeight;
BufferWidth = SysBufferWidth;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
ScreenBuffer = new int[BufferWidth * BufferHeight];
croppedBuffer = ScreenBuffer;
switch (borderType)
{
case AmstradCPC.BorderType.Uncropped:
break;
case AmstradCPC.BorderType.Uniform:
BufferWidth = 800;
BufferHeight = 600;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
croppedBuffer = new int[BufferWidth * BufferHeight];
break;
case AmstradCPC.BorderType.Widescreen:
break;
}
}
protected int[] croppedBuffer;
private AmstradCPC.BorderType _borderType;
public AmstradCPC.BorderType borderType
{
get { return _borderType; }
set { _borderType = value; }
}
#endregion
#region IPortIODevice
/// <summary>
/// Device responds to an IN instruction
/// </summary>
public bool ReadPort(ushort port, ref int result)
{
// gate array is OUT only
return false;
}
/// <summary>
/// Device responds to an OUT instruction
/// </summary>
public bool WritePort(ushort port, int result)
{
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result));
byte portUpper = (byte)(port >> 8);
byte portLower = (byte)(port & 0xff);
// The gate array is selected when bit 15 of the I/O port address is set to "0" and bit 14 of the I/O port address is set to "1"
bool accessed = false;
if (!portUpper.Bit(7) && portUpper.Bit(6))
accessed = true;
if (!accessed)
return accessed;
// Bit 9 and 8 of the data byte define the function to access
if (!dataBits[6] && !dataBits[7])
{
// select pen
PENR = (byte)result;
}
if (dataBits[6] && !dataBits[7])
{
// select colour for selected pen
INKR = (byte)result;
}
if (!dataBits[6] && dataBits[7])
{
// select screen mode, ROM configuration and interrupt control
RMR = (byte)result;
}
if (dataBits[6] && dataBits[7])
{
// RAM memory management
RAMR = (byte)result;
}
return true;
}
#endregion
#region Serialization
public void SyncState(Serializer ser)
{
ser.BeginSection("GateArray");
ser.SyncEnum(nameof(ChipType), ref ChipType);
ser.Sync(nameof(_PENR), ref _PENR);
ser.Sync(nameof(_INKR), ref _INKR);
ser.Sync(nameof(_RMR), ref _RMR);
ser.Sync(nameof(_RAMR), ref _RAMR);
ser.Sync(nameof(ColourRegisters), ref ColourRegisters, false);
ser.Sync(nameof(CurrentPen), ref CurrentPen);
ser.Sync(nameof(ClockCounter), ref ClockCounter);
ser.Sync(nameof(FrameClock), ref FrameClock);
ser.Sync(nameof(FrameEnd), ref FrameEnd);
ser.Sync(nameof(WaitLine), ref WaitLine);
ser.Sync(nameof(_interruptCounter), ref _interruptCounter);
ser.Sync(nameof(VSYNCDelay), ref VSYNCDelay);
ser.Sync(nameof(ScreenMode), ref ScreenMode);
ser.Sync(nameof(HSYNC), ref HSYNC);
//ser.Sync(nameof(HSYNC_falling), ref HSYNC_falling);
ser.Sync(nameof(HSYNC_counter), ref HSYNC_counter);
ser.Sync(nameof(VSYNC), ref VSYNC);
ser.Sync(nameof(InterruptRaised), ref InterruptRaised);
ser.Sync(nameof(InterruptHoldCounter), ref InterruptHoldCounter);
ser.Sync(nameof(_MA), ref _MA);
ser.Sync(nameof(IsNewFrame), ref IsNewFrame);
ser.Sync(nameof(IsNewLine), ref IsNewLine);
ser.Sync(nameof(HCC), ref HCC);
ser.Sync(nameof(VLC), ref VLC);
ser.Sync(nameof(VideoByte1), ref VideoByte1);
ser.Sync(nameof(VideoByte2), ref VideoByte2);
ser.Sync(nameof(NextVidRamLine), ref NextVidRamLine, false);
ser.EndSection();
}
#endregion
#region Enums, Classes & Lookups
/// <summary>
/// Represents a single scanline (in characters)
/// </summary>
public class CharacterLine
{
/// <summary>
/// Screenmode is defined at each HSYNC (start of a new character line)
/// Therefore we pass the mode in via constructor
/// </summary>
//public CharacterLine(int screenMode)
//{
//ScreenMode = screenMode;
//}
public int ScreenMode = 1;
public List<Phase> Phases = new List<Phase>();
public List<int> Pixels = new List<int>();
/// <summary>
/// Adds a new horizontal character to the list
/// </summary>
public void AddCharacter(Phase phase)
{
Phases.Add(phase);
}
public int PhaseCount
{
get { return Phases.Count(); }
}
public void Clear(int screenMode)
{
ScreenMode = screenMode;
Phases.Clear();
Pixels.Clear();
}
}
[Flags]
public enum Phase : int
{
/// <summary>
/// Nothing
/// </summary>
NONE = 0,
/// <summary>
/// Border is being rendered
/// </summary>
BORDER = 1,
/// <summary>
/// Display rendered from video RAM
/// </summary>
DISPLAY = 2,
/// <summary>
/// HSYNC in progress
/// </summary>
HSYNC = 3,
/// <summary>
/// VSYNC in process
/// </summary>
VSYNC = 4,
/// <summary>
/// HSYNC occurs within a VSYNC
/// </summary>
HSYNCandVSYNC = 5
}
public enum GateArrayType
{
/// <summary>
/// CPC 464
/// The first version of the Gate Array is the 40007 and was released with the CPC 464
/// </summary>
Amstrad40007,
/// <summary>
/// CPC 664
/// Later, the CPC 664 came out fitted with the 40008 version (and at the same time, the CPC 464 was also upgraded with this version).
/// This version is pinout incompatible with the 40007 (that's why the upgraded 464 of this period have two Gate Array slots on the motherboard,
/// one for a 40007 and one for a 40008)
/// </summary>
Amstrad40008,
/// <summary>
/// CPC 6128
/// The CPC 6128 was released with the 40010 version (and the CPC 464 and 664 manufactured at that time were also upgraded to this version).
/// The 40010 is pinout compatible with the previous 40008
/// </summary>
Amstrad40010,
/// <summary>
/// Costdown CPC
/// In the last serie of CPC 464 and 6128 produced by Amstrad in 1988, a small ASIC chip have been used to reduce the manufacturing costs.
/// This ASIC emulates the Gate Array, the PAL and the CRTC 6845. And no, there is no extra features like on the Amstrad Plus.
/// The only noticeable difference seems to be about the RGB output levels which are not exactly the same than those produced with a real Gate Array
/// </summary>
Amstrad40226,
/// <summary>
/// Plus & GX-4000
/// All the Plus range is built upon a bigger ASIC chip which is integrating many features of the classic CPC (FDC, CRTC, PPI, Gate Array/PAL) and all
/// the new Plus specific features. The Gate Array on the Plus have a new register, named RMR2, to expand the ROM mapping functionnalities of the machine.
/// This register requires to be unlocked first to be available. And finally, the RGB levels produced by the ASIC on the Plus are noticeably differents
/// </summary>
Amstrad40489,
}
#endregion
}
}