diff --git a/src/Spectron.Emulation/EmulatorTimer.cs b/src/Spectron.Emulation/EmulatorTimer.cs
index c5d15926..ed38a614 100644
--- a/src/Spectron.Emulation/EmulatorTimer.cs
+++ b/src/Spectron.Emulation/EmulatorTimer.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using OldBit.Spectron.Emulation.Platforms;
namespace OldBit.Spectron.Emulation;
@@ -12,6 +13,9 @@ internal sealed class EmulatorTimer : IDisposable
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly ManualResetEventSlim _stoppedEvent = new(initialState: false);
private readonly ManualResetEventSlim _pausedEvent = new(initialState: false);
+ private readonly TimerResolutionScope _timerResolutionScope;
+
+ private volatile bool _isDisposed;
internal bool IsPaused { get; private set; }
internal ThreadPriority Priority { get; set; } = ThreadPriority.AboveNormal;
@@ -20,12 +24,17 @@ internal sealed class EmulatorTimer : IDisposable
internal event EventHandler? Elapsed;
- internal EmulatorTimer() => _worker = new Thread(Worker)
+ internal EmulatorTimer()
{
- IsBackground = true,
- Priority = Priority,
- Name = "Emulator Timer"
- };
+ _timerResolutionScope = new TimerResolutionScope();
+
+ _worker = new Thread(Worker)
+ {
+ IsBackground = true,
+ Priority = Priority,
+ Name = "Emulator Timer"
+ };
+ }
internal void Start() => _worker.Start();
@@ -37,6 +46,11 @@ internal void Stop()
internal void Pause()
{
+ if (_isDisposed)
+ {
+ return;
+ }
+
_pausedEvent.Reset();
IsPaused = true;
@@ -106,6 +120,10 @@ private void Worker()
case < 10:
Thread.SpinWait(25);
break;
+
+ default:
+ Thread.Sleep(Math.Max(1, (int)timeToWait.TotalMilliseconds - 5));
+ break;
}
}
}
@@ -119,6 +137,9 @@ private void Worker()
public void Dispose()
{
+ _isDisposed = true;
+
+ _timerResolutionScope.Dispose();
_cancellationTokenSource.Dispose();
_stoppedEvent.Dispose();
_pausedEvent.Dispose();
diff --git a/src/Spectron.Emulation/Platforms/TimerResolutionScope.cs b/src/Spectron.Emulation/Platforms/TimerResolutionScope.cs
new file mode 100644
index 00000000..fe6d99e1
--- /dev/null
+++ b/src/Spectron.Emulation/Platforms/TimerResolutionScope.cs
@@ -0,0 +1,24 @@
+using OldBit.Spectron.Emulation.Platforms.Windows.Interop;
+
+namespace OldBit.Spectron.Emulation.Platforms;
+
+internal sealed class TimerResolutionScope : IDisposable
+{
+ private const int TimerResolutionMs = 1;
+
+ internal TimerResolutionScope()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ _ = Winmm.TimeBeginPeriod(TimerResolutionMs);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ _ = Winmm.TimeEndPeriod(TimerResolutionMs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Spectron.Emulation/Platforms/Windows/Interop/Winmm.cs b/src/Spectron.Emulation/Platforms/Windows/Interop/Winmm.cs
new file mode 100644
index 00000000..5535d474
--- /dev/null
+++ b/src/Spectron.Emulation/Platforms/Windows/Interop/Winmm.cs
@@ -0,0 +1,25 @@
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace OldBit.Spectron.Emulation.Platforms.Windows.Interop;
+
+[SupportedOSPlatform("windows")]
+internal static partial class Winmm
+{
+ ///
+ /// The timeBeginPeriod function requests a minimum resolution for periodic timers.
+ ///
+ /// Minimum timer resolution, in milliseconds, for the application or device driver.
+ /// A lower value specifies a higher (more accurate) resolution.
+ /// Returns TIMERR_NOERROR if successful or TIMERR_NOCANDO if the resolution specified in uPeriod is out of range.
+ [LibraryImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
+ internal static partial uint TimeBeginPeriod(uint uPeriod);
+
+ ///
+ /// The timeEndPeriod function clears a previously set minimum timer resolution.
+ ///
+ /// Minimum timer resolution specified in the previous call to the timeBeginPeriod function.
+ /// Returns TIMERR_NOERROR if successful or TIMERR_NOCANDO if the resolution specified in uPeriod is out of range.
+ [LibraryImport("winmm.dll", EntryPoint = "timeEndPeriod")]
+ internal static partial uint TimeEndPeriod(uint uPeriod);
+}
\ No newline at end of file
diff --git a/src/Spectron.Emulation/Spectron.Emulation.csproj b/src/Spectron.Emulation/Spectron.Emulation.csproj
index dceed476..d84b160f 100644
--- a/src/Spectron.Emulation/Spectron.Emulation.csproj
+++ b/src/Spectron.Emulation/Spectron.Emulation.csproj
@@ -4,6 +4,7 @@
net10.0
enable
enable
+ true
OldBit.Spectron.Emulation
OldBit.Spectron.Emulation
1.0.0
diff --git a/src/Spectron/Screen/FrameBufferConverter.cs b/src/Spectron/Screen/FrameBufferConverter.cs
index 1abe8541..f0324d2d 100644
--- a/src/Spectron/Screen/FrameBufferConverter.cs
+++ b/src/Spectron/Screen/FrameBufferConverter.cs
@@ -34,25 +34,22 @@ internal void UpdateBitmap()
{
using var lockedBitmap = ScreenBitmap.Lock();
- var targetAddress = lockedBitmap.Address;
+ var colCount = _endFrameBufferCol - _startFrameBufferCol + 1;
+ var rowBytes = colCount * 4;
- for (var frameBufferRow = _startFrameBufferRow; frameBufferRow <= _endFrameBufferRow; frameBufferRow++)
+ unsafe
{
- var rowOffset = frameBufferRow * _frameBuffer.Width;
-
- for (var frameBufferCol = _startFrameBufferCol; frameBufferCol <= _endFrameBufferCol; frameBufferCol++)
+ fixed (Color* pixelsBase = _frameBuffer.Pixels)
{
- var pixelIndex = rowOffset + frameBufferCol;
+ var destination = (byte*)lockedBitmap.Address;
- unsafe
+ for (var row = _startFrameBufferRow; row <= _endFrameBufferRow; row++)
{
- fixed (Color* color = &_frameBuffer.Pixels[pixelIndex])
- {
- var pixelColor = *(uint*)color;
- *(uint*)targetAddress = pixelColor;
+ var source = (byte*)(pixelsBase + row * _frameBuffer.Width + _startFrameBufferCol);
+
+ Buffer.MemoryCopy(source, destination, rowBytes, rowBytes);
- targetAddress += 4;
- }
+ destination += rowBytes;
}
}
}