Added double buffering ish.

This commit is contained in:
2026-05-21 17:25:57 +01:00
parent 9316a16ab9
commit cca1abf0be
3 changed files with 202 additions and 166 deletions

View File

@@ -63,11 +63,17 @@ namespace Desktop
PopulateIncludedRomsMenu();
}
private void DrawScreen()
private void DrawScreen(int[] safeFrame)
{
// Rapidly copy our VDP FrameBuffer into the Windows Bitmap
// Rapidly the Frame into the Windows Bitmap
var data = _screenBitmap.LockBits(new Rectangle(0, 0, 256, 192), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(_machine.VideoProcessor.FrameBuffer, 0, data.Scan0, _machine.VideoProcessor.FrameBuffer.Length);
//Frame buffer draw
Marshal.Copy(safeFrame, 0, data.Scan0, safeFrame.Length);
//Direct VDP draw
//Marshal.Copy(_machine.VideoProcessor.FrameBuffer, 0, data.Scan0, _machine.VideoProcessor.FrameBuffer.Length);
_screenBitmap.UnlockBits(data);
this.Invalidate();
TotalFrameCount++;
@@ -137,88 +143,108 @@ namespace Desktop
double nextFrameTargetTime = _stopwatch.Elapsed.TotalMilliseconds + TargetFrameTime;
double lastFpsUpdate = _stopwatch.Elapsed.TotalMilliseconds;
int framesThisSecond = 0;
while (IsRunning)
try
{
//targetFps = isTurboMode ? 1000 : 59.94;
//TargetFrameTime = 1000.0 / targetFps;
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
while (IsRunning)
{
targetFps = isTurboMode ? 1000 : 59.94;
TargetFrameTime = 1000.0 / targetFps;
});
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
// --- POLL PHYSICAL CONTROLLER ---
if (XInput.XInputGetState(0, out XInput.XINPUT_STATE state) == 0)
{
ushort btns = state.Gamepad.wButtons;
byte padState = 0xFF;
if ((btns & 0x0001) != 0) padState &= 0xFE; // Up
if ((btns & 0x0002) != 0) padState &= 0xFD; // Down
if ((btns & 0x0004) != 0) padState &= 0xFB; // Left
if ((btns & 0x0008) != 0) padState &= 0xF7; // Right
if ((btns & 0x1000) != 0) padState &= 0xEF; // Button 1
if ((btns & 0x2000) != 0) padState &= 0xDF; // Button 2
// THE FIX: Constantly update the gamepad state, even when it's 0xFF!
_machine.IoBus.Joypad1Gamepad = padState;
}
// --------------------------------
_machine.RunFrame();
// 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish!
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(); });
// 3. Safety catch
if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value)
{
IsRunning = false;
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); });
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
break;
}
// 4. Calculate TRUE Frame Time (Compute Time) BEFORE we go to sleep!
FrameTime = _stopwatch.Elapsed.TotalMilliseconds - frameStartTime;
// --- HIGH PRECISION THROTTLE ---
while (_stopwatch.Elapsed.TotalMilliseconds < nextFrameTargetTime)
{
if (nextFrameTargetTime - _stopwatch.Elapsed.TotalMilliseconds > 2.0)
{
Thread.Sleep(1);
}
else
{
Thread.SpinWait(10);
}
}
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
TotalFrameCount++;
framesThisSecond++;
if (currentTime - lastFpsUpdate >= 1000.0)
{
FramesPerSecond = framesThisSecond / ((currentTime - lastFpsUpdate) / 1000.0);
framesThisSecond = 0;
lastFpsUpdate = currentTime;
//targetFps = isTurboMode ? 1000 : 59.94;
//TargetFrameTime = 1000.0 / targetFps;
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{
this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName} [FPS: {FramesPerSecond:F0}]";
targetFps = isTurboMode ? 1000 : 59.94;
TargetFrameTime = 1000.0 / targetFps;
});
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
// --- POLL PHYSICAL CONTROLLER ---
if (XInput.XInputGetState(0, out XInput.XINPUT_STATE state) == 0)
{
ushort btns = state.Gamepad.wButtons;
byte padState = 0xFF;
if ((btns & 0x0001) != 0) padState &= 0xFE; // Up
if ((btns & 0x0002) != 0) padState &= 0xFD; // Down
if ((btns & 0x0004) != 0) padState &= 0xFB; // Left
if ((btns & 0x0008) != 0) padState &= 0xF7; // Right
if ((btns & 0x1000) != 0) padState &= 0xEF; // Button 1
if ((btns & 0x2000) != 0) padState &= 0xDF; // Button 2
// THE FIX: Constantly update the gamepad state, even when it's 0xFF!
_machine.IoBus.Joypad1Gamepad = padState;
}
// --------------------------------
_machine.RunFrame();
int[] frozenFrame = (int[])_machine.VideoProcessor.FrameBuffer.Clone();
// 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish!
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(frozenFrame); });
// 3. Safety catch
if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value)
{
IsRunning = false;
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); });
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
break;
}
// 4. Calculate TRUE Frame Time (Compute Time) BEFORE we go to sleep!
FrameTime = _stopwatch.Elapsed.TotalMilliseconds - frameStartTime;
// --- HIGH PRECISION THROTTLE ---
while (_stopwatch.Elapsed.TotalMilliseconds < nextFrameTargetTime)
{
if (nextFrameTargetTime - _stopwatch.Elapsed.TotalMilliseconds > 2.0)
{
Thread.Sleep(1);
}
else
{
Thread.SpinWait(10);
}
}
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
TotalFrameCount++;
framesThisSecond++;
if (currentTime - lastFpsUpdate >= 1000.0)
{
FramesPerSecond = framesThisSecond / ((currentTime - lastFpsUpdate) / 1000.0);
framesThisSecond = 0;
lastFpsUpdate = currentTime;
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{
this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName} [FPS: {FramesPerSecond:F0}]";
});
}
nextFrameTargetTime += TargetFrameTime;
if (currentTime > nextFrameTargetTime + TargetFrameTime)
{
nextFrameTargetTime = currentTime + TargetFrameTime;
}
}
}
catch (Exception ex)
{
IsRunning = false;
nextFrameTargetTime += TargetFrameTime;
if (currentTime > nextFrameTargetTime + TargetFrameTime)
this.Invoke((System.Windows.Forms.MethodInvoker)delegate
{
nextFrameTargetTime = currentTime + TargetFrameTime;
}
MessageBox.Show(
$"The Z80 CPU Panicked!\n\n" +
$"PC Address: 0x{_machine.Cpu.PC:X4}\n" +
$"Error: {ex.Message}\n\n" +
$"Stack Trace:\n{ex.StackTrace}",
"Fatal Emulator Crash",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
});
}
});
}