diff --git a/ComputerSystems/Commodore64/C64.cs b/ComputerSystems/Commodore64/C64.cs index 6dc3101..6959749 100644 --- a/ComputerSystems/Commodore64/C64.cs +++ b/ComputerSystems/Commodore64/C64.cs @@ -19,6 +19,8 @@ public class C64 { private bool _isRunnning = false; private TaskCompletionSource _tcsStop; + public double CpuClockSpeed { get; set; } = 1.0f / CLOCK_PAL; + public Cia Cia { get; private set; } public VicIi Vic { get; private set; } @@ -36,6 +38,7 @@ public void Initialize() { Cia = new Cia(); Vic = new VicIi(); + Vic.C64 = this; Memory = new C64Bus(Cia, Vic); Cpu = new Cpu(Memory); @@ -51,14 +54,13 @@ public void PowerOn() { _isRunnning = true; _tcsStop = new TaskCompletionSource(); - var cpuClockSpeedPal = 1.0f / CLOCK_PAL; var swCpuClock = Stopwatch.StartNew(); var t = new Thread(() => { while (_isRunnning) { // CPU clock - if (swCpuClock.Elapsed.TotalMilliseconds > cpuClockSpeedPal) { + if (swCpuClock.Elapsed.TotalMilliseconds > CpuClockSpeed) { // Clock CIA 1 Cia.Clock(); diff --git a/ComputerSystems/Commodore64/FormC64Screen.Designer.cs b/ComputerSystems/Commodore64/FormC64Screen.Designer.cs index b8dc2af..2575774 100644 --- a/ComputerSystems/Commodore64/FormC64Screen.Designer.cs +++ b/ComputerSystems/Commodore64/FormC64Screen.Designer.cs @@ -26,7 +26,7 @@ private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormC64Screen)); this.pScreen = new System.Windows.Forms.PictureBox(); this.statusMain = new System.Windows.Forms.StatusStrip(); - this.lblFps = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblStatusCpu = new System.Windows.Forms.ToolStripStatusLabel(); this.lblCycles = new System.Windows.Forms.ToolStripStatusLabel(); this.lblInstructions = new System.Windows.Forms.ToolStripStatusLabel(); this.lblKeyboardDisabled = new System.Windows.Forms.ToolStripStatusLabel(); @@ -46,9 +46,21 @@ private void InitializeComponent() { this.btnDebugger = new System.Windows.Forms.ToolStripButton(); this.ofd = new System.Windows.Forms.OpenFileDialog(); this.sfd = new System.Windows.Forms.SaveFileDialog(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.lblFps = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblStatusVic = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblVicCycles = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblVicGraphicsMode = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblVicScreenOn = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblVicCurrentLine = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblVicCurrentLineCycle = new System.Windows.Forms.ToolStripStatusLabel(); + this.btnClockSpeedSlower = new System.Windows.Forms.ToolStripButton(); + this.btnClockSpeedFaster = new System.Windows.Forms.ToolStripButton(); + this.lblClockSpeed = new System.Windows.Forms.ToolStripStatusLabel(); ((System.ComponentModel.ISupportInitialize)(this.pScreen)).BeginInit(); this.statusMain.SuspendLayout(); this.toolMain.SuspendLayout(); + this.statusStrip1.SuspendLayout(); this.SuspendLayout(); // // pScreen @@ -59,7 +71,7 @@ private void InitializeComponent() { this.pScreen.Location = new System.Drawing.Point(0, 25); this.pScreen.Margin = new System.Windows.Forms.Padding(0); this.pScreen.Name = "pScreen"; - this.pScreen.Size = new System.Drawing.Size(640, 400); + this.pScreen.Size = new System.Drawing.Size(806, 568); this.pScreen.TabIndex = 1; this.pScreen.TabStop = false; this.pScreen.DragDrop += new System.Windows.Forms.DragEventHandler(this.pScreen_DragDropAsync); @@ -70,22 +82,24 @@ private void InitializeComponent() { // statusMain // this.statusMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.lblFps, + this.lblStatusCpu, + this.lblClockSpeed, this.lblCycles, this.lblInstructions, this.lblKeyboardDisabled}); - this.statusMain.Location = new System.Drawing.Point(0, 423); + this.statusMain.Location = new System.Drawing.Point(0, 617); this.statusMain.Name = "statusMain"; - this.statusMain.Size = new System.Drawing.Size(640, 24); + this.statusMain.Size = new System.Drawing.Size(806, 24); this.statusMain.TabIndex = 2; this.statusMain.Text = "statusStrip1"; // - // lblFps + // lblStatusCpu // - this.lblFps.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; - this.lblFps.Name = "lblFps"; - this.lblFps.Size = new System.Drawing.Size(36, 19); - this.lblFps.Text = "0 fps"; + this.lblStatusCpu.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblStatusCpu.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); + this.lblStatusCpu.Name = "lblStatusCpu"; + this.lblStatusCpu.Size = new System.Drawing.Size(34, 19); + this.lblStatusCpu.Text = "CPU"; // // lblCycles // @@ -105,7 +119,7 @@ private void InitializeComponent() { // this.lblKeyboardDisabled.ForeColor = System.Drawing.Color.Red; this.lblKeyboardDisabled.Name = "lblKeyboardDisabled"; - this.lblKeyboardDisabled.Size = new System.Drawing.Size(455, 19); + this.lblKeyboardDisabled.Size = new System.Drawing.Size(392, 19); this.lblKeyboardDisabled.Spring = true; this.lblKeyboardDisabled.Text = "Keyboard Disabled"; this.lblKeyboardDisabled.TextAlign = System.Drawing.ContentAlignment.MiddleRight; @@ -124,10 +138,12 @@ private void InitializeComponent() { this.separator3, this.btnCopyOutput, this.separator4, - this.btnDebugger}); + this.btnDebugger, + this.btnClockSpeedSlower, + this.btnClockSpeedFaster}); this.toolMain.Location = new System.Drawing.Point(0, 0); this.toolMain.Name = "toolMain"; - this.toolMain.Size = new System.Drawing.Size(640, 25); + this.toolMain.Size = new System.Drawing.Size(806, 25); this.toolMain.TabIndex = 0; this.toolMain.Text = "toolStrip1"; // @@ -225,7 +241,7 @@ private void InitializeComponent() { // btnCopyRawOutput // this.btnCopyRawOutput.Name = "btnCopyRawOutput"; - this.btnCopyRawOutput.Size = new System.Drawing.Size(124, 22); + this.btnCopyRawOutput.Size = new System.Drawing.Size(180, 22); this.btnCopyRawOutput.Text = "Copy raw"; this.btnCopyRawOutput.ToolTipText = "Copy raw screen"; this.btnCopyRawOutput.Click += new System.EventHandler(this.BtnCopyRawOutput_Click); @@ -253,12 +269,106 @@ private void InitializeComponent() { // this.sfd.Filter = "PRG-files|*.prg"; // + // statusStrip1 + // + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.lblStatusVic, + this.lblFps, + this.lblVicCycles, + this.lblVicCurrentLine, + this.lblVicCurrentLineCycle, + this.lblVicGraphicsMode, + this.lblVicScreenOn}); + this.statusStrip1.Location = new System.Drawing.Point(0, 593); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Size = new System.Drawing.Size(806, 24); + this.statusStrip1.TabIndex = 4; + this.statusStrip1.Text = "statusStrip1"; + // + // lblFps + // + this.lblFps.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblFps.Name = "lblFps"; + this.lblFps.Size = new System.Drawing.Size(36, 19); + this.lblFps.Text = "0 fps"; + // + // lblStatusVic + // + this.lblStatusVic.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblStatusVic.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblStatusVic.Name = "lblStatusVic"; + this.lblStatusVic.Size = new System.Drawing.Size(43, 19); + this.lblStatusVic.Text = "VIC-II"; + // + // lblVicCycles + // + this.lblVicCycles.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblVicCycles.Name = "lblVicCycles"; + this.lblVicCycles.Size = new System.Drawing.Size(52, 19); + this.lblVicCycles.Text = "0 cycles"; + // + // lblVicGraphicsMode + // + this.lblVicGraphicsMode.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblVicGraphicsMode.Name = "lblVicGraphicsMode"; + this.lblVicGraphicsMode.Size = new System.Drawing.Size(45, 19); + this.lblVicGraphicsMode.Text = "Mode:"; + // + // lblVicScreenOn + // + this.lblVicScreenOn.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblVicScreenOn.Name = "lblVicScreenOn"; + this.lblVicScreenOn.Size = new System.Drawing.Size(49, 19); + this.lblVicScreenOn.Text = "Screen:"; + // + // lblVicCurrentLine + // + this.lblVicCurrentLine.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblVicCurrentLine.Name = "lblVicCurrentLine"; + this.lblVicCurrentLine.Size = new System.Drawing.Size(36, 19); + this.lblVicCurrentLine.Text = "Line:"; + // + // lblVicCurrentLineCycle + // + this.lblVicCurrentLineCycle.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblVicCurrentLineCycle.Name = "lblVicCurrentLineCycle"; + this.lblVicCurrentLineCycle.Size = new System.Drawing.Size(33, 19); + this.lblVicCurrentLineCycle.Text = "Pos:"; + // + // btnClockSpeedSlower + // + this.btnClockSpeedSlower.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.btnClockSpeedSlower.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnClockSpeedSlower.Name = "btnClockSpeedSlower"; + this.btnClockSpeedSlower.Size = new System.Drawing.Size(59, 22); + this.btnClockSpeedSlower.Text = "Speed [-]"; + this.btnClockSpeedSlower.ToolTipText = "Clock Speed -"; + this.btnClockSpeedSlower.Click += new System.EventHandler(this.btnSlowDown_Click); + // + // btnClockSpeedFaster + // + this.btnClockSpeedFaster.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.btnClockSpeedFaster.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnClockSpeedFaster.Name = "btnClockSpeedFaster"; + this.btnClockSpeedFaster.Size = new System.Drawing.Size(62, 22); + this.btnClockSpeedFaster.Text = "Speed [+]"; + this.btnClockSpeedFaster.ToolTipText = "Clock Speed +"; + this.btnClockSpeedFaster.Click += new System.EventHandler(this.btnClockSpeedFaster_Click); + // + // lblClockSpeed + // + this.lblClockSpeed.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.lblClockSpeed.Name = "lblClockSpeed"; + this.lblClockSpeed.Size = new System.Drawing.Size(34, 19); + this.lblClockSpeed.Text = "0 Hz"; + // // FormC64Screen // this.AllowDrop = true; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(640, 447); + this.ClientSize = new System.Drawing.Size(806, 641); + this.Controls.Add(this.statusStrip1); this.Controls.Add(this.toolMain); this.Controls.Add(this.statusMain); this.Controls.Add(this.pScreen); @@ -274,6 +384,8 @@ private void InitializeComponent() { this.statusMain.PerformLayout(); this.toolMain.ResumeLayout(false); this.toolMain.PerformLayout(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -282,7 +394,7 @@ private void InitializeComponent() { #endregion private System.Windows.Forms.PictureBox pScreen; private System.Windows.Forms.StatusStrip statusMain; - private System.Windows.Forms.ToolStripStatusLabel lblFps; + private System.Windows.Forms.ToolStripStatusLabel lblStatusCpu; private System.Windows.Forms.ToolStrip toolMain; private System.Windows.Forms.ToolStripButton btnUseCrtFilter; private System.Windows.Forms.ToolStripButton btnRestart; @@ -302,5 +414,16 @@ private void InitializeComponent() { private System.Windows.Forms.ToolStripStatusLabel lblKeyboardDisabled; private System.Windows.Forms.ToolStripButton btnReset; private System.Windows.Forms.ToolStripSeparator separator4; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel lblFps; + private System.Windows.Forms.ToolStripStatusLabel lblStatusVic; + private System.Windows.Forms.ToolStripStatusLabel lblVicCycles; + private System.Windows.Forms.ToolStripStatusLabel lblVicGraphicsMode; + private System.Windows.Forms.ToolStripStatusLabel lblVicScreenOn; + private System.Windows.Forms.ToolStripStatusLabel lblVicCurrentLine; + private System.Windows.Forms.ToolStripStatusLabel lblVicCurrentLineCycle; + private System.Windows.Forms.ToolStripButton btnClockSpeedSlower; + private System.Windows.Forms.ToolStripButton btnClockSpeedFaster; + private System.Windows.Forms.ToolStripStatusLabel lblClockSpeed; } } \ No newline at end of file diff --git a/ComputerSystems/Commodore64/FormC64Screen.cs b/ComputerSystems/Commodore64/FormC64Screen.cs index cf5f46c..2377129 100644 --- a/ComputerSystems/Commodore64/FormC64Screen.cs +++ b/ComputerSystems/Commodore64/FormC64Screen.cs @@ -2,17 +2,15 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Drawing.Imaging; -using System.Threading; using System.Windows.Forms; -using Extensions.Byte; -using Extensions.Enums; using System.IO; using System.Linq; using System.Collections.Generic; using Commodore64.Properties; using Timer = System.Threading.Timer; using Debugger; +using System.Threading; +using System.Drawing.Imaging; namespace ComputerSystem.Commodore64 { public partial class FormC64Screen : Form { @@ -22,29 +20,25 @@ public partial class FormC64Screen : Form { private readonly Stopwatch _stopWatch = new Stopwatch(); private double _fpsActual = 0.0f; - private double _screenRefreshRate = 1000.0f / 60.0f; // 60 fps - - private readonly Bitmap _bC64ScreenBuffer; + private double _screenRefreshRate = 1000.0f / 60.0f; // 60 fps private Bitmap _bC64ScreenOutputBuffer; private Graphics _gC64ScreenOutputBuffer; - private readonly Color[] _screenBufferPixels; private readonly Pen _penScanLine; private readonly Pen _penScanLine2; - private Timer _uiRefreshTimer; - + private Timer _uiRefreshTimer; + private Bitmap _bC64ScreenBuffer; public FormC64Screen(C64 c64) { InitializeComponent(); - C64 = c64; - + C64 = c64; + _bC64ScreenBuffer = new Bitmap(320, 200, PixelFormat.Format24bppRgb); _bC64ScreenOutputBuffer = new Bitmap(pScreen.Width, pScreen.Height); _gC64ScreenOutputBuffer = Graphics.FromImage(_bC64ScreenOutputBuffer); - _screenBufferPixels = new Color[_bC64ScreenBuffer.Width * _bC64ScreenBuffer.Height]; _penScanLine = new Pen(Color.FromArgb(100, 127, 127, 127)); _penScanLine2 = new Pen(Color.FromArgb(20, 127, 127, 127)); @@ -53,10 +47,20 @@ public FormC64Screen(C64 c64) { try { Invoke(new Action(() => { - lblFps.Text = $"{_fpsActual:F0} fps"; + lblClockSpeed.Text = $"{1 / c64.CpuClockSpeed:F0} Hz"; + lblCycles.Text = $"{c64.Cpu.TotalCycles:N0} cycles"; lblInstructions.Text = $"{c64.Cpu.TotalInstructions:N0} instructions"; lblKeyboardDisabled.Visible = !c64.KeyboardActivated; + + lblFps.Text = $"{((int)_fpsActual):D3} fps"; + lblVicCycles.Text = $"{c64.Vic.TotalCycles:N0} cycles"; + + lblVicCurrentLine.Text = $"Line: {c64.Vic.CurrentLine:D3}"; + lblVicCurrentLineCycle.Text = $"Pos: {c64.Vic.CurrentLineCycle:D2}"; + + lblVicGraphicsMode.Text = C64.Vic.ScreenControlRegisterTextModeBitmapMode ? "Mode: Bitmap" : "Mode: Text"; + lblVicScreenOn.Text = C64.Vic.ScreenControlRegisterScreenOffOn ? "Screen: On" : "Screen: Off"; })); } catch { } @@ -68,163 +72,35 @@ public FormC64Screen(C64 c64) { private void FormC64Screen_Load(object sender, EventArgs e) { pScreen.AllowDrop = true; - new Thread(InvalidateScreen).Start(); - } - - private void InvalidateScreen() { - while (true) { - if (!Visible || WindowState == FormWindowState.Minimized) { - Thread.Sleep(1000); - continue; - } - - try { - Invoke(new Action(() => { - pScreen.Invalidate(); - })); - - } catch (Exception) { - return; - } - - Thread.Sleep((int)_screenRefreshRate); - } - } - - public void UpdateScreenBuffer() { - var bgColor = Colors.FromByte((byte)(C64.Vic._registers[0x21] & 0b00001111)); - - for (var i = 0; i < 1000; i++) { - var petsciiCode = vicRead((ushort)(getScreenMemoryPointer() + i)); - var fgColor = Colors.FromByte((byte)(C64.Memory[C64MemoryOffsets.SCREEN_COLOR_RAM + i] & 0b00001111)); - //var fgColor = Colors.FromByte((byte)(vicRead((ushort)(0x0800 + i)) & 0b00001111)); + C64.Vic.OnLastScanLine += Vic_OnLastScanLine; - var line = (i / 40); - var characterInLine = i % 40; - var indexLineOffset = (2560 * line) + (8 * characterInLine); - - for (int row = 0; row <= 7; row++) { - var charRow = vicRead((ushort)(getCharacterMemoryPointer() + (petsciiCode * 8) + row)); - - var indexRowOffset = indexLineOffset + (320 * row); - - for (int col = 0; col <= 7; col++) { - var indexPixelOffset = indexRowOffset + col; - - _screenBufferPixels[indexPixelOffset] = charRow.IsBitSet(7 - (BitIndex)col) ? fgColor : bgColor; - } - - } - - } - - SetPixels(_bC64ScreenBuffer, _screenBufferPixels); - _gC64ScreenOutputBuffer.DrawImage(_bC64ScreenBuffer, 0, 0, _bC64ScreenOutputBuffer.Width, _bC64ScreenOutputBuffer.Height); + new Thread(InvalidateScreen).Start(); } - public int getScreenMemoryPointer() { - var bit4to7 = (C64.Memory.Read(0xD018) >> 4) & 0b00001111; - - switch (bit4to7) { - case 0b0000: - return 0x0000; - case 0b0001: - return 0x0400; - case 0b0010: - return 0x0800; - case 0b0011: - return 0x0C00; - case 0b0100: - return 0x1000; - case 0b0101: - return 0x1400; - case 0b0110: - return 0x1800; - case 0b0111: - return 0x1C00; - case 0b1000: - return 0x2000; - case 0b1001: - return 0x2400; - case 0b1010: - return 0x2800; - case 0b1011: - return 0x2C00; - case 0b1100: - return 0x3000; - case 0b1101: - return 0x3400; - case 0b1110: - return 0x3800; - case 0b1111: - return 0x3C00; + private void Vic_OnLastScanLine(object sender, EventArgs e) { - } - - throw new NotImplementedException(); - } - - public int getCharacterMemoryPointer() { - var bit1to3 = (C64.Memory.Read(0xD018) >> 1) & 0b00000111; - - switch (bit1to3) { - case 0b000: - return 0x0000; - case 0b001: - return 0x0800; - case 0b010: - return 0x1000; - case 0b011: - return 0x1800; - case 0b100: - return 0x2000; - case 0b101: - return 0x2800; - case 0b110: - return 0x3000; - case 0b111: - return 0x38000; - } - - throw new NotImplementedException(); - } - - public byte vicRead(ushort address) { + } - var vicBankOffset = 0; - - switch (C64.Memory[0xDD00] & 0b00000011) - { - case 0b00000011: - vicBankOffset = 0; - - if (address >= 0x1000 && address <= 0x1FFF) { - return C64.Memory._romCharacter[address - 0x1000]; - } - - break; - - case 0b00000010: - vicBankOffset = 0x4000; - break; - - case 0b00000001: - vicBankOffset = 0x8000; - if (address >= 0x1000 && address <= 0x1FFF) { - return C64.Memory._romCharacter[address - 0x1000]; - } - - break; - - case 0b00000000: - vicBankOffset = 0xC000; - break; - } - - - return C64.Memory.Read(vicBankOffset + address); - } + private void InvalidateScreen() { + while (true) { + if (!Visible || WindowState == FormWindowState.Minimized) { + Thread.Sleep(1000); + continue; + } + + try { + Invoke(new Action(() => { + pScreen.Invalidate(); + })); + + } catch (Exception) { + return; + } + + Thread.Sleep((int)_screenRefreshRate); + } + } public void ApplyCrtFilter() { for (int i = 0; i < _bC64ScreenOutputBuffer.Width; i += (int)(_penScanLine2.Width * 2)) { @@ -236,36 +112,10 @@ public void ApplyCrtFilter() { } } - public void SetPixels(Bitmap b, Color[] pixels) { - var width = b.Width; - var height = b.Height; - - BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); - - int stride = data.Stride; - - unsafe { - byte* ptr = (byte*)data.Scan0; - - for (int y = 0; y < height; y++) { - - for (int x = 0; x < width; x++) { - - var index = (y * width) + x; - - ptr[(x * 3) + y * stride] = pixels[index].B; - ptr[(x * 3) + y * stride + 1] = pixels[index].G; - ptr[(x * 3) + y * stride + 2] = pixels[index].R; - } - - } - } - - b.UnlockBits(data); - } + private void PScreen_Paint(object sender, PaintEventArgs e) { + SetPixels(_bC64ScreenBuffer, C64.Vic.ScreenBufferPixels); + _gC64ScreenOutputBuffer.DrawImage(_bC64ScreenBuffer, 0, 0, _bC64ScreenOutputBuffer.Width, _bC64ScreenOutputBuffer.Height); - private void PScreen_Paint(object sender, PaintEventArgs e) { - UpdateScreenBuffer(); if (btnUseCrtFilter.Checked) ApplyCrtFilter(); e.Graphics.DrawImage(_bC64ScreenOutputBuffer, 0, 0, pScreen.Width, pScreen.Height); @@ -290,7 +140,6 @@ private void PScreen_Resize(object sender, EventArgs e) { } private void FormC64Screen_FormClosing(object sender, FormClosingEventArgs e) { - _bC64ScreenBuffer.Dispose(); _gC64ScreenOutputBuffer.Dispose(); _bC64ScreenOutputBuffer.Dispose(); @@ -404,5 +253,41 @@ private void pScreen_DragEnter(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Copy; } } + + public void SetPixels(Bitmap b, Color[] pixels) { + var width = b.Width; + var height = b.Height; + + BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); + + int stride = data.Stride; + + unsafe { + byte* ptr = (byte*)data.Scan0; + + for (int y = 0; y < height; y++) { + + for (int x = 0; x < width; x++) { + + var index = (y * width) + x; + + ptr[(x * 3) + y * stride] = pixels[index].B; + ptr[(x * 3) + y * stride + 1] = pixels[index].G; + ptr[(x * 3) + y * stride + 2] = pixels[index].R; + } + + } + } + + b.UnlockBits(data); + } + + private void btnSlowDown_Click(object sender, EventArgs e) { + C64.CpuClockSpeed *= 10.0d; + } + + private void btnClockSpeedFaster_Click(object sender, EventArgs e) { + C64.CpuClockSpeed /= 10.0d; + } } } diff --git a/ComputerSystems/Commodore64/FormC64Screen.resx b/ComputerSystems/Commodore64/FormC64Screen.resx index 28ebb8f..49ea90f 100644 --- a/ComputerSystems/Commodore64/FormC64Screen.resx +++ b/ComputerSystems/Commodore64/FormC64Screen.resx @@ -138,14 +138,14 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGhSURBVDhPvVLJTgJBFJwP8ApD2MIOw8xA2LcAc9XEX/Du - F3gwGOPFg99hNNEvcYk3bxzUaOJB9KDGKJO2qmlUlOVmJR2G6vfqVb/3tH9FKBTaVJ+zEQgE1mKx2JH6 - O4FWqyUMw7jRdd1U1DcikUivUqk8O44jMpnMiaInUK/X3UajIcrl8vuXiMfjWSoUCq9MHJ9oNLojL38B - SVY+n3+AkDBN80rR0rbTbDbdsYDP5zPU1R9QBAWH1WpVoCfbkoT9DQrYtn2Hd7qSnIN4PH5AgVQqda55 - vV690+mIbrcreAnVdRk1B3C4TAE09FH2oN1uSwGKqZi5YBwaLorF4sgtuvpCF36/f1USC0AHFMC0BpKw - LOuSLpLJ5LEkFgA920dRkUgkTiWBxdmjAGbsBoNBW5IzwPnncrmPUqkkENtTtHRxy03DiAYclaInwGTY - vmcyfvuKHoFJaMoQ4xTosAt7h3jrChvGN9M2Kg9pHcv0NnVXWAG7cM1N46nVahSTh01jZYyuPzX5J8Lh - 8G46nb7IZrNPqOhy3mjwGd68pUIUNO0THEmJ2NejSm4AAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGdSURBVDhPvVLJTgJBFJwP8ApD2MIOw8yEfQ8wV038Be9+ + gQeDMV48+B1GE/0Sl3jzxkGNJh5ED2qMMmmrmkZFWW5W0mGofq+6uvpp/4pQKLSpPmcjEAisxWKxI/V3 + Aq1WS2QymRtd101FfSMSifQqlcqz4zgsOlH0BOr1uttoNES5XH7/EvF4PEv5fP6VjeMVjUZ35OYvoMnK + 5XIPEBKmaV4pWtp2ms2mOxbw+XyG2voDiuDAYbVaFchkW5Kwv0EB27bvcE9XknMQj8cPKJBKpc41r9er + dzod0e12BTehui6r5gAOlymArB5lBu12WwpQTNXMBesQuCgWiyO3SPWFLvx+/6okFoAOKJBOpweSsCzr + ki6SyeSxJBYAme3jUJFIJE4lgcHZowDe2A0Gg7YkZ4Dvj7A/SqWSQG1P0dLFLSetUCgM+FSKngCbEdw9 + m/HbV/QIbELzEM8pkLALe4e46woD451pGycPaR3D9DZ1VpS9a04aV61Wo5hcDI0nG4bRn9r8E+FweBcJ + X2Sz2Sec6PK9EfAZ7rylShQ07RPvPYm98+6JJQAAAABJRU5ErkJggg== @@ -216,4 +216,7 @@ 403, 17 + + 587, 17 + \ No newline at end of file diff --git a/ComputerSystems/Commodore64/VicIi.cs b/ComputerSystems/Commodore64/VicIi.cs index 91d6ff6..ffe06ec 100644 --- a/ComputerSystems/Commodore64/VicIi.cs +++ b/ComputerSystems/Commodore64/VicIi.cs @@ -6,11 +6,18 @@ namespace Commodore64 { public class VicIi { + /// + /// http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt + /// + public event EventHandler OnGenerateRasterLineInterrupt; + public event EventHandler OnLastScanLine; // VIC-II has 64 registers (47 in use, $D02F-$D03F Unusable (17 bytes), $D040-$D3FF VIC-II register images (repeated every $40, 64 bytes)) public byte[] _registers = new byte[0x40]; + //TODO: Temporary dependency + public C64 C64; private const byte REGISTER_SCREEN_CONTROL_0x11 = 0x11; private const byte REGISTER_CURRENT_RASTER_LINE_0x12 = 0x12; @@ -19,6 +26,8 @@ public class VicIi { private const byte REGISTER_INTERRUPT_STATUS_0x19 = 0x19; private const byte REGISTER_INTERRUPT_CONTROL_0x1A = 0x1A; + public Color[] ScreenBufferPixels { get; } = new Color[320 * 200]; + private int _rasterLineToGenerateInterruptAt = 0; public byte this[int index] { @@ -89,9 +98,9 @@ public enum TvSystem { public int CurrentLine = 0; public int CurrentLineCycle = 0; - public int TotalCycles = 0; + public bool InVerticalBlank => CurrentLine >= 300 || CurrentLine <= 15; - public Color[] ScreenBufferPixels = new Color[USABLE_WIDTH_BORDER * USABLE_HEIGHT_BORDER]; + public int TotalCycles = 0; // Screen control register public bool ScreenControlRegisterScreenHeight => this[REGISTER_SCREEN_CONTROL_0x11].IsBitSet(BitFlag.BIT_3); // False = 24 rows, True = 25 rows @@ -111,16 +120,18 @@ public VicIi() { } - public void Cycle() { - + public void Cycle() { CurrentLineCycle++; + // Every line takes 63 cycles if (CurrentLineCycle == 64) { CurrentLineCycle = 0; CurrentLine++; + // Generate raster interrupt if the current line equals interrupt line + // TODO: Implement the Interrupt latch register ($D019) !!!!!!! if (InterruptControlRegisterRasterInterruptEnabled && (CurrentLine == _rasterLineToGenerateInterruptAt)) { OnGenerateRasterLineInterrupt?.Invoke(this, null); } @@ -129,16 +140,146 @@ public void Cycle() { (CurrentTvSystem == TvSystem.NTSC && CurrentLine == FULL_HEIGHT_NTSC)) { CurrentLine = 0; + + OnLastScanLine?.Invoke(this, null); + UpdateScreenBufferPixels(); } } - UpdateScreenBufferPixels(); - TotalCycles++; } - private void UpdateScreenBufferPixels() { + public void UpdateScreenBufferPixels() { + var bgColor = Colors.FromByte((byte)(C64.Vic._registers[0x21] & 0b00001111)); + + for (var i = 0; i < 1000; i++) { + var petsciiCode = vicRead((ushort)(getScreenMemoryPointer() + i)); + var fgColor = Colors.FromByte((byte)(C64.Memory[C64MemoryOffsets.SCREEN_COLOR_RAM + i] & 0b00001111)); + //var fgColor = Colors.FromByte((byte)(vicRead((ushort)(0x0800 + i)) & 0b00001111)); + + var line = (i / 40); + var characterInLine = i % 40; + var indexLineOffset = (2560 * line) + (8 * characterInLine); + + for (int row = 0; row <= 7; row++) { + var charRow = vicRead((ushort)(getCharacterMemoryPointer() + (petsciiCode * 8) + row)); + + var indexRowOffset = indexLineOffset + (320 * row); + + for (int col = 0; col <= 7; col++) { + var indexPixelOffset = indexRowOffset + col; + + ScreenBufferPixels[indexPixelOffset] = charRow.IsBitSet(7 - (BitIndex)col) ? fgColor : bgColor; + } + + } + + } } + + public int getScreenMemoryPointer() { + var bit4to7 = (C64.Memory.Read(0xD018) >> 4) & 0b00001111; + + switch (bit4to7) { + case 0b0000: + return 0x0000; + case 0b0001: + return 0x0400; + case 0b0010: + return 0x0800; + case 0b0011: + return 0x0C00; + case 0b0100: + return 0x1000; + case 0b0101: + return 0x1400; + case 0b0110: + return 0x1800; + case 0b0111: + return 0x1C00; + case 0b1000: + return 0x2000; + case 0b1001: + return 0x2400; + case 0b1010: + return 0x2800; + case 0b1011: + return 0x2C00; + case 0b1100: + return 0x3000; + case 0b1101: + return 0x3400; + case 0b1110: + return 0x3800; + case 0b1111: + return 0x3C00; + + } + + throw new NotImplementedException(); + } + + public int getCharacterMemoryPointer() { + var bit1to3 = (C64.Memory.Read(0xD018) >> 1) & 0b00000111; + + switch (bit1to3) { + case 0b000: + return 0x0000; + case 0b001: + return 0x0800; + case 0b010: + return 0x1000; + case 0b011: + return 0x1800; + case 0b100: + return 0x2000; + case 0b101: + return 0x2800; + case 0b110: + return 0x3000; + case 0b111: + return 0x38000; + } + + throw new NotImplementedException(); + } + + public byte vicRead(ushort address) { + + var vicBankOffset = 0; + + switch (C64.Memory[0xDD00] & 0b00000011) { + case 0b00000011: + vicBankOffset = 0; + + if (address >= 0x1000 && address <= 0x1FFF) { + return C64.Memory._romCharacter[address - 0x1000]; + } + + break; + + case 0b00000010: + vicBankOffset = 0x4000; + break; + + case 0b00000001: + vicBankOffset = 0x8000; + + if (address >= 0x1000 && address <= 0x1FFF) { + return C64.Memory._romCharacter[address - 0x1000]; + } + + break; + + case 0b00000000: + vicBankOffset = 0xC000; + break; + } + + + return C64.Memory.Read(vicBankOffset + address); + } + } }