Updated VDP to handle GameGear. Added Turbo mode. Versio 1.0 me thinks!

This commit is contained in:
2026-05-18 14:57:19 +01:00
parent be43019fb0
commit 66f18d0510
5 changed files with 213 additions and 113 deletions

View File

@@ -41,9 +41,11 @@
resetToolStripMenuItem = new ToolStripMenuItem();
saveStateToolStripMenuItem = new ToolStripMenuItem();
loadStateToolStripMenuItem = new ToolStripMenuItem();
turboModeToolStripMenuItem = new ToolStripMenuItem();
helpToolStripMenuItem = new ToolStripMenuItem();
aboutToolStripMenuItem = new ToolStripMenuItem();
pALRegionToolStripMenuItem = new ToolStripMenuItem();
masterSystemToolStripMenuItem = new ToolStripMenuItem();
gameGearToolStripMenuItem = new ToolStripMenuItem();
menuStrip1.SuspendLayout();
SuspendLayout();
//
@@ -74,15 +76,15 @@
//
// includedToolStripMenuItem
//
includedToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { masterSystemToolStripMenuItem, gameGearToolStripMenuItem });
includedToolStripMenuItem.Name = "includedToolStripMenuItem";
includedToolStripMenuItem.Size = new Size(178, 26);
includedToolStripMenuItem.Size = new Size(224, 26);
includedToolStripMenuItem.Text = "Included";
includedToolStripMenuItem.Click += includedToolStripMenuItem_Click;
//
// selectROMToolStripMenuItem1
//
selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1";
selectROMToolStripMenuItem1.Size = new Size(178, 26);
selectROMToolStripMenuItem1.Size = new Size(224, 26);
selectROMToolStripMenuItem1.Text = "Select ROM...";
selectROMToolStripMenuItem1.Click += selectROMToolStripMenuItem_Click;
//
@@ -116,7 +118,7 @@
//
// machineToolStripMenuItem
//
machineToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resetToolStripMenuItem, saveStateToolStripMenuItem, loadStateToolStripMenuItem, pALRegionToolStripMenuItem });
machineToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resetToolStripMenuItem, saveStateToolStripMenuItem, loadStateToolStripMenuItem, turboModeToolStripMenuItem });
machineToolStripMenuItem.Name = "machineToolStripMenuItem";
machineToolStripMenuItem.Size = new Size(79, 24);
machineToolStripMenuItem.Text = "Machine";
@@ -142,6 +144,13 @@
loadStateToolStripMenuItem.Text = "Load State";
loadStateToolStripMenuItem.Click += loadStateToolStripMenuItem_Click;
//
// turboModeToolStripMenuItem
//
turboModeToolStripMenuItem.Name = "turboModeToolStripMenuItem";
turboModeToolStripMenuItem.Size = new Size(224, 26);
turboModeToolStripMenuItem.Text = "Turbo Mode";
turboModeToolStripMenuItem.Click += turboModeToolStripMenuItem_Click;
//
// helpToolStripMenuItem
//
helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { aboutToolStripMenuItem });
@@ -155,15 +164,17 @@
aboutToolStripMenuItem.Size = new Size(133, 26);
aboutToolStripMenuItem.Text = "About";
//
// pALRegionToolStripMenuItem
// masterSystemToolStripMenuItem
//
pALRegionToolStripMenuItem.Checked = true;
pALRegionToolStripMenuItem.CheckOnClick = true;
pALRegionToolStripMenuItem.CheckState = CheckState.Checked;
pALRegionToolStripMenuItem.Name = "pALRegionToolStripMenuItem";
pALRegionToolStripMenuItem.Size = new Size(224, 26);
pALRegionToolStripMenuItem.Text = "PAL Region?";
pALRegionToolStripMenuItem.Click += pALRegionToolStripMenuItem_Click;
masterSystemToolStripMenuItem.Name = "masterSystemToolStripMenuItem";
masterSystemToolStripMenuItem.Size = new Size(224, 26);
masterSystemToolStripMenuItem.Text = "Master System";
//
// gameGearToolStripMenuItem
//
gameGearToolStripMenuItem.Name = "gameGearToolStripMenuItem";
gameGearToolStripMenuItem.Size = new Size(224, 26);
gameGearToolStripMenuItem.Text = "Game Gear";
//
// ParsonsForm1
//
@@ -197,6 +208,8 @@
private ToolStripMenuItem vRAMViewerToolStripMenuItem;
private ToolStripMenuItem saveStateToolStripMenuItem;
private ToolStripMenuItem loadStateToolStripMenuItem;
private ToolStripMenuItem pALRegionToolStripMenuItem;
private ToolStripMenuItem turboModeToolStripMenuItem;
private ToolStripMenuItem masterSystemToolStripMenuItem;
private ToolStripMenuItem gameGearToolStripMenuItem;
}
}

