diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 9c699efcf6..6d2f2e25a4 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -686,8 +686,8 @@ namespace BizHawk.Client.Common nextComm, xmlGame.Assets.Select(a => a.Value), //.First(), cpcGI, // GameInfo.NullInstance, - (AmstradCPC.AmstradCPCSettings)GetCoreSettings(), - (AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings()); + (AmstradCPC.AmstradCPCSettings)GetCoreSettings(), + (AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings()); break; case "PSX": var entries = xmlGame.AssetFullPaths; diff --git a/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.Designer.cs index 38ecb803e8..359f6f2dba 100644 --- a/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.Designer.cs @@ -38,12 +38,15 @@ this.determEmucheckBox1 = new System.Windows.Forms.CheckBox(); this.lblAutoLoadText = new System.Windows.Forms.Label(); this.autoLoadcheckBox1 = new System.Windows.Forms.CheckBox(); + this.lblBorderInfo = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.borderTypecomboBox1 = new System.Windows.Forms.ComboBox(); this.SuspendLayout(); // // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(247, 375); + this.OkBtn.Location = new System.Drawing.Point(249, 432); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(60, 23); this.OkBtn.TabIndex = 3; @@ -55,7 +58,7 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(313, 375); + this.CancelBtn.Location = new System.Drawing.Point(315, 432); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(60, 23); this.CancelBtn.TabIndex = 4; @@ -80,7 +83,7 @@ this.MachineSelectionComboBox.FormattingEnabled = true; this.MachineSelectionComboBox.Location = new System.Drawing.Point(12, 62); this.MachineSelectionComboBox.Name = "MachineSelectionComboBox"; - this.MachineSelectionComboBox.Size = new System.Drawing.Size(361, 21); + this.MachineSelectionComboBox.Size = new System.Drawing.Size(363, 21); this.MachineSelectionComboBox.TabIndex = 13; this.MachineSelectionComboBox.SelectionChangeCommitted += new System.EventHandler(this.MachineSelectionComboBox_SelectionChangeCommitted); // @@ -105,7 +108,7 @@ // determEmucheckBox1 // this.determEmucheckBox1.AutoSize = true; - this.determEmucheckBox1.Location = new System.Drawing.Point(15, 302); + this.determEmucheckBox1.Location = new System.Drawing.Point(12, 373); this.determEmucheckBox1.Name = "determEmucheckBox1"; this.determEmucheckBox1.Size = new System.Drawing.Size(135, 17); this.determEmucheckBox1.TabIndex = 21; @@ -115,7 +118,7 @@ // lblAutoLoadText // this.lblAutoLoadText.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblAutoLoadText.Location = new System.Drawing.Point(175, 319); + this.lblAutoLoadText.Location = new System.Drawing.Point(172, 390); this.lblAutoLoadText.Name = "lblAutoLoadText"; this.lblAutoLoadText.Size = new System.Drawing.Size(196, 30); this.lblAutoLoadText.TabIndex = 27; @@ -126,20 +129,53 @@ // autoLoadcheckBox1 // this.autoLoadcheckBox1.AutoSize = true; - this.autoLoadcheckBox1.Location = new System.Drawing.Point(15, 325); + this.autoLoadcheckBox1.Location = new System.Drawing.Point(12, 396); this.autoLoadcheckBox1.Name = "autoLoadcheckBox1"; this.autoLoadcheckBox1.Size = new System.Drawing.Size(128, 17); this.autoLoadcheckBox1.TabIndex = 26; this.autoLoadcheckBox1.Text = "Auto Tape Start/Stop"; this.autoLoadcheckBox1.UseVisualStyleBackColor = true; // + // lblBorderInfo + // + this.lblBorderInfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblBorderInfo.Location = new System.Drawing.Point(175, 331); + this.lblBorderInfo.Name = "lblBorderInfo"; + this.lblBorderInfo.Size = new System.Drawing.Size(196, 21); + this.lblBorderInfo.TabIndex = 30; + this.lblBorderInfo.Text = "null"; + this.lblBorderInfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 315); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(118, 13); + this.label2.TabIndex = 29; + this.label2.Text = "Rendered Border Type:"; + // + // borderTypecomboBox1 + // + this.borderTypecomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.borderTypecomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.borderTypecomboBox1.FormattingEnabled = true; + this.borderTypecomboBox1.Location = new System.Drawing.Point(12, 331); + this.borderTypecomboBox1.Name = "borderTypecomboBox1"; + this.borderTypecomboBox1.Size = new System.Drawing.Size(159, 21); + this.borderTypecomboBox1.TabIndex = 28; + // // AmstradCPCCoreEmulationSettings // this.AcceptButton = this.OkBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(385, 410); + this.ClientSize = new System.Drawing.Size(387, 467); + this.Controls.Add(this.lblBorderInfo); + this.Controls.Add(this.label2); + this.Controls.Add(this.borderTypecomboBox1); this.Controls.Add(this.lblAutoLoadText); this.Controls.Add(this.autoLoadcheckBox1); this.Controls.Add(this.determEmucheckBox1); @@ -170,5 +206,8 @@ private System.Windows.Forms.CheckBox determEmucheckBox1; private System.Windows.Forms.Label lblAutoLoadText; private System.Windows.Forms.CheckBox autoLoadcheckBox1; + private System.Windows.Forms.Label lblBorderInfo; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox borderTypecomboBox1; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs b/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs index 69e70cc4ed..c01722fc5d 100644 --- a/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs +++ b/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs @@ -31,6 +31,15 @@ namespace BizHawk.Client.EmuHawk MachineSelectionComboBox.SelectedItem = _syncSettings.MachineType.ToString(); UpdateMachineNotes((MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString())); + // border selecton + var borderTypes = Enum.GetNames(typeof(AmstradCPC.BorderType)); + foreach (var val in borderTypes) + { + borderTypecomboBox1.Items.Add(val); + } + borderTypecomboBox1.SelectedItem = _syncSettings.BorderType.ToString(); + UpdateBorderNotes((AmstradCPC.BorderType)Enum.Parse(typeof(AmstradCPC.BorderType), borderTypecomboBox1.SelectedItem.ToString())); + // deterministic emulation determEmucheckBox1.Checked = _syncSettings.DeterministicEmulation; @@ -42,12 +51,14 @@ namespace BizHawk.Client.EmuHawk { bool changed = _syncSettings.MachineType.ToString() != MachineSelectionComboBox.SelectedItem.ToString() + || _syncSettings.BorderType.ToString() != borderTypecomboBox1.SelectedItem.ToString() || _syncSettings.DeterministicEmulation != determEmucheckBox1.Checked || _syncSettings.AutoStartStopTape != autoLoadcheckBox1.Checked; if (changed) { _syncSettings.MachineType = (MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString()); + _syncSettings.BorderType = (AmstradCPC.BorderType)Enum.Parse(typeof(AmstradCPC.BorderType), borderTypecomboBox1.SelectedItem.ToString()); _syncSettings.DeterministicEmulation = determEmucheckBox1.Checked; _syncSettings.AutoStartStopTape = autoLoadcheckBox1.Checked; @@ -80,5 +91,28 @@ namespace BizHawk.Client.EmuHawk { lblMachineNotes.Text = CPCMachineMetaData.GetMetaString(type); } + + private void borderTypecomboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + ComboBox cb = sender as ComboBox; + UpdateBorderNotes((AmstradCPC.BorderType)Enum.Parse(typeof(AmstradCPC.BorderType), cb.SelectedItem.ToString())); + } + + private void UpdateBorderNotes(AmstradCPC.BorderType type) + { + switch (type) + { + case AmstradCPC.BorderType.Uniform: + lblBorderInfo.Text = "Attempts to equalise the border areas"; + break; + case AmstradCPC.BorderType.Uncropped: + lblBorderInfo.Text = "Pretty much the signal the gate array is generating (looks pants)"; + break; + + case AmstradCPC.BorderType.Widescreen: + lblBorderInfo.Text = "Top and bottom border removed so that the result is *almost* 16:9"; + break; + } + } } } diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 2c4ae458c5..0247b95a49 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -140,6 +140,7 @@ + @@ -171,6 +172,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs index 14da6deb33..eda36aace2 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs @@ -101,6 +101,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC [DefaultValue(true)] public bool AutoStartStopTape { get; set; } + [DisplayName("Border type")] + [Description("Select how to show the border area")] + [DefaultValue(BorderType.Uniform)] + public BorderType BorderType { get; set; } + public AmstradCPCSyncSettings Clone() { return (AmstradCPCSyncSettings)MemberwiseClone(); @@ -299,5 +304,26 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } - } + + /// + /// The size of the Spectrum border + /// + public enum BorderType + { + /// + /// Attempts to equalise the border areas + /// + Uniform, + + /// + /// Pretty much the signal the gate array is generating (looks shit) + /// + Uncropped, + + /// + /// Top and bottom border removed so that the result is *almost* 16:9 + /// + Widescreen, + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs index bb9b3aad62..a11785c72d 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs @@ -46,11 +46,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { case MachineType.CPC464: ControllerDefinition = AmstradCPCControllerDefinition; - Init(MachineType.CPC464, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape); + Init(MachineType.CPC464, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape, + ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).BorderType); break; case MachineType.CPC6128: ControllerDefinition = AmstradCPCControllerDefinition; - Init(MachineType.CPC6128, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape); + Init(MachineType.CPC6128, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).BorderType); break; default: throw new InvalidOperationException("Machine not yet emulated"); @@ -72,7 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC ser.Register(_tracer); ser.Register(_cpu); - ser.Register(_machine.CRT); + ser.Register(_machine.GateArray); // initialize sound mixer and attach the various ISoundProvider devices SoundMixer = new SoundProviderMixer((int)(32767 / 10), "Tape Audio", (ISoundProvider)_machine.TapeBuzzer); @@ -158,7 +159,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC private MachineType _machineType; - private void Init(MachineType machineType, List files, bool autoTape) + private void Init(MachineType machineType, List files, bool autoTape, BorderType bType) { _machineType = machineType; @@ -166,7 +167,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC switch (machineType) { case MachineType.CPC464: - _machine = new CPC464(this, _cpu, files, autoTape); + _machine = new CPC464(this, _cpu, files, autoTape, bType); List roms64 = new List(); roms64.Add(RomData.InitROM(MachineType.CPC464, GetFirmware(0x4000, "OS464ROM"), RomData.ROMChipType.Lower)); roms64.Add(RomData.InitROM(MachineType.CPC464, GetFirmware(0x4000, "BASIC1-0ROM"), RomData.ROMChipType.Upper, 0)); @@ -174,7 +175,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC break; case MachineType.CPC6128: - _machine = new CPC6128(this, _cpu, files, autoTape); + _machine = new CPC6128(this, _cpu, files, autoTape, bType); List roms128 = new List(); roms128.Add(RomData.InitROM(MachineType.CPC6128, GetFirmware(0x4000, "OS6128ROM"), RomData.ROMChipType.Lower)); roms128.Add(RomData.InitROM(MachineType.CPC6128, GetFirmware(0x4000, "BASIC1-1ROM"), RomData.ROMChipType.Upper, 0)); diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs index 5658011667..659ca850c5 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs @@ -16,14 +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 /// - public class AmstradGateArray : IPortIODevice + 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 CRTDevice CRT => _machine.CRT; private IPSG PSG => _machine.AYDevice; private NECUPD765 FDC => _machine.UPDDiskDevice; private DatacorderDevice DATACORDER => _machine.TapeDevice; @@ -34,6 +34,85 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC #endregion + #region Palettes + + /// + /// The standard CPC Pallete (ordered by firmware #) + /// http://www.cpcwiki.eu/index.php/CPC_Palette + /// + 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 + }; + + /// + /// The standard CPC Pallete (ordered by hardware #) + /// http://www.cpcwiki.eu/index.php/CPC_Palette + /// + 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 /// @@ -68,8 +147,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC #endregion - - #region Construction public AmstradGateArray(CPCBase machine, GateArrayType chipType) @@ -77,8 +154,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC _machine = machine; ChipType = chipType; //PenColours = new int[17]; - CRT.SetupScreenSize(); + borderType = _machine.CPC.SyncSettings.BorderType; + SetupScreenSize(); //Reset(); + + CRCT.AttachHSYNCCallback(OnHSYNC); + CRCT.AttachVSYNCCallback(OnVSYNC); + + CurrentLine = new CharacterLine(); + InitByteLookup(); + CalculateNextScreenMemory(); } #endregion @@ -220,14 +305,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// The Video RAM is always located in the first 64K, VRAM is in no way affected by this register /// private byte _RAMR; + /// + /// This is actually implemented outside of here. These values do nothing. + /// public byte RAMR { get { return _RAMR; } set { _RAMR = value; - - // still todo } } @@ -246,6 +332,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC set { _interruptCounter = value; } } + /// + /// Interrupts are delayed when a VSYNC occurs + /// + private int VSYNCDelay; + /// /// Signals that the frame end has been reached /// @@ -289,7 +380,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// 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 /// - private bool HSYNC_falling; + //private bool HSYNC_falling; /// /// Used to count HSYNCs during a VSYNC @@ -343,7 +434,63 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC #endregion - #region Internal Methods + #region Clock Business + + /// + /// 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) + /// + 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(); + } /// /// Increments the internal clock counters by one @@ -359,7 +506,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC // check for frame end if (FrameClock == FrameLength) { - //FrameClock = 0; FrameEnd = true; } } @@ -408,6 +554,57 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } + #endregion + + #region Frame & Interrupt Handling + + /// + /// 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. + /// + 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; + } + } + /// /// Handles interrupt generation /// @@ -469,235 +666,272 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } - /// - /// 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. - /// - private void FrameDetector() - { - if (CRCT.HSYNC && !IsNewLine) - { - // start of a new line on the next render cycle - IsNewLine = true; + #endregion - // 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; - } - } + #region Rendering Business /// - /// Fetches a video RAM byte - /// This happens at 2Mhz when a memory fetch is due + /// 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 /// - /// - private void FetchByte(int index) + private void GACharacterCycle() { - switch (index) - { - case 1: - VideoByte1 = _machine.FetchScreenMemory(CRCT.CurrentByteAddress); - break; - case 2: - VideoByte2 = _machine.FetchScreenMemory((ushort)(CRCT.CurrentByteAddress + 1)); - break; - } - } - - /// - /// 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 - /// - 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/ - All 3 video modes are 200 lines in height. - Each line is 80 video bytes in size, representing 160, 320 or 640 pixels in width depending on the mode - - Video memory organisation: - - LINE R0W0 R0W1 R0W2 R0W3 R0W4 R0W5 R0W6 R0W7 - 1 C000 C800 D000 D800 E000 E800 F000 F800 - 2 C050 C850 D050 D850 E050 E850 F050 F850 - 3 C0A0 C8A0 D0A0 D8A0 E0A0 E8A0 F0A0 F8A0 - 4 C0F0 C8F0 D0F0 D8F0 E0F0 E8F0 F0F0 F8F0 - 5 C140 C940 D140 D940 E140 E940 F140 F940 - 6 C190 C990 D190 D990 E190 E990 F190 F990 - 7 C1E0 C9E0 D1E0 D9E0 E1E0 E9E0 F1E0 F9E0 - 8 C230 CA30 D230 DA30 E230 EA30 F230 FA30 - 9 C280 CA80 D280 DA80 E280 EA80 F280 FA80 - 10 C2D0 CAD0 D2D0 DAD0 E2D0 EAD0 F2D0 FAD0 - 11 C320 CB20 D320 DB20 E320 EB20 F320 FB20 - 12 C370 CB70 D370 DB70 E370 EB70 F370 FB70 - 13 C3C0 CBC0 D3C0 DBC0 E3C0 EBC0 F3C0 FBC0 - 14 C410 CC10 D410 DC10 E410 EC10 F410 FC10 - 15 C460 CC60 D460 DC60 E460 EC60 F460 FC60 - 16 C4B0 CCB0 D4B0 DCB0 E4B0 ECB0 F4B0 FCB0 - 17 C500 CD00 D500 DD00 E500 ED00 F500 FD00 - 18 C550 CD50 D550 DD50 E550 ED50 F550 FD50 - 19 C5A0 CDA0 D5A0 DDA0 E5A0 EDA0 F5A0 FDA0 - 20 C5F0 CDF0 D5F0 DDF0 E5F0 ED50 F550 FD50 - 21 C640 CE40 D640 DE40 E640 EE40 F640 FE40 - 22 C690 CE90 D690 DE90 E690 EE90 F690 FE90 - 23 C6E0 CEE0 D6E0 DEE0 E6E0 EEE0 F6E0 FEE0 - 24 C730 CF30 D730 DF30 E730 EF30 F730 FF30 - 25 C780 CF80 D780 DF80 E780 EF80 F780 FF80 - spare start C7D0 CFD0 D7D0 DFD0 E7D0 EFD0 F7D0 FFD0 - spare end C7FF CFFF D7FF DFFF E7FF EFFF F7FF FFFF - - Each byte represents 2, 4 or 8 pixels of the display depending on the mode. - - Mode 2, 640×200, 2 colors (each byte of video memory represents 8 pixels): - -------------------------------------------------------------------------- - bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 - pixel 0 pixel 1 pixel 2 pixel 3 pixel 4 pixel 5 pixel 6 pixel 7 - - Mode 1, 320×200, 4 colors (each byte of video memory represents 4 pixels): - -------------------------------------------------------------------------- - bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 - pixel 0 (bit 1) pixel 1 (bit 1) pixel 2 (bit 1) pixel 3 (bit 1) pixel 0 (bit 0) pixel 1(bit 0) pixel 2 (bit 0) pixel 3 (bit 0) - - Mode 0, 160×200, 16 colors (each byte of video memory represents 2 pixels): - --------------------------------------------------------------------------- - bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 - pixel 0 (bit 0) pixel 1 (bit 0) pixel 0 (bit 2) pixel 1 (bit 2) pixel 0 (bit 1) pixel 1 (bit 1) pixel 0 (bit 3) pixel 1 (bit 3) - - - Screen layout and generation: http://www.cpcwiki.eu/forum/programming/rupture/?action=dlattach;attach=16221 - */ - if (CRCT.VSYNC && CRCT.HSYNC) { // both hsync and vsync active - CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.HSYNCandVSYNC, VideoByte1, VideoByte2, ColourRegisters.ToArray()); - //CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.HSYNCandVSYNC, VideoByte1, VideoByte2, ColourRegisters); + CurrentLine.AddCharacter(Phase.HSYNCandVSYNC); } else if (CRCT.VSYNC) { // vsync is active but hsync is not - CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.VSYNC, VideoByte1, VideoByte2, ColourRegisters.ToArray()); - //CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.VSYNC, VideoByte1, VideoByte2, ColourRegisters); + CurrentLine.AddCharacter(Phase.VSYNC); } else if (CRCT.HSYNC) { // hsync is active but vsync is not - CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.HSYNC, VideoByte1, VideoByte2, ColourRegisters.ToArray()); - //CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.HSYNC, VideoByte1, VideoByte2, ColourRegisters); + CurrentLine.AddCharacter(Phase.HSYNC); } else if (!CRCT.DISPTMG) { // border generation - CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.BORDER, VideoByte1, VideoByte2, ColourRegisters.ToArray()); - //CRT.AddScanlineCharacter(VLC, HCC++, RenderPhase.BORDER, VideoByte1, VideoByte2, ColourRegisters); + CurrentLine.AddCharacter(Phase.BORDER); } else if (CRCT.DISPTMG) { // pixels generated from video RAM - CRT.CurrentLine.AddScanlineCharacter(HCC++, RenderPhase.DISPLAY, VideoByte1, VideoByte2, ColourRegisters.ToArray()); + CurrentLine.AddCharacter(Phase.DISPLAY); } } - #endregion - - #region Public Methods - /// - /// 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) + /// Holds the upcoming video RAM addresses for the next scanline + /// Firmware default size is 80 (40 characters - 2 bytes per character) /// - public void ClockCycle() + private ushort[] NextVidRamLine = new ushort[40 * 2]; + + /// + /// The current character line we are working from + /// + private CharacterLine CurrentLine; + + /// + /// List of screen lines as they are built up + /// + private List ScreenLines = new List(); + + /// + /// Pixel value lookups for every scanline byte value + /// Based on the lookup at https://github.com/gavinpugh/xnacpc + /// + private int[][] ByteLookup = new int[4][]; + private void InitByteLookup() { - // gatearray uses 4-phase clock to supply clocks to other devices - switch (ClockCounter) + int pix; + for (int m = 0; m < 4; m++) { - case 0: - CRCT.ClockCycle(); - //PSG.ClockCycle(FrameClock); - 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; - //if (CRCT.DISPTMG) - FetchByte(1); - break; - case 3: - // video fetch and render - WaitLine = true; - //if (CRCT.DISPTMG) - FetchByte(2); - PixelGenerator(); - break; + 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; + } + } } - - if (!HSYNC && CRCT.HSYNC) - { - HSYNC = true; - } - - // run the interrupt generator routine - InterruptGenerator(); - - if (!CRCT.HSYNC) - { - HSYNC = false; - } - - // conditional CPU cycle - DoConditionalCPUCycle(); - - AdvanceClock(); } + /// + /// Runs at HSYNC *AFTER* the scanline has been commmitted + /// Sets up the upcoming memory addresses for the next scanline + /// + 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; + } + } + + /// + /// Called at the start of HSYNC, this renders the currently built-up scanline + /// + 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 + /// /// Called when the Z80 acknowledges an interrupt /// @@ -707,6 +941,254 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC InterruptCounter &= ~(1 << 5); } + private int slCounter = 0; + private int slBackup = 0; + + /// + /// Fired when the CRCT flags HSYNC + /// + 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); + } + + /// + /// Fired when the CRCT flags VSYNC + /// + 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).ToList(); + var height = lines.Count(); + + 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 @@ -774,8 +1256,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC #endregion - - #region Serialization public void SyncState(Serializer ser) @@ -793,9 +1273,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC ser.Sync("FrameEnd", ref FrameEnd); ser.Sync("WaitLine", ref WaitLine); ser.Sync("_interruptCounter", ref _interruptCounter); + ser.Sync("VSYNCDelay", ref VSYNCDelay); ser.Sync("ScreenMode", ref ScreenMode); ser.Sync("HSYNC", ref HSYNC); - ser.Sync("HSYNC_falling", ref HSYNC_falling); + //ser.Sync("HSYNC_falling", ref HSYNC_falling); ser.Sync("HSYNC_counter", ref HSYNC_counter); ser.Sync("VSYNC", ref VSYNC); ser.Sync("InterruptRaised", ref InterruptRaised); @@ -807,12 +1288,83 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC ser.Sync("VLC", ref VLC); ser.Sync("VideoByte1", ref VideoByte1); ser.Sync("VideoByte2", ref VideoByte2); + ser.Sync("NextVidRamLine", ref NextVidRamLine, false); ser.EndSection(); } #endregion - #region Enums & Classes + #region Enums, Classes & Lookups + + /// + /// Represents a single scanline (in characters) + /// + public class CharacterLine + { + /// + /// Screenmode is defined at each HSYNC (start of a new character line) + /// Therefore we pass the mode in via constructor + /// + /// + //public CharacterLine(int screenMode) + //{ + //ScreenMode = screenMode; + //} + + public int ScreenMode = 1; + public List Phases = new List(); + public List Pixels = new List(); + + /// + /// Adds a new horizontal character to the list + /// + /// + 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 + { + /// + /// Nothing + /// + NONE = 0, + /// + /// Border is being rendered + /// + BORDER = 1, + /// + /// Display rendered from video RAM + /// + DISPLAY = 2, + /// + /// HSYNC in progress + /// + HSYNC = 3, + /// + /// VSYNC in process + /// + VSYNC = 4, + /// + /// HSYNC occurs within a VSYNC + /// + HSYNCandVSYNC = 5 + } public enum GateArrayType { diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCTChip.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCTChip.cs new file mode 100644 index 0000000000..2cda19e091 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCTChip.cs @@ -0,0 +1,832 @@ +using BizHawk.Common; +using BizHawk.Common.NumberExtensions; +using System.Collections; + +namespace BizHawk.Emulation.Cores.Computers.AmstradCPC +{ + /// + /// CRT CONTROLLER (CRTC) + /// http://archive.pcjs.org/pubs/pc/datasheets/MC6845-CRT.pdf + /// http://www.cpcwiki.eu/imgs/c/c0/Hd6845.hitachi.pdf + /// http://www.cpcwiki.eu/imgs/1/13/Um6845.umc.pdf + /// http://www.cpcwiki.eu/imgs/b/b5/Um6845r.umc.pdf + /// http://www.cpcwiki.eu/index.php/CRTC + /// + public class CRCTChip + { + #region Devices + + private CPCBase _machine { get; set; } + private CRCTType ChipType; + + #endregion + + #region Construction + + public CRCTChip(CRCTType chipType, CPCBase machine) + { + _machine = machine; + ChipType = chipType; + //Reset(); + } + + #endregion + + #region Output Lines + + // State output lines + /// + /// This TTL compatible output is an active high signal which drives the monitor directly or is fed to Video Processing Logic for composite generation. + /// This signal determines the vertical position of the displayed text. + /// + public bool VSYNC { get { return _VSYNC; } } + /// + /// This TTL compatible output is an active high signal which drives the monitor directly or is fed to Video Processing Logic for composite generation. + /// This signal determines the horizontal position of the displayed text. + /// + public bool HSYNC { get { return _HSYNC; } } + /// + /// This TTL compatible output is an active high signal which indicates the CRTC is providing addressing in the active Display Area. + /// + public bool DisplayEnable { get { return DisplayEnable; } } + /// + /// This TTL compatible output indicates Cursor Display to external Video Processing Logic.Active high signal. + /// + public bool Cursor { get { return _Cursor; } } + + private bool _VSYNC; + private bool _HSYNC; + private bool _DisplayEnable; + private bool _Cursor; + + // Refresh memory addresses + /* + Refresh Memory Addresses (MAO-MA13) -These 14 outputs are used to refresh the CRT screen with pages of + data located within a 16K block of refresh memory. These outputs drive a TTL load and 30pF. A high level on + MAO-MA 13 is a logical "1." + */ + public bool MA0 { get { return LinearAddress.Bit(0); } } + public bool MA1 { get { return LinearAddress.Bit(1); } } + public bool MA2 { get { return LinearAddress.Bit(2); } } + public bool MA3 { get { return LinearAddress.Bit(3); } } + public bool MA4 { get { return LinearAddress.Bit(4); } } + public bool MA5 { get { return LinearAddress.Bit(5); } } + public bool MA6 { get { return LinearAddress.Bit(6); } } + public bool MA7 { get { return LinearAddress.Bit(7); } } + public bool MA8 { get { return LinearAddress.Bit(8); } } + public bool MA9 { get { return LinearAddress.Bit(9); } } + public bool MA10 { get { return LinearAddress.Bit(10); } } // cpcwiki would suggest that this isnt connected in the CPC range + public bool MA11 { get { return LinearAddress.Bit(11); } } // cpcwiki would suggest that this isnt connected in the CPC range + public bool MA12 { get { return LinearAddress.Bit(12); } } // cpcwiki would suggest that this is connected in the CPC range but not used + public bool MA13 { get { return LinearAddress.Bit(13); } } // cpcwiki would suggest that this is connected in the CPC range but not used + + // Row addresses for character generators + /* + Raster Addresses (RAO-RA4) - These 5 outputs from the internal Raster Counter address the Character ROM + for the row of a character. These outputs drive a TTL load and 30pF. A high level (on RAO-RA4) is a logical "1." + */ + public bool RA0 { get { return ScanLineCTR.Bit(0); } } + public bool RA1 { get { return ScanLineCTR.Bit(1); } } + public bool RA2 { get { return ScanLineCTR.Bit(2); } } + public bool RA3 { get { return ScanLineCTR.Bit(3); } } // cpcwiki would suggest that this isnt connected in the CPC range + public bool RA4 { get { return ScanLineCTR.Bit(4); } } // cpcwiki would suggest that this isnt connected in the CPC range + + /// + /// Built from R12, R13 and CLK + /// This is a logical emulator output and is how the CPC gatearray would translate the lines + /* + Memory Address Signal Signal source Signal name + A15 6845 MA13 + A14 6845 MA12 + A13 6845 RA2 + A12 6845 RA1 + A11 6845 RA0 + A10 6845 MA9 + A9 6845 MA8 + A8 6845 MA7 + A7 6845 MA6 + A6 6845 MA5 + A5 6845 MA4 + A4 6845 MA3 + A3 6845 MA2 + A2 6845 MA1 + A1 6845 MA0 + A0 Gate-Array CLK + + */ + /// + public ushort AddressLine + { + get + { + BitArray MA = new BitArray(16); + MA[0] = _CLK; + MA[1] = MA0; + MA[2] = MA1; + MA[3] = MA2; + MA[4] = MA3; + MA[5] = MA4; + MA[6] = MA5; + MA[7] = MA6; + MA[8] = MA7; + MA[9] = MA8; + MA[10] = MA9; + MA[11] = RA0; + MA[12] = RA1; + MA[13] = RA2; + MA[14] = MA12; + MA[15] = MA13; + ushort[] array = new ushort[1]; + MA.CopyTo(array, 0); + return array[0]; + } + } + + #endregion + + #region Input Lines + + /// + /// This TTL compatible output indicates Cursor Display to external Video Processing Logic.Active high signal. + /// + public bool CLK { get { return _CLK; } } + /// + /// The RES input is used to Reset the CRTC. An input low level on RES forces CRTC into following status: + /// (A) All the counters in CRTC are cleared and the device stops the display operation. + /// (C) Control registers in CRTC are not affected and remain unchanged. + /// This signal is different from other M6800 family in the following functions: + /// (A) RES signal has capability of reset function only. when LPSTB is at low level. + /// (B) After RES has gone down to low level, output s ignals of MAO -MA13 and RAO - RA4, synchronizing with CLK low level, goes down to low level. + /// (At least 1 cycle CLK signal is necessary for reset.) + /// (C) The CRTC starts the Display operation immediately after the release of RES signal. + /// + public bool RESET { get { return _RESET; } } + /// + /// Light Pen Strobe (LPSTR) - This high impedance TTLIMOS compatible input latches the cu rrent Refresh Addresses in the Register File. + /// Latching is on the low to high edge and is synchronized internally to character clock. + /// + public bool LPSTB { get { return _LPSTB; } } + + private bool _CLK; + private bool _RESET; + private bool _LPSTB; + + #endregion + + #region Internal Registers + + /// + /// The currently selected register + /// + private byte AddressRegister; + + /// + /// The internal register + /// The Address Register is a 5 bit write-only register used as an "indirect" or "pointer" register. + /// Its contents are the address of one of the other 18 registers in the file.When RS and CS are low, + /// the Address Register itself is addressed.When RS is high, the Register File is accessed. + /// + private byte[] Register = new byte[18]; + + // Horizontal timing register constants + /// + /// This 8 bit write-only register determines the horizontal frequency of HS. + /// It is the total of displayed plus non-displayed character time units minus one. + /// + private const int H_TOTAL = 0; + /// + /// This 8 bit write-only register determines the number of displayed characters per horizontal line. + /// + private const int H_DISPLAYED = 1; + /// + /// This 8 bit write-only register determines the horizontal sync postiion on the horizontal line. + /// + private const int H_SYNC_POS = 2; + /// + /// This 4 bit write-only register determines the width of the HS pulse. It may not be apparent why this width needs to be programmed.However, + /// consider that all timing widths must be programmed as multiples of the character clock period which varies.If HS width were fixed as an integral + /// number of character times, it would vary with character rate and be out of tolerance for certain monitors. + /// The rate programmable feature allows compensating HS width. + /// NOTE: Dependent on chiptype this also may include VSYNC width - check the UpdateWidths() method + /// + private const int SYNC_WIDTHS = 3; + + // Vertical timing register constants + /// + /// The vertical frequency of VS is determined by both R4 and R5.The calculated number of character I ine times is usual I y an integer plus a fraction to + /// get exactly a 50 or 60Hz vertical refresh rate. The integer number of character line times minus one is programmed in the 7 bit write-only Vertical Total Register; + /// the fraction is programmed in the 5 bit write-only Vertical Scan Adjust Register as a number of scan line times. + /// + private const int V_TOTAL = 4; + private const int V_TOTAL_ADJUST = 5; + /// + /// This 7 bit write-only register determines the number of displayed character rows on the CRT screen, and is programmed in character row times. + /// + private const int V_DISPLAYED = 6; + /// + /// This 7 bit write-only register determines the vertical sync position with respect to the reference.It is programmed in character row times. + /// + private const int V_SYNC_POS = 7; + /// + /// This 2 bit write-only register controls the raster scan mode(see Figure 11 ). When bit 0 and bit 1 are reset, or bit 0 is reset and bit 1 set, + /// the non· interlace raster scan mode is selected.Two interlace modes are available.Both are interlaced 2 fields per frame.When bit 0 is set and bit 1 is reset, + /// the interlace sync raster scan mode is selected.Also when bit 0 and bit 1 are set, the interlace sync and video raster scan mode is selected. + /// + private const int INTERLACE_MODE = 8; + /// + /// This 5 bit write·only register determines the number of scan lines per character row including spacing. + /// The programmed value is a max address and is one less than the number of scan l1nes. + /// + private const int MAX_SL_ADDRESS = 9; + + // Other register constants + /// + /// This 7 bit write-only register controls the cursor format(see Figure 10). Bit 5 is the blink timing control.When bit 5 is low, the blink frequency is 1/16 of the + /// vertical field rate, and when bit 5 is high, the blink frequency is 1/32 of the vertical field rate.Bit 6 is used to enable a blink. + /// The cursor start scan line is set by the lower 5 bits. + /// + private const int CURSOR_START = 10; + /// + /// This 5 bit write-only register sets the cursor end scan line + /// + private const int CURSOR_END = 11; + /// + /// Start Address Register is a 14 bit write-only register which determines the first address put out as a refresh address after vertical blanking. + /// It consists of an 8 bit lower register, and a 6 bit higher register. + /// + private const int START_ADDR_H = 12; + private const int START_ADDR_L = 13; + /// + /// This 14 bit read/write register stores the cursor location.This register consists of an 8 bit lower and 6 bit higher register. + /// + private const int CURSOR_H = 14; + private const int CURSOR_L = 15; + /// + /// This 14 bit read -only register is used to store the contents of the Address Register(H & L) when the LPSTB input pulses high. + /// This register consists of an 8 bit lower and 6 bit higher register. + /// + private const int LIGHT_PEN_H = 16; + private const int LIGHT_PEN_L = 17; + + #endregion + + #region Internal Fields & Properties + + /// + /// Calculated when set based on R3 + /// + private int HSYNCWidth; + /// + /// Calculated when set based on R3 + /// + private int VSYNCWidth; + + /// + /// Character pos address (0 index). + /// Feeds the MA lines + /// + private int LinearAddress; + + /// + /// The currently selected Interlace Mode (based on R8) + /// + private InterlaceMode CurrentInterlaceMode + { + get + { + if (!Register[INTERLACE_MODE].Bit(0)) + { + return InterlaceMode.NormalSyncMode; + } + else if (Register[INTERLACE_MODE].Bit(0)) + { + if (Register[INTERLACE_MODE].Bit(1)) + { + return InterlaceMode.InterlaceSyncAndVideoMode; + } + else + { + return InterlaceMode.InterlaceSyncMode; + } + } + + return InterlaceMode.NormalSyncMode; + } + } + + /// + /// The current cursor display mode (based on R14 & R15) + /// + private CursorControl CurrentCursorMode + { + get + { + if (!Register[CURSOR_START].Bit(6) && !Register[CURSOR_START].Bit(5)) + { + return CursorControl.NonBlink; + } + else if (!Register[CURSOR_START].Bit(6) && Register[CURSOR_START].Bit(5)) + { + return CursorControl.CursorNonDisplay; + } + else if (Register[CURSOR_START].Bit(6) && !Register[CURSOR_START].Bit(5)) + { + return CursorControl.Blink1_16; + } + else + { + return CursorControl.Blink1_32; + } + } + } + + // Counter microchips + private int HorizontalCTR { get { return _HorizontalCTR; } + set + { + if (value > 255) + _HorizontalCTR = value - 255; + } + } + + private int HorizontalSyncWidthCTR + { + get { return _HorizontalSyncWidthCTR; } + set + { + if (value > 15) + _HorizontalSyncWidthCTR = value - 15; + } + } + + private int CharacterRowCTR + { + get { return CharacterRowCTR; } + set + { + if (value > 127) + _CharacterRowCTR = value - 127; + } + } + + private int ScanLineCTR + { + get { return ScanLineCTR; } + set + { + if (value > 31) + _ScanLineCTR = value - 31; + } + } + + private int _HorizontalCTR; + private int _HorizontalSyncWidthCTR; + private int _CharacterRowCTR; + private int _ScanLineCTR; + + #endregion + + #region Databus Interface + /* + RegIdx Register Name Type + 0 1 2 3 4 + 0 Horizontal Total Write Only Write Only Write Only (note 2) (note 3) + 1 Horizontal Displayed Write Only Write Only Write Only (note 2) (note 3) + 2 Horizontal Sync Position Write Only Write Only Write Only (note 2) (note 3) + 3 H and V Sync Widths Write Only Write Only Write Only (note 2) (note 3) + 4 Vertical Total Write Only Write Only Write Only (note 2) (note 3) + 5 Vertical Total Adjust Write Only Write Only Write Only (note 2) (note 3) + 6 Vertical Displayed Write Only Write Only Write Only (note 2) (note 3) + 7 Vertical Sync position Write Only Write Only Write Only (note 2) (note 3) + 8 Interlace and Skew Write Only Write Only Write Only (note 2) (note 3) + 9 Maximum Raster Address Write Only Write Only Write Only (note 2) (note 3) + 10 Cursor Start Raster Write Only Write Only Write Only (note 2) (note 3) + 11 Cursor End Raster Write Only Write Only Write Only (note 2) (note 3) + 12 Disp. Start Address (High) Read/Write Write Only Write Only Read/Write (note 2) (note 3) + 13 Disp. Start Address (Low) Read/Write Write Only Write Only Read/Write (note 2) (note 3) + 14 Cursor Address (High) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3) + 15 Cursor Address (Low) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3) + 16 Light Pen Address (High) Read Only Read Only Read Only Read Only (note 2) (note 3) + 17 Light Pen Address (Low) Read Only Read Only Read Only Read Only (note 2) (note 3) + + 1. On type 0 and 1, if a Write Only register is read from, "0" is returned. + 2. See the document "Extra CPC Plus Hardware Information" for more details. + 3. CRTC type 4 is the same as CRTC type 3. The registers also repeat as they do on the type 3. + */ + + /* CPC: + #BCXX %x0xxxx00 xxxxxxxx 6845 CRTC Index - Write + #BDXX %x0xxxx01 xxxxxxxx 6845 CRTC Data Out - Write + #BEXX %x0xxxx10 xxxxxxxx 6845 CRTC Status (as far as supported) Read - + #BFXX %x0xxxx11 xxxxxxxx 6845 CRTC Data In (as far as supported) Read - + */ + + /// + /// CPU (or other device) reads from the 8-bit databus + /// + /// + /// + public bool ReadPort(ushort port, ref int result) + { + byte portUpper = (byte)(port >> 8); + byte portLower = (byte)(port & 0xff); + + bool accessed = false; + + // The 6845 is selected when bit 14 of the I/O port address is set to "0" + if (portUpper.Bit(6)) + return accessed; + + // Bit 9 and 8 of the I/O port address define the function to access + if (portUpper.Bit(1) && !portUpper.Bit(0)) + { + // read status register + accessed = ReadStatus(ref result); + } + else if ((portUpper & 3) == 3) + { + // read data register + accessed = ReadRegister(ref result); + } + else + { + result = 0; + } + + return accessed; + } + + /// + /// CPU (or other device) writes to the 8-bit databus + /// + /// + /// + public bool WritePort(ushort port, int value) + { + byte portUpper = (byte)(port >> 8); + byte portLower = (byte)(port & 0xff); + + bool accessed = false; + + // The 6845 is selected when bit 14 of the I/O port address is set to "0" + if (portUpper.Bit(6)) + return accessed; + + var func = portUpper & 3; + + switch (func) + { + // reg select + case 0: + SelectRegister(value); + break; + + // data write + case 1: + WriteRegister(value); + break; + } + + return accessed; + } + + #endregion + + #region Internal IO Methods + + /// + /// Selects a specific register + /// + /// + private void SelectRegister(int value) + { + var v = (byte)((byte)value & 0x1F); + if (v > 0 && v < 18) + { + AddressRegister = v; + } + } + + /// + /// Writes to the currently latched address register + /// + /// + private void WriteRegister(int value) + { + byte val = (byte)value; + + // lightpen regs are readonly on all models + if (AddressRegister == 16 || AddressRegister == 17) + return; + + // all other models can be written to + switch (AddressRegister) + { + case H_TOTAL: + case H_DISPLAYED: + case H_SYNC_POS: + case START_ADDR_L: + Register[AddressRegister] = val; + break; + + case SYNC_WIDTHS: + Register[AddressRegister] = val; + UpdateWidths(); + break; + + case V_TOTAL_ADJUST: + case CURSOR_END: + case MAX_SL_ADDRESS: + Register[AddressRegister] = (byte)(val & 0x1F); + break; + + case START_ADDR_H: + case CURSOR_H: + Register[AddressRegister] = (byte)(val & 0x3F); + break; + + case V_TOTAL: + case V_DISPLAYED: + case V_SYNC_POS: + case CURSOR_START: + Register[AddressRegister] = (byte)(val & 0x7F); + break; + + case INTERLACE_MODE: + Register[AddressRegister] = (byte)(val & 0x3); + break; + } + } + + /// + /// Reads from the currently selected register + /// + /// + private bool ReadRegister(ref int data) + { + bool addressed = false; + switch (AddressRegister) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + if ((int)ChipType == 0 || (int)ChipType == 1) + { + addressed = true; + data = 0; + } + break; + case 12: + case 13: + addressed = true; + if ((int)ChipType == 0) + data = Register[AddressRegister]; + else if ((int)ChipType == 1) + data = 0; + break; + case 14: + case 15: + case 16: + case 17: + addressed = true; + data = Register[AddressRegister]; + break; + + default: + // registers 18-31 read as 0, on type 0 and 2. registers 18-30 read as 0 on type1, register 31 reads as 0x0ff. + if (AddressRegister >= 18 && AddressRegister <= 30) + { + switch ((int)ChipType) + { + case 0: + case 2: + case 1: + addressed = true; + data = 0; + break; + } + } + else if (AddressRegister == 31) + { + if ((int)ChipType == 1) + { + addressed = true; + data = 0x0ff; + } + else if ((int)ChipType == 0 || (int)ChipType == 2) + { + addressed = true; + data = 0; + } + } + break; + } + + return addressed; + } + + /// + /// Updates the V and H SYNC widths + /// + private void UpdateWidths() + { + switch (ChipType) + { + case CRCTType.HD6845S: + // Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC. Bits 3..0 define Horizontal Sync Width. + // If 0 is programmed no HSYNC is generated. + HSYNCWidth = (Register[SYNC_WIDTHS] >> 0) & 0x0F; + VSYNCWidth = (Register[SYNC_WIDTHS] >> 4) & 0x0F; + break; + case CRCTType.UM6845R: + // Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed no HSYNC is generated. + HSYNCWidth = (Register[SYNC_WIDTHS] >> 0) & 0x0F; + VSYNCWidth = 16; + break; + case CRCTType.MC6845: + // Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed this gives a HSYNC width of 16. + HSYNCWidth = (Register[SYNC_WIDTHS] >> 0) & 0x0F; + if (HSYNCWidth == 0) + HSYNCWidth = 16; + VSYNCWidth = 16; + break; + case CRCTType.AMS40489: + case CRCTType.AMS40226: + // Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC.Bits 3..0 define Horizontal Sync Width. + // If 0 is programmed this gives a HSYNC width of 16. + HSYNCWidth = (Register[SYNC_WIDTHS] >> 0) & 0x0F; + VSYNCWidth = (Register[SYNC_WIDTHS] >> 4) & 0x0F; + if (HSYNCWidth == 0) + HSYNCWidth = 16; + if (VSYNCWidth == 0) + VSYNCWidth = 16; + break; + } + } + + + + /// + /// Reads from the status register + /// + /// + /// + private bool ReadStatus(ref int data) + { + bool addressed = false; + switch ((int)ChipType) + { + case 1: + // read status + //todo!! + addressed = true; + break; + case 0: + case 2: + // status reg not available + break; + case 3: + case 4: + // read from internal register instead + addressed = ReadRegister(ref data); + break; + } + return addressed; + } + + #endregion + + #region Public Functions + + /// + /// Performs a CRCT clock cycle. + /// On CPC this is called at 1Mhz == 1 Character cycle (2 bytes) + /// + public void ClockCycle() + { + // H clock + HorizontalCTR++; + + if (HorizontalCTR == Register[H_TOTAL]) + { + // end of current scanline + HorizontalCTR = 0; + // CRCT starts its scalines at the display area + _DisplayEnable = true; + + ScanLineCTR++; + + if (ScanLineCTR > Register[MAX_SL_ADDRESS]) + { + // end of vertical character + ScanLineCTR = 0; + CharacterRowCTR++; + + if (CharacterRowCTR == Register[V_TOTAL]) + { + // check for vertical adjust + if (Register[V_TOTAL_ADJUST] > 0) + { + + } + else + { + // end of CRCT frame + CharacterRowCTR = 0; + } + } + } + } + else if (HorizontalCTR == Register[H_DISPLAYED] + 1) + { + // end of display area + _DisplayEnable = false; + } + } + + #endregion + + #region Internal Functions + + + + #endregion + + #region Serialization + + public void SyncState(Serializer ser) + { + ser.BeginSection("CRCT"); + ser.SyncEnum("ChipType", ref ChipType); + ser.Sync("_VSYNC", ref _VSYNC); + ser.Sync("_HSYNC", ref _HSYNC); + ser.Sync("_DisplayEnable", ref _DisplayEnable); + ser.Sync("_Cursor", ref _Cursor); + ser.Sync("_CLK", ref _CLK); + ser.Sync("_RESET", ref _RESET); + ser.Sync("_LPSTB", ref _LPSTB); + ser.Sync("AddressRegister", ref AddressRegister); + ser.Sync("Register", ref Register, false); + ser.Sync("HSYNCWidth", ref HSYNCWidth); + ser.Sync("VSYNCWidth", ref VSYNCWidth); + ser.Sync("_HorizontalCTR", ref _HorizontalCTR); + ser.Sync("_HorizontalSyncWidthCTR", ref _HorizontalSyncWidthCTR); + ser.Sync("_CharacterRowCTR", ref _CharacterRowCTR); + ser.Sync("_ScanLineCTR", ref _ScanLineCTR); + ser.EndSection(); + + /* + + * */ + } + + #endregion + + #region Enums + + /// + /// The types of CRCT chip found in the CPC range + /// + public enum CRCTType + { + HD6845S = 0, + UM6845 = 0, + UM6845R = 1, + MC6845 = 2, + AMS40489 = 3, + AMS40226 = 4 + } + + /// + /// The available interlace modes in the CRCT + /// + private enum InterlaceMode + { + NormalSyncMode, + InterlaceSyncMode, + InterlaceSyncAndVideoMode + } + + /// + /// Cursor display modes + /// + private enum CursorControl + { + NonBlink, + CursorNonDisplay, + Blink1_16, + Blink1_32 + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs index fd7271f5bb..8176b7db14 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs @@ -19,16 +19,37 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC #endregion + #region CallBacks + + public delegate void CallBack(); + + private CallBack HSYNC_Callbacks; + private CallBack VSYNC_Callbacks; + + public void AttachVSYNCCallback(CallBack vCall) + { + VSYNC_Callbacks += vCall; + } + + public void AttachHSYNCCallback(CallBack hCall) + { + HSYNC_Callbacks += hCall; + } + + #endregion + #region Construction public CRCT_6845(CRCTType chipType, CPCBase machine) { _machine = machine; ChipType = chipType; - Reset(); } + private const int WRITE = 0; + private const int READ = 1; + #endregion #region Public Lines @@ -55,15 +76,25 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// public short MA; + /// + /// Vertical Character Count + /// + public int VCC; + + /// + /// Vertical Scanline Count (within the current vertical character) + /// + public int VLC; + #endregion - + #region Public Lookups /* * These are not accessible directlyon real hardware * It just makes screen generation easier to have these accessbile from the gate array */ - + /// /// The total frame width (in characters) /// @@ -86,6 +117,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } + /// + /// The total frame height (in scanlines) + /// + public int FrameHeightInChars + { + get + { + return ((int)Regs[VER_TOTAL] + 1); + } + } + /// /// The width of the display area (in characters) /// @@ -108,6 +150,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } + /// + /// The width of the display area (in scanlines) + /// + public int DisplayHeightInChars + { + get + { + return (int)Regs[VER_DISPLAYED]; + } + } + /// /// The character at which to start HSYNC /// @@ -153,7 +206,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } /// - /// The number of scanlines in one character + /// The number of scanlines in one character (MAXRASTER) /// public int ScanlinesPerCharacter { @@ -183,6 +236,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } + public int DStartHigh + { get { return Regs[DISP_START_ADDR_H]; } } + + public int DStartLow + { get { return Regs[DISP_START_ADDR_L]; } } + /// /// Returns the video buffer size as specified within R12 /// @@ -394,12 +453,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// /// CPC register default values /// Taken from https://web.archive.org/web/20170501112330/http://www.grimware.org/doku.php/documentations/devices/crtc + /// http://www.cantrell.org.uk/david/tech/cpc/cpc-firmware/firmware.pdf /// (The defaults values given here are those programmed by the firmware ROM after a cold/warm boot of the CPC/Plus) /// - private byte[] RegDefaults = new byte[] { 63, 40, 46, 0x8e, 38, 0, 25, 30, 0, 7, 0, 0, 0x20, 0x00, 0, 0, 0, 0 }; + private byte[] RegDefaults = new byte[] { 63, 40, 46, 112, 38, 0, 25, 30, 0, 7, 0, 0, 48, 0, 192, 7, 0, 0 }; /// /// Register masks + /// 0 = WRITE + /// 1 = READ /// private byte[] CPCMask = new byte[] { 255, 255, 255, 255, 127, 31, 127, 126, 3, 31, 31, 31, 63, 255, 63, 255, 63, 255 }; @@ -408,16 +470,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// private int HCC; - /// - /// Vertical Character Count - /// - public int VCC; - - /// - /// Vertical Scanline Count - /// - public int VLC; - /// /// Internal cycle counter /// @@ -452,11 +504,99 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC #region Public Methods + public void ClockCycle() + { + CheckHSYNCOff(); + + HCC++; + + if (HCC == Regs[HOR_TOTAL] + 1) + { + // end of scanline + HCC = 0; + + if (VSYNCCounter > 0) + { + VSYNCCounter--; + if (VSYNCCounter == 0) + { + VSYNC = false; + } + } + + VLC++; + + if (VLC == Regs[MAX_RASTER_ADDR] + 1) + { + // end of rasterline + VLC = 0; + VCC++; + + if (VCC == Regs[VER_TOTAL] + 1) + { + // end of screen + VCC = 0; + } + + if (VCC == Regs[VER_SYNC_POS] && !VSYNC) + { + VSYNC = true; + VSYNCCounter = VSYNCWidth; + VSYNC_Callbacks(); + } + } + } + else + { + // still on the current scanline + if (HCC == Regs[HOR_SYNC_POS] && !HSYNC) + { + HSYNC = true; + HSYNCCounter = HSYNCWidth; + HSYNC_Callbacks(); + ByteCounter = 0; + } + + if (HCC >= Regs[HOR_DISPLAYED] + 1 || VCC >= Regs[VER_DISPLAYED]) + { + DISPTMG = false; + } + else + { + DISPTMG = true; + + var line = VCC; + var row = VLC; + var addrX = (LatchedRAMOffset * 2) + ((VCC * LatchedScreenWidthBytes) & 0x7ff) + ByteCounter; + // remove artifacts caused by certain hardware scrolling addresses + addrX &= 0x7ff; + var addrY = LatchedRAMStartAddress + (2048 * VLC); + + //var addr = VideoPageBase + (line * (0x50)) + (row * 0x800) + (ByteCounter); + CurrentByteAddress = (ushort)(addrX + addrY); + + ByteCounter += 2; + } + } + } + + private void CheckHSYNCOff() + { + if (HSYNCCounter > 0) + { + HSYNCCounter--; + if (HSYNCCounter == 0) + { + HSYNC = false; + } + } + } + /// /// Runs a CRCT clock cycle /// This should be called at 1Mhz / 1us / every 4 uncontended CPU t-states /// - public void ClockCycle() + public void ClockCycle2() { if (HSYNC) { @@ -475,7 +615,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC if (HSYNC && HSYNCCounter == 1) { - + } // move one horizontal character @@ -493,7 +633,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC else { DISPTMG = true; - + var line = VCC; var row = VLC; var addrX = (LatchedRAMOffset * 2) + ((VCC * LatchedScreenWidthBytes) & 0x7ff) + ByteCounter; @@ -503,8 +643,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC //var addr = VideoPageBase + (line * (0x50)) + (row * 0x800) + (ByteCounter); CurrentByteAddress = (ushort)(addrX + addrY); - - ByteCounter += 2; } @@ -515,7 +653,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC // end of the current scanline HCC = 0; - if (ChipType == CRCTType.UMC_UM6845R && VLC <= Regs[MAX_RASTER_ADDR]) + + if (ChipType == (CRCTType)1 && VLC <= Regs[MAX_RASTER_ADDR]) { // https://web.archive.org/web/20170501112330/http://www.grimware.org/doku.php/documentations/devices/crtc // The MA is reloaded with the value from R12 and R13 when VCC=0 and VLC=0 (that's when a new CRTC screen begin). @@ -548,7 +687,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC // still doing extra scanlines } else - { // finished doing extra scanlines EndOfScreen = false; @@ -585,8 +723,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { VSYNC = true; VSYNCCounter = 0; + VSYNC_Callbacks(); } - } + } } else { @@ -598,6 +737,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { HSYNC = true; HSYNCCounter = 0; + HSYNC_Callbacks(); + lineCounter++; LatchedRAMStartAddress = VideoPageBase; LatchedRAMOffset = VideoRAMOffset; @@ -608,6 +749,95 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } } + /// + /// Runs a CRCT clock cycle + /// This should be called at 1Mhz / 1us / every 4 uncontended CPU t-states + /// + public void ClockCycle1() + { + // HSYNC processing + if (HSYNCCounter > 0) + { + HSYNCCounter--; + if (HSYNCCounter == 0) + HSYNC = false; + } + + HCC++; + + if (HCC == FrameWidth) + { + // we have finished the current scanline + HCC = 0; + + if (VSYNCCounter > 0) + { + VSYNCCounter--; + if (VSYNCCounter == 0) + VSYNC = false; + } + + VLC++; + + if (VLC == ScanlinesPerCharacter) + { + // completed a vertical character + VLC = 0; + VCC++; + + if (VCC == FrameHeight) + { + // screen has completed + VCC = 0; + } + } + + // check whether VSYNC should be raised + if (VCC == VerticalSyncPos && !VSYNC) + { + VSYNC = true; + VSYNCCounter = VSYNCWidth; + VSYNC_Callbacks(); + } + } + else if (HCC == HorizontalSyncPos && !HSYNC) + { + // start of HSYNC period + HSYNC = true; + HSYNCCounter = HSYNCWidth; + HSYNC_Callbacks(); + } + + // DISPTMG + if (HCC >= Regs[HOR_DISPLAYED] || VCC >= Regs[VER_DISPLAYED]) + { + DISPTMG = false; + } + else + { + DISPTMG = true; + } + /* + // check for DISPTMG + if (HCC >= Regs[HOR_DISPLAYED] + 1) + { + DISPTMG = false; + } + else if (VCC >= Regs[VER_DISPLAYED]) + { + DISPTMG = false; + } + else + { + DISPTMG = true; + } + */ + } + + public int lineCounter = 0; + + + /// /// Resets the chip /// @@ -624,6 +854,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC // updates widths UpdateWidths(); + + HSYNC = false; + VSYNC = false; + + HSYNCCounter = 0; + VSYNCCounter = 0; + + HCC = 0; + VCC = 0; + VLC = 0; } #endregion @@ -690,6 +930,18 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC } + if (SelectedRegister == HOR_TOTAL) + { + // always 63 + if (data != 63) + return; + } + + if (SelectedRegister == 1) + { + var d = data; + } + Regs[SelectedRegister] = (byte)(data & CPCMask[SelectedRegister]); if (SelectedRegister == HOR_AND_VER_SYNC_WIDTHS) @@ -809,26 +1061,26 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { switch (ChipType) { - case CRCTType.Hitachi_HD6845S: + case CRCTType.HD6845S: // Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC. Bits 3..0 define Horizontal Sync Width. // If 0 is programmed no HSYNC is generated. HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F; VSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 4) & 0x0F; break; - case CRCTType.UMC_UM6845R: + case CRCTType.UM6845R: // Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed no HSYNC is generated. HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F; VSYNCWidth = 16; break; - case CRCTType.Motorola_MC6845: + case CRCTType.MC6845: // Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed this gives a HSYNC width of 16. HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F; if (HSYNCWidth == 0) HSYNCWidth = 16; VSYNCWidth = 16; break; - case CRCTType.Amstrad_AMS40489: - case CRCTType.Amstrad_40226: + case CRCTType.AMS40489: + case CRCTType.AMS40226: // Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC.Bits 3..0 define Horizontal Sync Width. // If 0 is programmed this gives a HSYNC width of 16. HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F; @@ -960,11 +1212,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// public enum CRCTType { - Hitachi_HD6845S = 0, - UMC_UM6845R = 1, - Motorola_MC6845 = 2, - Amstrad_AMS40489 = 3, - Amstrad_40226 = 4 + HD6845S = 0, + UM6845 = 0, + UM6845R = 1, + MC6845 = 2, + AMS40489 = 3, + AMS40226 = 4 } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs index 23d3e3b641..5f318c47be 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs @@ -28,12 +28,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { _machine = machine; CurrentLine = new ScanLine(this); + + CRCT.AttachHSYNCCallback(OnHSYNC); + CRCT.AttachVSYNCCallback(OnVSYNC); } #endregion #region Palettes - + /// /// The standard CPC Pallete (ordered by firmware #) /// http://www.cpcwiki.eu/index.php/CPC_Palette @@ -108,6 +111,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC Colors.ARGB(0x80, 0x80, 0x00), // Yellow Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue }; + #endregion @@ -154,6 +158,22 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC ScreenBuffer = new int[BufferWidth * BufferHeight]; } + /// + /// Fired when the CRCT flags HSYNC + /// + public void OnHSYNC() + { + + } + + /// + /// Fired when the CRCT flags VSYNC + /// + public void OnVSYNC() + { + + } + #endregion #region IVideoProvider @@ -370,11 +390,37 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC // 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels) // RECT case 0: - Characters[charIndex].Pixels = new int[8]; + Characters[charIndex].Pixels = new int[16]; int m0Count = 0; - int m0B0P0i = vid1 & 170; + int pix = vid1 & 0xaa; + pix = ((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02 << 2)); + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + pix = vid1 & 0x55; + pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01 << 3))); + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + + pix = vid2 & 0xaa; + pix = ((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02 << 2)); + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + pix = vid2 & 0x55; + pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01 << 3))); + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]]; + /* + int m0B0P0i = vid1 & 0xaa; 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)); @@ -393,6 +439,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P0]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]]; Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]]; + */ break; // 2 bits per pixel - 2 bytes - 8 pixels (16 CRT pixels) @@ -500,6 +547,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC switch (ScreenMode) { case 0: + hScale = 1; + vScale = 2; + break; case 1: case 3: hScale = 2; diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs index 3c9cc04c2c..930efa8fcd 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs @@ -16,15 +16,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// /// /// - public CPC464(AmstradCPC cpc, Z80A cpu, List files, bool autoTape) + public CPC464(AmstradCPC cpc, Z80A cpu, List files, bool autoTape, AmstradCPC.BorderType borderType) { CPC = cpc; CPU = cpu; FrameLength = 79872; - CRCT = new CRCT_6845(CRCT_6845.CRCTType.Motorola_MC6845, this); - CRT = new CRTDevice(this); + CRCT = new CRCT_6845(CRCT_6845.CRCTType.MC6845, this); + //CRT = new CRTDevice(this); GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007); PPI = new PPI_8255(this); diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs index 04514c89f3..c0aae0bf36 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs @@ -16,15 +16,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// /// /// - public CPC6128(AmstradCPC cpc, Z80A cpu, List files, bool autoTape) + public CPC6128(AmstradCPC cpc, Z80A cpu, List files, bool autoTape, AmstradCPC.BorderType borderType) { CPC = cpc; CPU = cpu; FrameLength = 79872; - CRCT = new CRCT_6845(CRCT_6845.CRCTType.Motorola_MC6845, this); - CRT = new CRTDevice(this); + CRCT = new CRCT_6845(CRCT_6845.CRCTType.MC6845, this); + //CRT = new CRTDevice(this); GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007); PPI = new PPI_8255(this); diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Media.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Media.cs index 3cf30ce22d..af001c4957 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Media.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Media.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -95,7 +96,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC diskMediaIndex = result; // fire osd message - //Spectrum.OSD_DiskInserted(); + CPC.OSD_DiskInserted(); LoadDiskMedia(); } @@ -132,6 +133,63 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC diskImages.Add(m); CPC._diskInfo.Add(CPC._gameInfo[cnt]); break; + case CPCMediaType.DiskDoubleSided: + // this is a bit tricky. we will attempt to parse the double sided disk image byte array, + // then output two separate image byte arrays + List working = new List(); + foreach (DiskType type in Enum.GetValues(typeof(DiskType))) + { + bool found = false; + + switch (type) + { + case DiskType.CPCExtended: + found = CPCExtendedFloppyDisk.SplitDoubleSided(m, working); + break; + case DiskType.CPC: + found = CPCFloppyDisk.SplitDoubleSided(m, working); + break; + } + + if (found) + { + // add side 1 + diskImages.Add(working[0]); + // add side 2 + diskImages.Add(working[1]); + + Common.GameInfo one = new Common.GameInfo(); + Common.GameInfo two = new Common.GameInfo(); + var gi = CPC._gameInfo[cnt]; + for (int i = 0; i < 2; i++) + { + Common.GameInfo work = new Common.GameInfo(); + if (i == 0) + { + work = one; + } + else if (i == 1) + { + work = two; + } + + work.FirmwareHash = gi.FirmwareHash; + work.Hash = gi.Hash; + work.Name = gi.Name + " (Parsed Side " + (i + 1) + ")"; + work.Region = gi.Region; + work.NotInDatabase = gi.NotInDatabase; + work.Status = gi.Status; + work.System = gi.System; + + CPC._diskInfo.Add(work); + } + } + else + { + + } + } + break; } cnt++; @@ -179,7 +237,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC if (hdr.ToUpper().Contains("EXTENDED CPC DSK") || hdr.ToUpper().Contains("MV - CPC")) { // amstrad .dsk disk file - return CPCMediaType.Disk; + // check for number of sides + var sides = data[0x31]; + if (sides == 1) + return CPCMediaType.Disk; + else + return CPCMediaType.DiskDoubleSided; } // tape checking @@ -198,7 +261,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { None, Tape, - Disk + Disk, + DiskDoubleSided } } diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs index 6dc9ba3ee2..0fb5f9a916 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs @@ -65,7 +65,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// /// Renders pixels to the screen /// - public CRTDevice CRT { get; set; } + //public CRTDevice CRT { get; set; } /// /// The PPI contoller chip @@ -140,6 +140,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC public virtual void ExecuteFrame(bool render, bool renderSound) { GateArray.FrameEnd = false; + CRCT.lineCounter = 0; InputRead = false; _render = render; @@ -157,8 +158,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC PollInput(); - CRT.SetupVideo(); - CRT.ScanlineCounter = 0; + //CRT.SetupVideo(); + //CRT.ScanlineCounter = 0; while (!GateArray.FrameEnd) { @@ -168,9 +169,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded) TapeDevice.TapeCycle(); } - - //OverFlow = (int)CurrentFrameCycle - GateArray.FrameLength; - // we have reached the end of a frame LastFrameStartCPUTick = CPU.TotalExecutedCycles; // - OverFlow; @@ -346,7 +344,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC ser.Sync("RAM64KBank", ref RAM64KBank); CRCT.SyncState(ser); - CRT.SyncState(ser); + //CRT.SyncState(ser); GateArray.SyncState(ser); KeyboardDevice.SyncState(ser); TapeBuzzer.SyncState(ser); diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCExtendedFloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCExtendedFloppyDisk.cs index c55d9175d2..07919dce7a 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCExtendedFloppyDisk.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCExtendedFloppyDisk.cs @@ -1,5 +1,7 @@ using System.Text; using BizHawk.Common; +using System; +using System.Collections.Generic; namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { @@ -144,6 +146,88 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC return true; } + /// + /// Takes a double-sided disk byte array and converts into 2 single-sided arrays + /// + /// + /// + /// + public static bool SplitDoubleSided(byte[] data, List results) + { + // look for standard magic string + string ident = Encoding.ASCII.GetString(data, 0, 16); + if (!ident.ToUpper().Contains("EXTENDED CPC DSK")) + { + // incorrect format + return false; + } + + byte[] S0 = new byte[data.Length]; + byte[] S1 = new byte[data.Length]; + + // disk info block + Array.Copy(data, 0, S0, 0, 0x100); + Array.Copy(data, 0, S1, 0, 0x100); + // change side number + S0[0x31] = 1; + S1[0x31] = 1; + + // extended format has different track sizes + int[] trkSizes = new int[data[0x30] * data[0x31]]; + + int pos = 0x34; + for (int i = 0; i < data[0x30] * data[0x31]; i++) + { + trkSizes[i] = data[pos] * 256; + // clear destination trk sizes (will be added later) + S0[pos] = 0; + S1[pos] = 0; + pos++; + } + + // start at track info blocks + int mPos = 0x100; + int s0Pos = 0x100; + int s0tCount = 0; + int s1tCount = 0; + int s1Pos = 0x100; + int tCount = 0; + + while (tCount < data[0x30] * data[0x31]) + { + // which side is this? + var side = data[mPos + 0x11]; + if (side == 0) + { + // side 1 + Array.Copy(data, mPos, S0, s0Pos, trkSizes[tCount]); + s0Pos += trkSizes[tCount]; + // trk size table + S0[0x34 + s0tCount++] = (byte)(trkSizes[tCount] / 256); + } + else if (side == 1) + { + // side 2 + Array.Copy(data, mPos, S1, s1Pos, trkSizes[tCount]); + s1Pos += trkSizes[tCount]; + // trk size table + S1[0x34 + s1tCount++] = (byte)(trkSizes[tCount] / 256); + } + + mPos += trkSizes[tCount++]; + } + + byte[] s0final = new byte[s0Pos]; + byte[] s1final = new byte[s1Pos]; + Array.Copy(S0, 0, s0final, 0, s0Pos); + Array.Copy(S1, 0, s1final, 0, s1Pos); + + results.Add(s0final); + results.Add(s1final); + + return true; + } + /// /// State serlialization /// diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCFloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCFloppyDisk.cs index d6298a81b3..c30415842e 100644 --- a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCFloppyDisk.cs +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/CPCFloppyDisk.cs @@ -1,5 +1,7 @@ using System.Text; using BizHawk.Common; +using System.Collections.Generic; +using System; namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { @@ -149,6 +151,70 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC return true; } + /// + /// Takes a double-sided disk byte array and converts into 2 single-sided arrays + /// + /// + /// + /// + public static bool SplitDoubleSided(byte[] data, List results) + { + // look for standard magic string + string ident = Encoding.ASCII.GetString(data, 0, 16); + if (!ident.ToUpper().Contains("MV - CPC")) + { + // incorrect format + return false; + } + + byte[] S0 = new byte[data.Length]; + byte[] S1 = new byte[data.Length]; + + // disk info block + Array.Copy(data, 0, S0, 0, 0x100); + Array.Copy(data, 0, S1, 0, 0x100); + // change side number + S0[0x31] = 1; + S1[0x31] = 1; + + int trkSize = MediaConverter.GetWordValue(data, 0x32); + + // start at track info blocks + int mPos = 0x100; + int s0Pos = 0x100; + int s1Pos = 0x100; + + while (mPos < trkSize * data[0x30] * data[0x31]) + { + // which side is this? + var side = data[mPos + 0x11]; + if (side == 0) + { + // side 1 + Array.Copy(data, mPos, S0, s0Pos, trkSize); + s0Pos += trkSize; + } + else if (side == 1) + { + // side 2 + Array.Copy(data, mPos, S1, s1Pos, trkSize); + s1Pos += trkSize; + } + + mPos += trkSize; + } + + byte[] s0final = new byte[s0Pos]; + byte[] s1final = new byte[s1Pos]; + Array.Copy(S0, 0, s0final, 0, s0Pos); + Array.Copy(S1, 0, s1final, 0, s1Pos); + + results.Add(s0final); + results.Add(s1final); + + return true; + } + /// /// State serlialization /// diff --git a/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/DiskHandler.cs b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/DiskHandler.cs new file mode 100644 index 0000000000..bc06c1094b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/DiskHandler.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.AmstradCPC +{ + /// + /// This is called first when importing disk images + /// Disk images can be single or double-sided, so we need to handle that + /// + public class DiskHandler + { + + } +}