CPCHawk: Gatearray now displaying a picture (mode1)

This commit is contained in:
Asnivor 2018-07-13 16:34:36 +01:00
parent 6863368dd3
commit 4192f764b1
10 changed files with 946 additions and 462 deletions

View File

@ -139,7 +139,7 @@
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IKeyboard.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IPortIODevice.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IPSG.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\CRCT\CRCT_6845.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Display\CRCT_6845.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Datacorder\DatacorderDevice.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Disk\CHRN.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.cs" />
@ -149,7 +149,8 @@
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.IPortIODevice.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.Timing.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPS765.Static.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\GateArray\AmstradGateArray.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Display\AmstradGateArray.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Display\CRTDevice.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Input\StandardKeyboard.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\AY38912.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\Beeper.cs" />

View File

@ -30,6 +30,10 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
public Action IRQCallback = delegate () { };
public Action NMICallback = delegate () { };
// this will be a few cycles off for now
// it should suffice for now until Alyosha returns from hiatus
public Action IRQACKCallback = delegate () { };
private void NMI_()
{
cur_instr = new ushort[]
@ -47,7 +51,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
BUSRQ = new ushort[] { 0, SPh, 0, 0, SPh, 0, 0, PCh, 0, 0, 0 };
MEMRQ = new ushort[] { 0, SPh, 0, 0, SPh, 0, 0, PCh, 0, 0, 0 };
}
}
// Mode 0 interrupts only take effect if a CALL or RST is on the data bus
// Otherwise operation just continues as normal
@ -67,7 +71,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
BUSRQ = new ushort[] { PCh, 0, 0, PCh, 0, 0, 0 };
MEMRQ = new ushort[] { PCh, 0, 0, PCh, 0, 0, 0 };
}
IRQACKCallback();
}
// Just jump to $0038
private void INTERRUPT_1()
@ -89,7 +95,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
BUSRQ = new ushort[] { I, 0, 0, SPh, 0, 0, SPh, 0, 0, PCh, 0, 0, 0 };
MEMRQ = new ushort[] { I, 0, 0, SPh, 0, 0, SPh, 0, 0, PCh, 0, 0, 0 };
}
IRQACKCallback();
}
// Interrupt mode 2 uses the I vector combined with a byte on the data bus
private void INTERRUPT_2()
@ -117,7 +125,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
BUSRQ = new ushort[] { I, 0, 0, SPh, 0, 0, SPh, 0, 0, W, 0, 0, W, 0 ,0 ,PCh, 0, 0, 0 };
MEMRQ = new ushort[] { I, 0, 0, SPh, 0, 0, SPh, 0, 0, W, 0, 0, W, 0, 0, PCh, 0, 0, 0 };
}
IRQACKCallback();
}
private void ResetInterrupts()
{

View File

@ -63,11 +63,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_cpu.ReadHardware = _machine.ReadPort;
_cpu.WriteHardware = _machine.WritePort;
_cpu.FetchDB = _machine.PushBus;
_cpu.IRQACKCallback = _machine.GateArray.IORQA;
//_cpu.OnExecFetch = _machine.CPUMon.OnExecFetch;
ser.Register<ITraceable>(_tracer);
ser.Register<IDisassemblable>(_cpu);
ser.Register<IVideoProvider>(_machine.GateArray);
ser.Register<IVideoProvider>(_machine.CRT);
// initialize sound mixer and attach the various ISoundProvider devices
SoundMixer = new SoundProviderMixer((int)(32767 / 10), "Tape Audio", (ISoundProvider)_machine.TapeBuzzer);

View File

@ -16,13 +16,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// 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 : IVideoProvider, IPortIODevice
public class AmstradGateArray : IPortIODevice
{
#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;
@ -67,84 +68,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#endregion
#region Palletes
/// <summary>
/// The standard CPC Pallete (ordered by firmware #)
/// http://www.cpcwiki.eu/index.php/CPC_Palette
/// </summary>
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
};
/// <summary>
/// The standard CPC Pallete (ordered by hardware #)
/// http://www.cpcwiki.eu/index.php/CPC_Palette
/// </summary>
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
@ -153,7 +77,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_machine = machine;
ChipType = chipType;
//PenColours = new int[17];
SetupScreenSize();
CRT.SetupScreenSize();
//Reset();
}
@ -193,10 +117,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
/// <summary>
/// 0-16: Pen Registers
/// 17: Border Colour
/// 0-15: Pen Registers
/// 16: Border Colour
/// </summary>
private int[] ColourRegisters = new int[17];
public int[] ColourRegisters = new int[17];
/// <summary>
/// The currently selected Pen
@ -243,7 +167,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
set
{
_RMR = value;
ScreenMode = _RMR & 0x03;
//ScreenMode = _RMR & 0x03;
if ((_RMR & 0x08) != 0)
_machine.UpperROMPaged = false;
@ -254,6 +178,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_machine.LowerROMPaged = false;
else
_machine.LowerROMPaged = true;
if (_RMR.Bit(4))
{
// reset interrupt counter
InterruptCounter = 0;
}
}
}
@ -297,7 +227,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
/// <summary>
/// The selected screen mode
/// The selected screen mode (updated after every HSYNC)
/// </summary>
private int ScreenMode;
@ -344,12 +274,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
/// <summary>
/// Video mode change is syncronised with HSYNC. When the mode is change it takes effect
/// from the next HSYNC
/// </summary>
private int LatchedMode;
/// <summary>
/// Set when the HSYNC signal is detected from the CRCT
/// </summary>
@ -383,16 +307,34 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
private int InterruptHoldCounter;
/// <summary>
/// 2 state field
/// Because the renderer outputs 1 pixel for every 2 GA cycles
/// Set at the start of a new frame
/// </summary>
private bool RendererFlipFlop = true;
public bool IsNewFrame;
/// <summary>
/// Used for counting the screen buffer positions
/// Set when a new line is beginning
/// </summary>
private int RenderCounter;
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
@ -406,14 +348,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
FrameClock++;
ClockCounter++;
if (ClockCounter == 16)
if (ClockCounter == 4)
ClockCounter = 0;
// check for frame end
if (FrameClock == GAFrameLength)
if (FrameClock == FrameLength)
{
FrameClock = 0;
//FrameEnd = true;
FrameEnd = true;
}
}
@ -433,8 +375,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// /WAIT line is active
switch (ClockCounter)
{
case 8:
case 12:
case 2:
case 3:
// gate array video fetch is occuring
// check for memory access
if (BUSRQ > 0)
@ -444,7 +386,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
break;
case 4:
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)
@ -462,10 +404,145 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
/// <summary>
/// Performs one gate array (rendering) cycle
/// Handles interrupt generation
/// </summary>
private void DoCycle()
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;
}
}
}
/// <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&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);
//CRT.InitScanline(VLC, ScreenMode);
}
else if (!CRCT.HSYNC)
{
// reset the flags
IsNewLine = false;
IsNewFrame = false;
}
}
/// <summary>
/// Fetches a video RAM byte
/// This happens at 2Mhz when a memory fetch is due
/// </summary>
/// <param name="index"></param>
private void FetchByte(int index)
{
switch (index)
{
case 1:
VideoByte1 = _machine.FetchScreenMemory(CRCT.CurrentByteAddress);
break;
case 2:
VideoByte2 = _machine.FetchScreenMemory((ushort)(CRCT.CurrentByteAddress + 1));
break;
}
}
/// <summary>
/// Called at 1Mhz
/// Generates the internal screen layout (to be displayed at the end of the frame by the CRT)
/// Each PixelGenerator cycle will process 1 horizontal character
/// If the area to generate is in display RAM, 2 bytes will be processed
/// </summary>
private void PixelGenerator()
{
// mode 0: 160x200 pixels: 1 character == 1Mhz == 2 pixels == 4 bits per pixel = 2 pixels per screenbyte
// mode 1: 320x200 pixels: 1 character == 1Mhz == 4 pixels == 2 bits per pixel = 4 pixels per screenbyte
// mode 2: 640x200 pixels: 1 character == 1Mhz == 8 pixels == 1 bits per pixel = 8 pixels per screenbyte
// mode 3: 160x200 pixels: 1 character == 1Mhz == 2 pixels == 2 bits per pixel = 2 pixels per screenbyte
/*
http://www.cpcmania.com/Docs/Programming/Painting_pixels_introduction_to_video_memory.htm
http://www.cantrell.org.uk/david/tech/cpc/cpc-firmware/
@ -524,157 +601,34 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
Screen layout and generation: http://www.cpcwiki.eu/forum/programming/rupture/?action=dlattach;attach=16221
*/
// run the interrupt generator routine
InterruptGenerator();
#region Testing
if (CRCT.DISPTMG)
if (CRCT.VSYNC && CRCT.HSYNC)
{
displayCounter++;
}
else if (CRCT.HSYNC)
{
hsyncCounter++;
}
else if (!CRCT.DISPTMG)
{
borderCounter++;
// both hsync and vsync active
CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.HSYNCandVSYNC, VideoByte1, VideoByte2, ColourRegisters);
//CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.HSYNCandVSYNC, VideoByte1, VideoByte2, ColourRegisters);
}
else if (CRCT.VSYNC)
{
vsyncCounter++;
// vsync is active but hsync is not
CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.VSYNC, VideoByte1, VideoByte2, ColourRegisters);
//CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.VSYNC, VideoByte1, VideoByte2, ColourRegisters);
}
if (!CRCT.HSYNC && HSYNC)
else if (CRCT.HSYNC)
{
// end of line
displayCounter = 0;
hsyncCounter = 0;
borderCounter = 0;
vsyncCounter = 0;
lineCounter++;
// hsync is active but vsync is not
CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.HSYNC, VideoByte1, VideoByte2, ColourRegisters);
//CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.HSYNC, VideoByte1, VideoByte2, ColourRegisters);
}
if (borderCounter > 160)
else if (!CRCT.DISPTMG)
{
// border generation
CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.BORDER, VideoByte1, VideoByte2, ColourRegisters);
//CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.BORDER, VideoByte1, VideoByte2, ColourRegisters);
}
if (CRCT.VSYNC)
else if (CRCT.DISPTMG)
{
}
if (!CRCT.VSYNC && VSYNC)
{
// end of screen
lineCounter = 0;
}
#endregion
// When the start of the vertical sync is seen by the monitor it starts the next frame. This means border
// is effectively split between top and bottom of the display. Border above the VSYNC is the bottom
// border, Border below the VSYNC is the top border
if (!VSYNC && CRCT.VSYNC)
{
VSYNC = true;
FrameEnd = true;
return;
}
// update HSYNC & VSYNC from CRCT
HSYNC = CRCT.HSYNC;
VSYNC = CRCT.VSYNC;
// 2 GA cycles per pixel
RendererFlipFlop = !RendererFlipFlop;
if (RendererFlipFlop)
{
if (HSYNC)
{
// HSYNC in progress
// output black
}
else if (!CRCT.DISPTMG)
{
// outputting border colour
ScreenBuffer[RenderCounter++] = CPCHardwarePalette[ColourRegisters[16]];
}
else if (CRCT.DISPTMG)
{
// outputting vid RAM
Random rnd = new Random();
ScreenBuffer[RenderCounter++] = CPCHardwarePalette[ColourRegisters[1]];
}
if (CRCT.VSYNC)
{
RenderCounter = 40;
}
}
}
int displayCounter = 0;
int hsyncCounter = 0;
int borderCounter = 0;
int vsyncCounter = 0;
int lineCounter = 0;
/// <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;
}
}
if (InterruptCounter == 52)
{
// gatearray should raise an interrupt
InterruptRaised = true;
InterruptCounter = 0;
}
}
if (InterruptRaised)
{
// interrupt has been raised
CPU.IFF1 = 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.IFF1 = false;
InterruptRaised = false;
}
// pixels generated from video RAM
CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.DISPLAY, VideoByte1, VideoByte2, ColourRegisters);
}
}
@ -683,119 +637,64 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#region Public Methods
/// <summary>
/// The gate array is clocked at 16Mhz
/// It provides the CPU clock at 4Mhz
/// The CRCT clock at 1Mhz
/// The PSG clock at 1Mhz
///
/// Each time this method is called, the gatearray performs 16 cycles
/// (equivalent to 4 uncontended CPU cycles)
/// 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 DoCycles()
public void ClockCycle()
{
// 16 gatearray cycles
// gatearray uses 4-phase clock to supply clocks to other devices
switch (ClockCounter)
{
// 0Mhz
case 0:
// wait line inactive
WaitLine = false;
CRCT.ClockCycle();
//psg
// GA render cycle
DoCycle();
// CPU
DoConditionalCPUCycle();
// cycle the tape device
if (FDC == null || !FDC.FDD_IsDiskLoaded)
DATACORDER.TapeCycle();
//psg clockcycle
WaitLine = false;
break;
// 4Mhz
case 4:
// wait line active
case 1:
WaitLine = true;
// GA render cycle
DoCycle();
// CPU
DoConditionalCPUCycle();
// cycle the tape device
if (FDC == null || !FDC.FDD_IsDiskLoaded)
DATACORDER.TapeCycle();
// detect new scanline and upcoming new frame on next render cycle
FrameDetector();
break;
// 8Mhz
case 8:
// wait line active
WaitLine = true;
// GA render cycle
DoCycle();
// CPU
DoConditionalCPUCycle();
case 2:
// video fetch
// cycle the tape device
if (FDC == null || !FDC.FDD_IsDiskLoaded)
DATACORDER.TapeCycle();
break;
// 12Mhz
case 12:
// wait line active
WaitLine = true;
// GA render cycle
DoCycle();
// CPU
DoConditionalCPUCycle();
// video fetch
// cycle the tape device
if (FDC == null || !FDC.FDD_IsDiskLoaded)
DATACORDER.TapeCycle();
break;
// all other GA cycles
default:
// GA render cycle
DoCycle();
//if (CRCT.DISPTMG)
FetchByte(1);
break;
case 3:
// video fetch and render
WaitLine = true;
//if (CRCT.DISPTMG)
FetchByte(2);
PixelGenerator();
break;
}
// run the interrupt generator routine
InterruptGenerator();
// conditional CPU cycle
DoConditionalCPUCycle();
AdvanceClock();
}
#endregion
#region VideoLookups
public struct PixelLookupTable
{
}
/// <summary>
/// Runs at the start of a frame in order to setup the
/// video buffer (in case the CRCT has changed anything)
/// Called when the Z80 acknowledges an interrupt
/// </summary>
public void SetupVideo()
public void IORQA()
{
// bit 5 of the interrupt counter is reset
InterruptCounter &= ~(1 << 5);
}
#endregion
#region IPortIODevice
/// <summary>
@ -856,101 +755,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#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 { return CPCHardwarePalette[16]; }
}
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 Z80ClockSpeed * 50; }
set { }
}
public int VsyncDenominator
{
get { return Z80ClockSpeed; }
}
public int[] GetVideoBuffer()
{
/*
Random rnd = new Random();
for (int i = 0; i < BufferWidth * BufferHeight; i++)
{
ScreenBuffer[i] = CPCHardwarePalette[rnd.Next(0, CPCHardwarePalette.Length - 1)];
}
*/
//RenderCounter = 0;
return ScreenBuffer;
}
protected void SetupScreenSize()
{
/*
* Rect Pixels: Mode 0: 160×200 pixels with 16 colors (4 bpp)
Square 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 = 400; // 640;
BufferHeight = 400;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
ScreenBuffer = new int[BufferWidth * BufferHeight];
croppedBuffer = ScreenBuffer;
}
protected int[] croppedBuffer;
#endregion
#region Serialization
@ -969,35 +774,19 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Sync("WaitLine", ref WaitLine);
ser.Sync("_interruptCounter", ref _interruptCounter);
ser.Sync("ScreenMode", ref ScreenMode);
ser.Sync("LatchedMode", ref LatchedMode);
ser.Sync("HSYNC", ref HSYNC);
ser.Sync("HSYNC_falling", ref HSYNC_falling);
ser.Sync("HSYNC_counter", ref HSYNC_counter);
ser.Sync("VSYNC", ref VSYNC);
ser.Sync("InterruptRaised", ref InterruptRaised);
ser.Sync("InterruptHoldCounter", ref InterruptHoldCounter);
ser.Sync("RendererFlipFlop", ref RendererFlipFlop);
ser.Sync("_MA", ref _MA);
ser.EndSection();
/*
* /// <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, processing happens
/// </summary>
private bool ;
/// <summary>
/// Used to count HSYNCs during a VSYNC
/// </summary>
private int ;
* */
}
#endregion
#region Enums
#region Enums & Classes
public enum GateArrayType
{

View File

@ -1,4 +1,5 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Collections;
@ -59,7 +60,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#region Public Lookups
/*
* These are not accessible on real hardware
* These are not accessible directlyon real hardware
* It just makes screen generation easier to have these accessbile from the gate array
*/
@ -147,7 +148,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
get
{
return VSYNCWidth * ((int)Regs[MAX_RASTER_ADDR] + 1);
return VSYNCWidth; // * ((int)Regs[MAX_RASTER_ADDR] + 1);
}
}
@ -162,6 +163,53 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
/// <summary>
/// Returns the starting video page address as specified within R12
/// </summary>
public int VideoPageBase
{
get
{
if (!Regs[12].Bit(4) && Regs[12].Bit(5))
return 0x8000;
if (Regs[12].Bit(4) && !Regs[12].Bit(5))
return 0x4000;
if (!Regs[12].Bit(4) && !Regs[12].Bit(5))
return 0x0000;
return 0xC000;
}
}
/// <summary>
/// Returns the video buffer size as specified within R12
/// </summary>
public int VideoBufferSize
{
get
{
if (Regs[12].Bit(3) && Regs[12].Bit(2))
return 0x8000;
return 0x4000;
}
}
/* Easier memory functions */
/// <summary>
/// The current byte address
/// </summary>
public ushort CurrentByteAddress;
/// <summary>
/// ByteCOunter
/// </summary>
public int ByteCounter;
#endregion
#region Internal Registers and State
@ -387,6 +435,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// HSYNC in progress
HSYNCCounter++;
ByteCounter = 0;
if (HSYNCCounter >= HSYNCWidth)
{
// end of HSYNC
@ -403,9 +453,20 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
DISPTMG = false;
}
else if (VCC >= Regs[VER_DISPLAYED])
{
DISPTMG = false;
}
else
{
DISPTMG = true;
var line = VCC;
var row = VLC;
var addr = VideoPageBase + (line * 0x50) + (row * 0x800) + (ByteCounter);
CurrentByteAddress = (ushort)addr;
ByteCounter += 2;
}
// check for the end of the current scanline
@ -574,6 +635,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
if (SelectedRegister > 17)
return;
if (SelectedRegister == DISP_START_ADDR_L)
{
}
if (SelectedRegister == DISP_START_ADDR_H)
{
}
Regs[SelectedRegister] = (byte)(data & CPCMask[SelectedRegister]);
if (SelectedRegister == HOR_AND_VER_SYNC_WIDTHS)

