Updated graphics renderer to allow for pixel perfect resizing

This commit is contained in:
2026-04-24 14:47:10 +01:00
parent 2842af182f
commit dce4240842
2 changed files with 57 additions and 24 deletions

View File

@@ -28,7 +28,6 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
picScreen = new PictureBox();
menuStrip1 = new MenuStrip(); menuStrip1 = new MenuStrip();
fileToolStripMenuItem = new ToolStripMenuItem(); fileToolStripMenuItem = new ToolStripMenuItem();
openToolStripMenuItem = new ToolStripMenuItem(); openToolStripMenuItem = new ToolStripMenuItem();
@@ -44,20 +43,9 @@
resetToolStripMenuItem1 = new ToolStripMenuItem(); resetToolStripMenuItem1 = new ToolStripMenuItem();
optionsToolStripMenuItem = new ToolStripMenuItem(); optionsToolStripMenuItem = new ToolStripMenuItem();
HighSpeedToolStripMenuItem = new ToolStripMenuItem(); HighSpeedToolStripMenuItem = new ToolStripMenuItem();
((System.ComponentModel.ISupportInitialize)picScreen).BeginInit();
menuStrip1.SuspendLayout(); menuStrip1.SuspendLayout();
SuspendLayout(); SuspendLayout();
// //
// picScreen
//
picScreen.BackColor = Color.Black;
picScreen.Location = new Point(10, 26);
picScreen.Name = "picScreen";
picScreen.Size = new Size(720, 576);
picScreen.SizeMode = PictureBoxSizeMode.Zoom;
picScreen.TabIndex = 0;
picScreen.TabStop = false;
//
// menuStrip1 // menuStrip1
// //
menuStrip1.ImageScalingSize = new Size(24, 24); menuStrip1.ImageScalingSize = new Size(24, 24);
@@ -163,7 +151,7 @@
// HighSpeedToolStripMenuItem // HighSpeedToolStripMenuItem
// //
HighSpeedToolStripMenuItem.Name = "HighSpeedToolStripMenuItem"; HighSpeedToolStripMenuItem.Name = "HighSpeedToolStripMenuItem";
HighSpeedToolStripMenuItem.Size = new Size(224, 26); HighSpeedToolStripMenuItem.Size = new Size(170, 26);
HighSpeedToolStripMenuItem.Text = "High Speed"; HighSpeedToolStripMenuItem.Text = "High Speed";
HighSpeedToolStripMenuItem.Click += btnHighSpeedToggle_Click; HighSpeedToolStripMenuItem.Click += btnHighSpeedToggle_Click;
// //
@@ -172,13 +160,11 @@
AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(738, 608); ClientSize = new Size(738, 608);
Controls.Add(picScreen);
Controls.Add(menuStrip1); Controls.Add(menuStrip1);
KeyPreview = true; KeyPreview = true;
MainMenuStrip = menuStrip1; MainMenuStrip = menuStrip1;
Name = "Form1"; Name = "Form1";
Text = "Parsons Sinclair ZX Spectrum 48K - 2026"; Text = "Parsons Sinclair ZX Spectrum 48K - 2026";
((System.ComponentModel.ISupportInitialize)picScreen).EndInit();
menuStrip1.ResumeLayout(false); menuStrip1.ResumeLayout(false);
menuStrip1.PerformLayout(); menuStrip1.PerformLayout();
ResumeLayout(false); ResumeLayout(false);
@@ -186,8 +172,6 @@
} }
#endregion #endregion
private PictureBox picScreen;
private MenuStrip menuStrip1; private MenuStrip menuStrip1;
private ToolStripMenuItem fileToolStripMenuItem; private ToolStripMenuItem fileToolStripMenuItem;
private ToolStripMenuItem openToolStripMenuItem; private ToolStripMenuItem openToolStripMenuItem;

View File

