Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions src/Spectron.Emulation/EmulatorTimer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using OldBit.Spectron.Emulation.Platforms;

namespace OldBit.Spectron.Emulation;

Expand All @@ -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;
Expand All @@ -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();

Expand All @@ -37,6 +46,11 @@ internal void Stop()

internal void Pause()
{
if (_isDisposed)
{
return;
}

_pausedEvent.Reset();
IsPaused = true;

Expand Down Expand Up @@ -106,6 +120,10 @@ private void Worker()
case < 10:
Thread.SpinWait(25);
break;

default:
Thread.Sleep(Math.Max(1, (int)timeToWait.TotalMilliseconds - 5));
break;
}
}
}
Expand All @@ -119,6 +137,9 @@ private void Worker()

public void Dispose()
{
_isDisposed = true;

_timerResolutionScope.Dispose();
_cancellationTokenSource.Dispose();
_stoppedEvent.Dispose();
_pausedEvent.Dispose();
Expand Down
24 changes: 24 additions & 0 deletions src/Spectron.Emulation/Platforms/TimerResolutionScope.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
25 changes: 25 additions & 0 deletions src/Spectron.Emulation/Platforms/Windows/Interop/Winmm.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The timeBeginPeriod function requests a minimum resolution for periodic timers.
/// </summary>
/// <param name="uPeriod">Minimum timer resolution, in milliseconds, for the application or device driver.
/// A lower value specifies a higher (more accurate) resolution.</param>
/// <returns>Returns TIMERR_NOERROR if successful or TIMERR_NOCANDO if the resolution specified in uPeriod is out of range.</returns>
[LibraryImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
internal static partial uint TimeBeginPeriod(uint uPeriod);

/// <summary>
/// The timeEndPeriod function clears a previously set minimum timer resolution.
/// </summary>
/// <param name="uPeriod">Minimum timer resolution specified in the previous call to the timeBeginPeriod function.</param>
/// <returns>Returns TIMERR_NOERROR if successful or TIMERR_NOCANDO if the resolution specified in uPeriod is out of range.</returns>
[LibraryImport("winmm.dll", EntryPoint = "timeEndPeriod")]
internal static partial uint TimeEndPeriod(uint uPeriod);
}
1 change: 1 addition & 0 deletions src/Spectron.Emulation/Spectron.Emulation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>OldBit.Spectron.Emulation</AssemblyName>
<RootNamespace>OldBit.Spectron.Emulation</RootNamespace>
<Version>1.0.0</Version>
Expand Down
23 changes: 10 additions & 13 deletions src/Spectron/Screen/FrameBufferConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
Loading