View File

@ -0,0 +1,573 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// Render pixels to the screen
/// </summary>
public class CRTDevice : IVideoProvider
{
#region Devices
private CPCBase _machine;
private CRCT_6845 CRCT => _machine.CRCT;
private AmstradGateArray GateArray => _machine.GateArray;
#endregion
#region Construction
public CRTDevice(CPCBase machine)
{
_machine = machine;
CurrentLine = new ScanLine(this);
}
#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 Public Stuff
/// <summary>
/// The current scanline that is being added to
/// (will be processed and committed to the screen buffer every HSYNC)
/// </summary>
public ScanLine CurrentLine;
/// <summary>
/// The number of top border scanlines to ommit when rendering
/// </summary>
public int TopLinesToTrim = 20;
/// <summary>
/// Count of rendered scanlines this frame
/// </summary>
public int ScanlineCounter = 0;
/// <summary>
/// Video buffer processing
/// </summary>
public int[] ProcessVideoBuffer()
{
return ScreenBuffer;
}
/// <summary>
/// Sets up buffers and the like at the start of a frame
/// </summary>
public void SetupVideo()
{
if (BufferHeight == 576)
return;
BufferWidth = 800;
BufferHeight = 576;
VirtualWidth = BufferWidth / 2;
VirtualHeight = BufferHeight / 2;
ScreenBuffer = new int[BufferWidth * BufferHeight];
}
#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 { 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 VsyncNumerator
{
get { return GateArray.Z80ClockSpeed * 50; }
set { }
}
public int VsyncDenominator
{
get { return GateArray.Z80ClockSpeed; }
}
public int[] GetVideoBuffer()
{
return ProcessVideoBuffer();
}
public void SetupScreenSize()
{
BufferWidth = 1024; // 512;
BufferHeight = 768;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
ScreenBuffer = new int[BufferWidth * BufferHeight];
croppedBuffer = ScreenBuffer;
}
protected int[] croppedBuffer;
#endregion
#region Serialization
public void SyncState(Serializer ser)
{
ser.BeginSection("CRT");
ser.EndSection();
}
#endregion
}
/// <summary>
/// Represents a single scanline buffer
/// </summary>
public class ScanLine
{
/// <summary>
/// Array of character information
/// </summary>
public Character[] Characters;
/// <summary>
/// The screenmode that was set at the start of this scanline
/// </summary>
public int ScreenMode = 1;
/// <summary>
/// The scanline number (0 based)
/// </summary>
public int LineIndex;
/// <summary>
/// The calling CRT device
/// </summary>
private CRTDevice CRT;
public ScanLine(CRTDevice crt)
{
Reset();
CRT = crt;
}
// To be run after scanline has been fully processed
public void InitScanline(int screenMode, int lineIndex)
{
Reset();
ScreenMode = screenMode;
LineIndex = lineIndex;
}
/// <summary>
/// Adds a single scanline character into the matrix
/// </summary>
/// <param name="charIndex"></param>
/// <param name="phase"></param>
public void AddScanlineCharacter(int index, RenderPhase phase, byte vid1, byte vid2, int[] pens)
{
switch (phase)
{
case RenderPhase.BORDER:
AddBorderValue(index, CRTDevice.CPCHardwarePalette[pens[16]]);
break;
case RenderPhase.DISPLAY:
AddDisplayValue(index, vid1, vid2, pens);
break;
default:
AddSyncValue(index, phase);
break;
}
}
/// <summary>
/// Adds a HSYNC, VSYNC or HSYNC+VSYNC character into the scanline
/// </summary>
/// <param name="charIndex"></param>
/// <param name="phase"></param>
private void AddSyncValue(int charIndex, RenderPhase phase)
{
Characters[charIndex].Phase = phase;
Characters[charIndex].Pixels = new int[0];
}
/// <summary>
/// Adds a border character into the scanline
/// </summary>
/// <param name="index"></param>
/// <param name="colourValue"></param>
private void AddBorderValue(int charIndex, int colourValue)
{
Characters[charIndex].Phase = RenderPhase.BORDER;
Characters[charIndex].Pixels = new int[8];
for (int i = 0; i < Characters[charIndex].Pixels.Length; i++)
{
Characters[charIndex].Pixels[i] = colourValue;
}
}
/// <summary>
/// Adds a display character into the scanline
/// Pixel matrix is calculated based on the current ScreenMode
/// </summary>
/// <param name="charIndex"></param>
/// <param name="vid1"></param>
/// <param name="vid2"></param>
public void AddDisplayValue(int charIndex, byte vid1, byte vid2, int[] pens)
{
Characters[charIndex].Phase = RenderPhase.DISPLAY;
// generate pixels based on screen mode
switch (ScreenMode)
{
// 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels)
case 0:
Characters[charIndex].Pixels = new int[4];
int m0Count = 0;
int m0B0P0i = vid1 & 170;
int m0B0P0 = ((m0B0P0i & 0x80) >> 7) | ((m0B0P0i & 0x08) >> 2) | ((m0B0P0i & 0x20) >> 3) | ((m0B0P0i & 0x02 << 2));
int m0B0P1i = vid1 & 85;
int m0B0P1 = ((m0B0P1i & 0x40) >> 6) | ((m0B0P1i & 0x04) >> 1) | ((m0B0P1i & 0x10) >> 2) | ((m0B0P1i & 0x01 << 3));
Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P0]];
Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P1]];
int m0B1P0i = vid1 & 170;
int m0B1P0 = ((m0B1P0i & 0x80) >> 7) | ((m0B1P0i & 0x08) >> 2) | ((m0B1P0i & 0x20) >> 3) | ((m0B1P0i & 0x02 << 2));
int m0B1P1i = vid1 & 85;
int m0B1P1 = ((m0B1P1i & 0x40) >> 6) | ((m0B1P1i & 0x04) >> 1) | ((m0B1P1i & 0x10) >> 2) | ((m0B1P1i & 0x01 << 3));
Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P0]];
Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]];
break;
// 2 bits per pixel - 2 bytes - 8 pixels (16 CRT pixels)
case 1:
Characters[charIndex].Pixels = new int[8];
int m1Count = 0;
int m1B0P0 = (((vid1 & 0x80) >> 7) | ((vid1 & 0x08) >> 2));
int m1B0P1 = (((vid1 & 0x40) >> 6) | ((vid1 & 0x04) >> 1));
int m1B0P2 = (((vid1 & 0x20) >> 5) | ((vid1 & 0x02)));
int m1B0P3 = (((vid1 & 0x10) >> 4) | ((vid1 & 0x01) << 1));
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P0]];
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P1]];
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P2]];
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P3]];
int m1B1P0 = (((vid2 & 0x80) >> 7) | ((vid2 & 0x08) >> 2));
int m1B1P1 = (((vid2 & 0x40) >> 6) | ((vid2 & 0x04) >> 1));
int m1B1P2 = (((vid2 & 0x20) >> 5) | ((vid2 & 0x02)));
int m1B1P3 = (((vid2 & 0x10) >> 4) | ((vid2 & 0x01) << 1));
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P0]];
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P1]];
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P2]];
Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P3]];
break;
// 1 bit per pixel - 2 bytes - 16 pixels (32 CRT pixels)
case 2:
Characters[charIndex].Pixels = new int[16];
int m2Count = 0;
for (int bit = 7; bit >= 0; bit--)
{
int val = vid1.Bit(bit) ? 1 : 0;
Characters[charIndex].Pixels[m2Count++] = CRTDevice.CPCHardwarePalette[pens[val]];
}
for (int bit = 7; bit >= 0; bit--)
{
int val = vid2.Bit(bit) ? 1 : 0;
Characters[charIndex].Pixels[m2Count++] = CRTDevice.CPCHardwarePalette[pens[val]];
}
break;
// 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels)
case 3:
Characters[charIndex].Pixels = new int[4];
int m3Count = 0;
int m3B0P0i = vid1 & 170;
int m3B0P0 = ((m3B0P0i & 0x80) >> 7) | ((m3B0P0i & 0x08) >> 2) | ((m3B0P0i & 0x20) >> 3) | ((m3B0P0i & 0x02 << 2));
int m3B0P1i = vid1 & 85;
int m3B0P1 = ((m3B0P1i & 0x40) >> 6) | ((m3B0P1i & 0x04) >> 1) | ((m3B0P1i & 0x10) >> 2) | ((m3B0P1i & 0x01 << 3));
Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B0P0]];
Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B0P1]];
int m3B1P0i = vid1 & 170;
int m3B1P0 = ((m3B1P0i & 0x80) >> 7) | ((m3B1P0i & 0x08) >> 2) | ((m3B1P0i & 0x20) >> 3) | ((m3B1P0i & 0x02 << 2));
int m3B1P1i = vid1 & 85;
int m3B1P1 = ((m3B1P1i & 0x40) >> 6) | ((m3B1P1i & 0x04) >> 1) | ((m3B1P1i & 0x10) >> 2) | ((m3B1P1i & 0x01 << 3));
Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B1P0]];
Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B1P1]];
break;
}
}
/// <summary>
/// Returns the number of pixels decoded in this scanline (border and display)
/// </summary>
/// <returns></returns>
private int GetPixelCount()
{
int cnt = 0;
foreach (var c in Characters)
{
if (c.Pixels != null)
cnt += c.Pixels.Length;
}
return cnt;
}
/// <summary>
/// Called at the start of HSYNC
/// Processes and adds the scanline to the Screen Buffer
/// </summary>
public void CommitScanline()
{
int hPix = GetPixelCount() * 2;
int leftOver = CRT.BufferWidth - hPix;
int lPad = leftOver / 2;
int rPad = lPad;
int rem = leftOver % 2;
if (rem != 0)
rPad += rem;
if (LineIndex < CRT.TopLinesToTrim)
{
return;
}
// render out the scanline
int pCount = (LineIndex - CRT.TopLinesToTrim) * 2 * CRT.BufferWidth;
// double up
for (int s = 0; s < 2; s++)
{
// left padding
for (int lP = 0; lP < lPad; lP++)
{
CRT.ScreenBuffer[pCount++] = 0;
}
// border and display
foreach (var c in Characters)
{
if (c.Pixels == null || c.Pixels.Length == 0)
continue;
for (int p = 0; p < c.Pixels.Length; p++)
{
CRT.ScreenBuffer[pCount++] = c.Pixels[p];
CRT.ScreenBuffer[pCount++] = c.Pixels[p];
}
}
// right padding
for (int rP = 0; rP < rPad; rP++)
{
CRT.ScreenBuffer[pCount++] = 0;
}
if (pCount != hPix)
{
}
CRT.ScanlineCounter++;
}
}
public void Reset()
{
ScreenMode = 1;
Characters = new Character[64];
for (int i = 0; i < Characters.Length; i++)
{
Characters[i] = new Character();
}
}
}
/// <summary>
/// Contains data relating to one character written on one scanline
/// </summary>
public class Character
{
/// <summary>
/// Array of pixels generated for this character
/// </summary>
public int[] Pixels;
/// <summary>
/// The type (NONE/BORDER/DISPLAY/HSYNC/VSYNC/HSYNC+VSYNC
/// </summary>
public RenderPhase Phase = RenderPhase.NONE;
public Character()
{
Pixels = new int[0];
}
}
[Flags]
public enum RenderPhase : 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
}
}