@@ -18,6 +18,7 @@ namespace Desktop
private TapManager _tapManager = null!; private TapManager _tapManager = null!;
private DebuggerForm? _debugger = null; private DebuggerForm? _debugger = null;
private BeeperDevice _beeper = null!; private BeeperDevice _beeper = null!;
private Bitmap _screenBitmap = null!;
private string _baseTitle = ""; private string _baseTitle = "";
private bool _isRunning = false; private bool _isRunning = false;
private bool _isPaused = false; private bool _isPaused = false;
@@ -34,6 +35,8 @@ namespace Desktop
public Form1() public Form1()
{ {
InitializeComponent(); InitializeComponent();
this.DoubleBuffered = true;
this.ResizeRedraw = true;
InitializeEmulator(); InitializeEmulator();
} }
@@ -173,7 +176,10 @@ namespace Desktop
{ {
this.BeginInvoke((MethodInvoker)delegate this.BeginInvoke((MethodInvoker)delegate
{ {
UpdateScreenBitmap(); UpdateScreenBitmap(); // Your existing method that writes the ULA data to _screenBitmap
this.Invalidate(); // Tells Windows: "The bitmap changed, please run OnPaint()!"
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}"; this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}";
}); });
} }
@@ -181,10 +187,13 @@ namespace Desktop
} }
else else
{ {
this.Invoke((MethodInvoker)delegate this.BeginInvoke((MethodInvoker)delegate
{ {
UpdateScreenBitmap(); UpdateScreenBitmap(); // Your existing method that writes the ULA data to _screenBitmap
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1} - Tape Loaded: {tapeLoaded.ToString()}";
this.Invalidate(); // Tells Windows: "The bitmap changed, please run OnPaint()!"
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}";
}); });
} }
@@ -225,6 +234,40 @@ namespace Desktop
}); });
} }
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// If we don't have a screen bitmap yet, just paint the background black and exit
if (_screenBitmap == null)
{
e.Graphics.Clear(Color.Black);
return;
}
// 1. Calculate the scaling factor to fit the window while preserving aspect ratio
float scaleX = (float)this.ClientSize.Width / _screenBitmap.Width;
float scaleY = (float)this.ClientSize.Height / _screenBitmap.Height;
float scale = Math.Min(scaleX, scaleY); // Pick the smallest scale so it fits inside
int newWidth = (int)(_screenBitmap.Width * scale);
int newHeight = (int)(_screenBitmap.Height * scale);
// 2. Center the image horizontally and vertically (Pillarboxing/Letterboxing)
int posX = (this.ClientSize.Width - newWidth) / 2;
int posY = (this.ClientSize.Height - newHeight) / 2;
// 3. RETRO MAGIC: Force Nearest-Neighbor interpolation for razor-sharp pixels!
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
// PixelOffsetMode.Half is a GDI+ trick required to make NearestNeighbor perfectly accurate
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
// 4. Paint the black borders (if any) and draw the screen
e.Graphics.Clear(Color.Black);
e.Graphics.DrawImage(_screenBitmap, posX, posY, newWidth, newHeight);
}
private void HandleInstantTapeLoad() private void HandleInstantTapeLoad()
{ {
byte[] block = _tapManager.GetNextBlock(); // Your original Queue.Dequeue() method byte[] block = _tapManager.GetNextBlock(); // Your original Queue.Dequeue() method
@@ -271,7 +314,7 @@ namespace Desktop
_cpu.SP++; _cpu.SP++;
_cpu.PC = (ushort)((pcHigh << 8) | pcLow); _cpu.PC = (ushort)((pcHigh << 8) | pcLow);
} }
private void UpdateScreenBitmap() private void UpdateScreenBitmap()
{ {
@@ -286,8 +329,14 @@ namespace Desktop
Marshal.Copy(_ula.FrameBuffer, 0, bmpData.Scan0, _ula.FrameBuffer.Length); Marshal.Copy(_ula.FrameBuffer, 0, bmpData.Scan0, _ula.FrameBuffer.Length);
bmp.UnlockBits(bmpData); bmp.UnlockBits(bmpData);
if (picScreen.Image != null) picScreen.Image.Dispose(); // Dispose of the old frame to prevent massive RAM leaks!
picScreen.Image = bmp; if (_screenBitmap != null)
{
_screenBitmap.Dispose();
}
// Save the new frame so OnPaint() can draw it
_screenBitmap = bmp;
} }
private void loadTAPToolStripMenuItem_Click(object sender, EventArgs e) private void loadTAPToolStripMenuItem_Click(object sender, EventArgs e)