From 34808d7839786391ceb8c0324c0a912a2d5cc0dc Mon Sep 17 00:00:00 2001 From: ogamespec Date: Tue, 21 Jun 2022 20:43:17 +0300 Subject: [PATCH 1/3] OAMBufferBit_RGB --- BreaksPPU/PPUSim/cram.cpp | 2 +- BreaksPPU/PPUSim/oam.cpp | 41 ++++++++++++++++++++++++++++++++++++++- BreaksPPU/PPUSim/oam.h | 15 +++++++++++++- BreaksPPU/PPUSim/ppu.h | 1 + 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/BreaksPPU/PPUSim/cram.cpp b/BreaksPPU/PPUSim/cram.cpp index 7f2d0391..c652e194 100644 --- a/BreaksPPU/PPUSim/cram.cpp +++ b/BreaksPPU/PPUSim/cram.cpp @@ -301,7 +301,7 @@ namespace PPUSim /// /// The different version for RGB PPU (the one studied) is that CRAM is Write-Only. /// - void CBBit_RGB::sim(size_t bit_num, BaseLogic::TriState* cell, BaseLogic::TriState n_OE) + void CBBit_RGB::sim(size_t bit_num, TriState* cell, TriState n_OE) { TriState PCLK = ppu->wire.PCLK; TriState n_PCLK = ppu->wire.n_PCLK; diff --git a/BreaksPPU/PPUSim/oam.cpp b/BreaksPPU/PPUSim/oam.cpp index bf377f31..e18b59eb 100644 --- a/BreaksPPU/PPUSim/oam.cpp +++ b/BreaksPPU/PPUSim/oam.cpp @@ -12,7 +12,18 @@ namespace PPUSim for (size_t n = 0; n < 8; n++) { - ob[n] = new OAMBufferBit(ppu); + switch (ppu->rev) + { + // TBD: Check how things are in other RGB PPUs. + + case Revision::RP2C04_0003: + ob[n] = new OAMBufferBit_RGB(ppu); + break; + + default: + ob[n] = new OAMBufferBit(ppu); + break; + } } for (size_t n = 0; n < num_lanes; n++) @@ -423,4 +434,32 @@ namespace PPUSim { return decay_behav; } + + /// + /// The different version for RGB PPU (the one studied) is that OAM is Write-Only. + /// + void OAMBufferBit_RGB::sim(OAMLane* lane, size_t column, size_t bit_num, TriState OB_OAM, TriState n_WE) + { + TriState PCLK = ppu->wire.PCLK; + TriState BLNK = ppu->fsm.BLNK; + TriState I_OAM2 = ppu->fsm.IOAM2; + + TriState FromOAM = TriState::Z; + if (lane != nullptr) + { + lane->sim(column, bit_num, FromOAM); + } + + Input_FF.set(MUX(PCLK, FromOAM, TriState::Zero)); + OB_FF.set(NOT(MUX(PCLK, NOR(I_OAM2, Input_FF.get()), NOT(OB_FF.get())))); + auto OBOut = OB_FF.get(); + ppu->wire.OB[bit_num] = OBOut; + out_latch.set(MUX(BLNK, MUX(OB_OAM, TriState::Z, OBOut), ppu->GetDBBit(bit_num)), TriState::One); + + if (lane != nullptr) + { + TriState val_out = MUX(NOT(n_WE), TriState::Z, out_latch.get()); + lane->sim(column, bit_num, val_out); + } + } } diff --git a/BreaksPPU/PPUSim/oam.h b/BreaksPPU/PPUSim/oam.h index 4a41edce..94af9647 100644 --- a/BreaksPPU/PPUSim/oam.h +++ b/BreaksPPU/PPUSim/oam.h @@ -66,6 +66,7 @@ namespace PPUSim class OAMBufferBit { + protected: PPU* ppu = nullptr; BaseLogic::FF Input_FF; @@ -77,12 +78,24 @@ namespace PPUSim OAMBufferBit(PPU* parent) { ppu = parent; } ~OAMBufferBit() {} - void sim(OAMLane *lane, size_t column, size_t bit_num, BaseLogic::TriState OB_OAM, BaseLogic::TriState n_WE); + virtual void sim(OAMLane *lane, size_t column, size_t bit_num, BaseLogic::TriState OB_OAM, BaseLogic::TriState n_WE); BaseLogic::TriState get(); void set(BaseLogic::TriState val); }; + /// + /// Special version for RGB PPU. + /// + class OAMBufferBit_RGB : public OAMBufferBit + { + public: + OAMBufferBit_RGB(PPU* parent) : OAMBufferBit(parent) {} + ~OAMBufferBit_RGB() {} + + virtual void sim(OAMLane* lane, size_t column, size_t bit_num, BaseLogic::TriState OB_OAM, BaseLogic::TriState n_WE); + }; + class OAM { friend PPUSimUnitTest::UnitTest; diff --git a/BreaksPPU/PPUSim/ppu.h b/BreaksPPU/PPUSim/ppu.h index befd5072..e7658d5f 100644 --- a/BreaksPPU/PPUSim/ppu.h +++ b/BreaksPPU/PPUSim/ppu.h @@ -187,6 +187,7 @@ namespace PPUSim friend Mux; friend OAMEval; friend OAMBufferBit; + friend OAMBufferBit_RGB; friend OAMCell; friend OAM; friend FIFOLane; From 2cee5e6ae105909e09d4c305b40c5c593e954772 Mon Sep 17 00:00:00 2001 From: ogamespec Date: Tue, 21 Jun 2022 21:01:11 +0300 Subject: [PATCH 2/3] Fixed index overflow in VideoProcessing --- BreaksPPU/PPUPlayer/VideoProcessing.cs | 14 ++++++++++++++ BreaksPPU/PPUSim/video_out.cpp | 10 +++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/BreaksPPU/PPUPlayer/VideoProcessing.cs b/BreaksPPU/PPUPlayer/VideoProcessing.cs index 2438cb0f..93ec35b0 100644 --- a/BreaksPPU/PPUPlayer/VideoProcessing.cs +++ b/BreaksPPU/PPUPlayer/VideoProcessing.cs @@ -126,6 +126,12 @@ void ProcessSample(PPUPlayerInterop.VideoOutSample sample) SyncFound = false; WritePtr = 0; } + + if (WritePtr >= 2 * SamplesPerScan) + { + SyncFound = false; + WritePtr = 0; + } } void ProcessScanRAW() @@ -173,6 +179,14 @@ void ProcessScanRGB() while (ScanBuffer[ReadPtr].nSYNC == 0) { ReadPtr++; + + if (ReadPtr >= ScanBuffer.Length) + break; + } + + if (ReadPtr >= ScanBuffer.Length) + { + return; } ReadPtr += ppu_features.BackPorchSize * ppu_features.SamplesPerPCLK; diff --git a/BreaksPPU/PPUSim/video_out.cpp b/BreaksPPU/PPUSim/video_out.cpp index 356ae60f..5395e53b 100644 --- a/BreaksPPU/PPUSim/video_out.cpp +++ b/BreaksPPU/PPUSim/video_out.cpp @@ -682,21 +682,25 @@ namespace PPUSim vout.RGB.GREEN = 0; vout.RGB.BLUE = 0; + // By multiplying by 35 we are just a little bit short of 255 for a component of 7, but that will do. + + const size_t Current_summation_factor = 35; + for (size_t n = 0; n < 8; n++) { if (rgb_red_latch[n].get() == TriState::One) { - vout.RGB.RED = n * 35; + vout.RGB.RED = (uint8_t)(n * Current_summation_factor); } if (rgb_green_latch[n].get() == TriState::One) { - vout.RGB.GREEN = n * 35; + vout.RGB.GREEN = (uint8_t)(n * Current_summation_factor); } if (rgb_blue_latch[n].get() == TriState::One) { - vout.RGB.BLUE = n * 35; + vout.RGB.BLUE = (uint8_t)(n * Current_summation_factor); } } From 8dd37304e4b49b304eb8de48bff18e1b700791be Mon Sep 17 00:00:00 2001 From: ogamespec Date: Tue, 21 Jun 2022 21:57:17 +0300 Subject: [PATCH 3/3] FormComponentViewer --- .../PPUPlayer/FormComponentViewer.Designer.cs | 104 +++++++++ BreaksPPU/PPUPlayer/FormComponentViewer.cs | 217 ++++++++++++++++++ BreaksPPU/PPUPlayer/FormComponentViewer.resx | 60 +++++ BreaksPPU/PPUPlayer/FormMain.Designer.cs | 12 +- BreaksPPU/PPUPlayer/FormMain.cs | 17 ++ BreaksPPU/PPUPlayer/PPUPlayer.csproj.user | 3 + 6 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 BreaksPPU/PPUPlayer/FormComponentViewer.Designer.cs create mode 100644 BreaksPPU/PPUPlayer/FormComponentViewer.cs create mode 100644 BreaksPPU/PPUPlayer/FormComponentViewer.resx diff --git a/BreaksPPU/PPUPlayer/FormComponentViewer.Designer.cs b/BreaksPPU/PPUPlayer/FormComponentViewer.Designer.cs new file mode 100644 index 00000000..93cbfed5 --- /dev/null +++ b/BreaksPPU/PPUPlayer/FormComponentViewer.Designer.cs @@ -0,0 +1,104 @@ +namespace PPUPlayer +{ + partial class FormComponentViewer + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.comboBox1); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.pictureBox1); + this.splitContainer1.Size = new System.Drawing.Size(800, 450); + this.splitContainer1.SplitterDistance = 32; + this.splitContainer1.TabIndex = 2; + // + // comboBox1 + // + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Location = new System.Drawing.Point(12, 6); + this.comboBox1.Name = "comboBox1"; + this.comboBox1.Size = new System.Drawing.Size(121, 23); + this.comboBox1.TabIndex = 0; + // + // pictureBox1 + // + this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox1.Location = new System.Drawing.Point(0, 0); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(800, 414); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // FormComponentViewer + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.splitContainer1); + this.KeyPreview = true; + this.Name = "FormComponentViewer"; + this.Text = "Component Viewer"; + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.FormComponentViewer_KeyDown); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.PictureBox pictureBox1; + } +} \ No newline at end of file diff --git a/BreaksPPU/PPUPlayer/FormComponentViewer.cs b/BreaksPPU/PPUPlayer/FormComponentViewer.cs new file mode 100644 index 00000000..e90e1b3a --- /dev/null +++ b/BreaksPPU/PPUPlayer/FormComponentViewer.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +using System.IO; +using System.Runtime.InteropServices; + +namespace PPUPlayer +{ + public partial class FormComponentViewer : Form + { + PPUPlayerInterop.VideoSignalFeatures ppu_features; + int SamplesPerScan; + + PPUPlayerInterop.VideoOutSample[]? ScanBuffer; + int WritePtr = 0; + bool SyncFound = false; + int SyncPos = -1; + + Color[] field = new Color[256 * 240]; + int CurrentScan = 0; + List fields = new List(); + + public FormComponentViewer(string filename) + { + InitializeComponent(); + + // Load the Logisim dump + + string text = File.ReadAllText(filename); + UInt32[] dump = LogisimHEXConv.HEXToUIntArray(text); + + // Process the dump field by field and fill the ComboBox and PictureView. + + ResetVisualize(); + + comboBox1.Items.Clear(); + + for (int i = 0; i < dump.Length; i++) + { + PPUPlayerInterop.VideoOutSample sample = new(); + + sample.RED = (byte)(dump[i] & 0xff); + sample.GREEN = (byte)((dump[i] >> 8) & 0xff); + sample.BLUE = (byte)((dump[i] >> 16) & 0xff); + sample.nSYNC = (byte)((dump[i] >> 24) & 0xff); + + ProcessSample(sample); + } + + if (fields.Count != 0) + { + ShowField(0); + } + else + { + AddField(); + Console.WriteLine("Added partial field."); + ShowField(0); + } + } + + void ResetVisualize() + { + PPUPlayerInterop.GetSignalFeatures(out ppu_features); + + SamplesPerScan = ppu_features.PixelsPerScan * ppu_features.SamplesPerPCLK; + ScanBuffer = new PPUPlayerInterop.VideoOutSample[2 * SamplesPerScan]; + WritePtr = 0; + + SyncFound = false; + SyncPos = -1; + CurrentScan = 0; + } + + void ProcessSample(PPUPlayerInterop.VideoOutSample sample) + { + ScanBuffer[WritePtr] = sample; + + // Check that the sample is HSync. + + bool sync = false; + sync = sample.nSYNC == 0; + + // If the beginning of HSync is found - remember its offset in the input buffer. + + if (sync && !SyncFound) + { + SyncPos = WritePtr; + SyncFound = true; + } + + // Advance write pointer + + WritePtr++; + + // If HSync is found and the number of samples is more than enough, process the scan. + + if (SyncFound && (SyncPos + WritePtr) >= SamplesPerScan) + { + ProcessScanRGB(); + + SyncFound = false; + WritePtr = 0; + } + } + + void ProcessScanRGB() + { + int ReadPtr = SyncPos; + + // Skip HSync and Back Porch + + while (ScanBuffer[ReadPtr].nSYNC == 0) + { + ReadPtr++; + + if (ReadPtr >= ScanBuffer.Length) + break; + } + + if (ReadPtr >= ScanBuffer.Length) + { + return; + } + + ReadPtr += ppu_features.BackPorchSize * ppu_features.SamplesPerPCLK; + + // Output the visible part of the signal + + for (int i = 0; i < 256; i++) + { + if (CurrentScan < 240) + { + int red = 0, green = 0, blue = 0; + + for (int n = 0; n < ppu_features.SamplesPerPCLK; n++) + { + var sample = ScanBuffer[ReadPtr]; + red += sample.RED; + green += sample.GREEN; + blue += sample.BLUE; + ReadPtr++; + } + + byte r = (byte)(red / ppu_features.SamplesPerPCLK); + byte g = (byte)(green / ppu_features.SamplesPerPCLK); + byte b = (byte)(blue / ppu_features.SamplesPerPCLK); + + field[CurrentScan * 256 + i] = Color.FromArgb(r, g, b); + } + } + + CurrentScan++; + if (CurrentScan >= ppu_features.ScansPerField) + { + AddField(); + Console.WriteLine("Added Field"); + CurrentScan = 0; + } + } + + void AddField() + { + int w = 256; + int h = 240; + + comboBox1.Items.Add(fields.Count.ToString()); + + Bitmap? field_pic = new(w, h, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + Bitmap pic = field_pic; + Graphics gr = Graphics.FromImage(pic); + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + Color p = field[w * y + x]; + gr.FillRectangle(new SolidBrush(p), x, y, 1, 1); + } + } + + fields.Add(field_pic); + gr.Dispose(); + } + + void ShowField(int n) + { + if (n < fields.Count) + { + pictureBox1.Image = fields[n]; + Console.WriteLine("Show Field: " + n.ToString()); + } + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + int n = comboBox1.SelectedIndex; + ShowField(n); + } + + private void FormComponentViewer_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Escape) + { + Close(); + } + } + } +} diff --git a/BreaksPPU/PPUPlayer/FormComponentViewer.resx b/BreaksPPU/PPUPlayer/FormComponentViewer.resx new file mode 100644 index 00000000..f298a7be --- /dev/null +++ b/BreaksPPU/PPUPlayer/FormComponentViewer.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BreaksPPU/PPUPlayer/FormMain.Designer.cs b/BreaksPPU/PPUPlayer/FormMain.Designer.cs index ab4e5c44..c9d092e3 100644 --- a/BreaksPPU/PPUPlayer/FormMain.Designer.cs +++ b/BreaksPPU/PPUPlayer/FormMain.Designer.cs @@ -113,6 +113,7 @@ private void InitializeComponent() this.saveFileDialogVCD = new System.Windows.Forms.SaveFileDialog(); this.openFileDialogPPUDump = new System.Windows.Forms.OpenFileDialog(); this.openFileDialogHEX = new System.Windows.Forms.OpenFileDialog(); + this.loadRGBDumpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); this.toolStrip1.SuspendLayout(); @@ -352,7 +353,8 @@ private void InitializeComponent() this.miscToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.pPUColorSpaceToolStripMenuItem, this.loadRAWDumpToolStripMenuItem, - this.loadCompositeDumpToolStripMenuItem}); + this.loadCompositeDumpToolStripMenuItem, + this.loadRGBDumpToolStripMenuItem}); this.miscToolStripMenuItem.Name = "miscToolStripMenuItem"; this.miscToolStripMenuItem.Size = new System.Drawing.Size(44, 20); this.miscToolStripMenuItem.Text = "Misc"; @@ -908,6 +910,13 @@ private void InitializeComponent() this.openFileDialogHEX.DefaultExt = "hex"; this.openFileDialogHEX.Filter = "Logisim Hex files|*.hex|All files|*.*"; // + // loadRGBDumpToolStripMenuItem + // + this.loadRGBDumpToolStripMenuItem.Name = "loadRGBDumpToolStripMenuItem"; + this.loadRGBDumpToolStripMenuItem.Size = new System.Drawing.Size(206, 22); + this.loadRGBDumpToolStripMenuItem.Text = "Load RGB Dump..."; + this.loadRGBDumpToolStripMenuItem.Click += new System.EventHandler(this.loadRGBDumpToolStripMenuItem_Click); + // // FormMain // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -1047,5 +1056,6 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem loadRAWDumpToolStripMenuItem; private System.Windows.Forms.OpenFileDialog openFileDialogHEX; private System.Windows.Forms.ToolStripMenuItem loadCompositeDumpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem loadRGBDumpToolStripMenuItem; } } diff --git a/BreaksPPU/PPUPlayer/FormMain.cs b/BreaksPPU/PPUPlayer/FormMain.cs index e62ae74a..3293b9f1 100644 --- a/BreaksPPU/PPUPlayer/FormMain.cs +++ b/BreaksPPU/PPUPlayer/FormMain.cs @@ -802,5 +802,22 @@ private void loadCompositeDumpToolStripMenuItem_Click(object sender, EventArgs e formCompositeViewer.Show(); } } + + private void loadRGBDumpToolStripMenuItem_Click(object sender, EventArgs e) + { + if (!SimulationStarted) + { + MessageBox.Show( + "You need to run the simulation so that the PPU instance is active.", + "Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + if (openFileDialogHEX.ShowDialog() == DialogResult.OK) + { + FormComponentViewer formComponentViewer = new FormComponentViewer(openFileDialogHEX.FileName); + formComponentViewer.Show(); + } + } } } diff --git a/BreaksPPU/PPUPlayer/PPUPlayer.csproj.user b/BreaksPPU/PPUPlayer/PPUPlayer.csproj.user index 19134aad..a3cb97ac 100644 --- a/BreaksPPU/PPUPlayer/PPUPlayer.csproj.user +++ b/BreaksPPU/PPUPlayer/PPUPlayer.csproj.user @@ -4,6 +4,9 @@ Form + + Form + Form