View File

@ -213,7 +213,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return _keyboard.ReadCurrentLine();
}
return _registers[_activeRegister];
if (_activeRegister < 16)
return _registers[_activeRegister];
return 0;
}
/// <summary>

View File

@ -24,6 +24,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
FrameLength = 79872;
CRCT = new CRCT_6845(CRCT_6845.CRCTType.Motorola_MC6845, this);
CRT = new CRTDevice(this);
GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007);
PPI = new PPI_8255(this);

View File

@ -94,9 +94,35 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns>
public virtual byte FetchScreenMemory(ushort addr)
{
var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000));
//var value = ReadBus(addr);
return value;
int divisor = addr / 0x4000;
byte result = 0xff;
switch (divisor)
{
// 0x000
case 0:
result = RAM0[addr % 0x4000];
break;
// 0x4000
case 1:
result = RAM1[addr % 0x4000];
break;
// 0x8000
case 2:
result = RAM2[addr % 0x4000];
break;
// 0xc000 or UpperROM
case 3:
result = RAM3[addr % 0x4000];
break;
default:
break;
}
return result;
}
#endregion

View File

@ -62,6 +62,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public AmstradGateArray GateArray { get; set; }
/// <summary>
/// Renders pixels to the screen
/// </summary>
public CRTDevice CRT { get; set; }
/// <summary>
/// The PPI contoller chip
/// </summary>
@ -135,7 +140,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public virtual void ExecuteFrame(bool render, bool renderSound)
{
GateArray.FrameEnd = false;
//ULADevice.ULACycleCounter = CurrentFrameCycle;
InputRead = false;
_render = render;
@ -154,11 +158,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
PollInput();
GateArray.SetupVideo();
CRT.SetupVideo();
CRT.ScanlineCounter = 0;
while (!GateArray.FrameEnd)
{
GateArray.DoCycles();
GateArray.ClockCycle();
// cycle the tape device
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
TapeDevice.TapeCycle();
}
OverFlow = (int)CurrentFrameCycle - GateArray.FrameLength;