View File

@@ -18,14 +18,14 @@ namespace Desktop
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
private NAudioPlayer _audioPlayer;
private Task _emulatorTask;
private double TargetFrameTime = 16.667f; //NTSC
//private double TargetFrameTime = 20; //PAL
public int TotalFrameCount = 0;
public double FrameTime { get; private set; } = 0;
public double FramesPerSecond { get; private set; } = 0;
private string _currentRomName = "No ROM";
private string _currentRomPath = "";
private Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();
private Stopwatch _stopwatch = new Stopwatch();
private string _romType = "";
private bool isTurboMode = false;
public bool IsRunning { get; private set; } = false;
public ushort? Breakpoint
@@ -37,21 +37,17 @@ namespace Desktop
public ParsonsForm1()
{
InitializeComponent();
// These are perfectly safe for the Visual Studio Designer!
this.KeyPreview = true;
this.KeyDown += Form1_KeyDown;
this.KeyUp += Form1_KeyUp;
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
// The Designer ignores this completely, but the compiled game runs it instantly!
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Text = $"Parsons Master System - {_currentRomName}";
this.Text = $"Parsons Master System 2026 - {_currentRomName}";
this.BackColor = Color.Black;
_machine = new SmsMachine();
@@ -70,6 +66,7 @@ namespace Desktop
this.Invalidate();
TotalFrameCount++;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
@@ -80,44 +77,90 @@ namespace Desktop
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
// 2. Calculate the actual usable window space
int topOffset = menuStrip1.Height;
int availableWidth = this.ClientSize.Width;
int availableHeight = this.ClientSize.Height - topOffset;
// 3. Calculate the maximum scale factor that fits perfectly
float scaleX = (float)availableWidth / 256f;
float scaleY = (float)availableHeight / 192f;
// 2. THE LENS LENS: Determine what part of the VDP buffer we actually want to show!
// (Safely check if the machine/video processor exists yet)
bool isGG = _machine?.VideoProcessor?.IsGameGear ?? false;
int sourceWidth = isGG ? 160 : 256;
int sourceHeight = isGG ? 144 : 192;
int sourceX = isGG ? 48 : 0; // Skip 48 pixels in!
int sourceY = isGG ? 24 : 0; // Skip 24 pixels down!
// Create a rectangle defining the specific pixels to extract from the raw frame buffer
Rectangle sourceRect = new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight);
// 3. Calculate the maximum scale factor for the cropped image
float scaleX = (float)availableWidth / sourceWidth;
float scaleY = (float)availableHeight / sourceHeight;
// Pick the smaller scale so the image never bleeds off the edge
float scale = Math.Min(scaleX, scaleY);
// 4. Calculate the new physical pixel dimensions
int newWidth = (int)(256 * scale);
int newHeight = (int)(192 * scale);
int newWidth = (int)(sourceWidth * scale);
int newHeight = (int)(sourceHeight * scale);
// 5. Center the image (Letterboxing / Pillarboxing)
int offsetX = (availableWidth - newWidth) / 2;
int offsetY = topOffset + ((availableHeight - newHeight) / 2);
Rectangle renderArea = new Rectangle(offsetX, offsetY, newWidth, newHeight);
// Create a rectangle defining exactly where on the Windows form to draw the extracted pixels
Rectangle destRect = new Rectangle(offsetX, offsetY, newWidth, newHeight);
// 6. Draw the scaled image
e.Graphics.DrawImage(_screenBitmap, renderArea);
// 6. Blast the perfectly cropped and scaled bitmap onto the screen!
e.Graphics.DrawImage(_screenBitmap, destRect, sourceRect, GraphicsUnit.Pixel);
}
}
//protected override void OnPaint(PaintEventArgs e)
//{
// base.OnPaint(e);
// if (_screenBitmap != null && menuStrip1 != null)
// {
// // 1. Maintain perfect, chunky retro pixels
// e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
// e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
// // 2. Calculate the actual usable window space
// int topOffset = menuStrip1.Height;
// int availableWidth = this.ClientSize.Width;
// int availableHeight = this.ClientSize.Height - topOffset;
// // 3. Calculate the maximum scale factor that fits perfectly
// float scaleX = (float)availableWidth / 256f;
// float scaleY = (float)availableHeight / 192f;
// // Pick the smaller scale so the image never bleeds off the edge
// float scale = Math.Min(scaleX, scaleY);
// // 4. Calculate the new physical pixel dimensions
// int newWidth = (int)(256 * scale);
// int newHeight = (int)(192 * scale);
// // 5. Center the image (Letterboxing / Pillarboxing)
// int offsetX = (availableWidth - newWidth) / 2;
// int offsetY = topOffset + ((availableHeight - newHeight) / 2);
// Rectangle renderArea = new Rectangle(offsetX, offsetY, newWidth, newHeight);
// // 6. Draw the scaled image
// e.Graphics.DrawImage(_screenBitmap, renderArea);
// }
//}
public void StartEmulator()
{
if (IsRunning) return;
IsRunning = true;
TotalFrameCount = 0;
double targetFps = _machine.VideoProcessor.IsPalRegion ? 50.0 : 59.92274;
double TargetFrameTime = 1000.0 / targetFps;
_emulatorTask = Task.Run(() =>
{
double targetFps = 59.94;
double TargetFrameTime = 1000.0 / targetFps;
_stopwatch.Restart();
double nextFrameTargetTime = _stopwatch.Elapsed.TotalMilliseconds + TargetFrameTime;
@@ -126,7 +169,13 @@ namespace Desktop
while (IsRunning)
{
// Mark exactly when the emulator starts thinking
//targetFps = isTurboMode ? 1000 : 59.94;
//TargetFrameTime = 1000.0 / targetFps;
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{
targetFps = isTurboMode ? 1000 : 59.94;
TargetFrameTime = 1000.0 / targetFps;
});
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
// --- POLL PHYSICAL CONTROLLER ---
@@ -187,7 +236,7 @@ namespace Desktop
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{
this.Text = $"Parsons Master System 2026 - {_currentRomName} [FPS/FT: {FramesPerSecond:F0}/{FrameTime:F1}]";
this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName} [FPS: {FramesPerSecond:F0}]";
});
}
@@ -212,6 +261,18 @@ namespace Desktop
{
await _emulatorTask;
}
_romType = Path.GetExtension(filePath);
if (_romType == ".sms")
{
_machine.VideoProcessor.IsGameGear = false;
}
else if (_romType == ".gg")
{
_machine.VideoProcessor.IsGameGear = true;
}
else
throw new Exception("Incorrect ROM Type!");
_romType = _machine.VideoProcessor.IsGameGear ? "GG" : "SMS";
// 1. SAVE THE PREVIOUS GAME!
SaveCurrentSram();
@@ -225,7 +286,7 @@ namespace Desktop
// 4. Update the path tracking
_currentRomPath = filePath;
_currentRomName = Path.GetFileNameWithoutExtension(filePath);
this.Text = $"Parsons Master System - {_currentRomName}";
//this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}";
// 5. LOAD THE NEW SAVE DATA FROM THE EXE FOLDER!
string savPath = GetSaveFilePath();
@@ -263,13 +324,11 @@ namespace Desktop
private void PopulateIncludedRomsMenu()
{
// The folder you used for Golden Axe Warrior
string romsDirectory = @"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\";
if (Directory.Exists(romsDirectory))
{
string[] romFiles = Directory.GetFiles(romsDirectory, "*.sms");
foreach (string file in romFiles)
{
// Create a new dropdown item for each .sms file it finds
@@ -280,8 +339,20 @@ namespace Desktop
romMenuItem.Click += (sender, e) => LoadRomAndStart(file);
// Add it to the "Included" submenu (make sure the name matches your designer element!)
includedToolStripMenuItem.DropDownItems.Add(romMenuItem);
masterSystemToolStripMenuItem.DropDownItems.Add(romMenuItem);
}
romFiles = Directory.GetFiles(romsDirectory, "*.gg");
foreach (string file in romFiles)
{
// Create a new dropdown item for each .gg file it finds
string romName = Path.GetFileNameWithoutExtension(file);
ToolStripMenuItem romMenuItem = new ToolStripMenuItem(romName);
// When clicked, pass the file path to the helper method
romMenuItem.Click += (sender, e) => LoadRomAndStart(file);
gameGearToolStripMenuItem.DropDownItems.Add(romMenuItem);
}
}
}
@@ -290,7 +361,7 @@ namespace Desktop
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.Title = "Select Master System ROM";
ofd.Filter = "SMS ROMs (*.sms)|*.sms|All Files (*.*)|*.*";
ofd.Filter = "SMS ROMs (*.sms)|*.sms|GG ROMs (*.gg)|*.gg|All Files (*.*)|*.*";
if (ofd.ShowDialog() == DialogResult.OK)
{
@@ -324,11 +395,7 @@ namespace Desktop
_vramViewer.Show();
}
private void includedToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
@@ -412,14 +479,10 @@ namespace Desktop
StartEmulator();
}
private void pALRegionToolStripMenuItem_Click(object sender, EventArgs e)
private void turboModeToolStripMenuItem_Click(object sender, EventArgs e)
{
if (pALRegionToolStripMenuItem.Checked)
_machine.VideoProcessor.IsPalRegion = true;
else _machine.VideoProcessor.IsPalRegion= false;
pALRegionToolStripMenuItem.Checked = !pALRegionToolStripMenuItem.Checked;
turboModeToolStripMenuItem.Checked = !turboModeToolStripMenuItem.Checked;
isTurboMode = turboModeToolStripMenuItem.Checked ? true : false;
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Desktop
this.Text = "VRAM Viewer (512 Tiles)";
// 256x128 native resolution, scaled up by 2 for visibility!
this.ClientSize = new Size(1024, 512);
this.ClientSize = new Size(512, 256);
this.DoubleBuffered = true;
this.FormBorderStyle = FormBorderStyle.FixedToolWindow;
@@ -45,7 +45,6 @@ namespace Desktop
int tileX = (tile % 32) * 8;
int tileY = (tile / 32) * 8;
int tileAddress = tile * 32;
// Decode the 8x8 pixels
for (int y = 0; y < 8; y++)
{
@@ -57,19 +56,19 @@ namespace Desktop
for (int x = 0; x < 8; x++)
{
int shift = 7 - x;
int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
// Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly
byte smsColor = _vdp.CRAM[16 + colorIndex];
int r = (smsColor & 0x03) * 85;
int g = ((smsColor >> 2) & 0x03) * 85;
int b = ((smsColor >> 4) & 0x03) * 85;
int colour = _vdp.GetRgbColour(16, colourIndex);
//int r = (smsColor & 0x03) * 85;
//int g = ((smsColor >> 2) & 0x03) * 85;
//int b = ((smsColor >> 4) & 0x03) * 85;
// Background color (Index 0) is technically transparent for sprites, so we render it as magenta
if (colorIndex == 0) { r = 255; g = 0; b = 255; }
//if (colourIndex == 0) { int r = 255; int g = 0; int b = 255; }
_pixelData[((tileY + y) * 256) + (tileX + x)] = (255 << 24) | (r << 16) | (g << 8) | b;
_pixelData[((tileY + y) * 256) + (tileX + x)] = colourIndex == 0 ? (255 << 24) | (255 << 16) | (0 << 8) | 255 : colour;
}
}
}