Skip to content
Open
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
226 changes: 226 additions & 0 deletions CoreBoy.Windows/BitmapDisplayControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows.Forms;
using CoreBoy.gpu;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Image = System.Drawing.Image;

namespace CoreBoy.Windows
{
public sealed class BitmapDisplayControl : Control, IDisplay
{
public static readonly int DisplayWidth = 160;
public static readonly int DisplayHeight = 144;
public static readonly float AspectRatio = DisplayWidth / (DisplayHeight * 1f);

public static readonly int[] Colors = { 0xe6f8da, 0x99c886, 0x437969, 0x051f2a };

private readonly int[] _rgb;
private readonly MemoryStream _imageStream = new MemoryStream();
private readonly Image<Rgba32> _imageBuffer = new Image<Rgba32>(DisplayWidth, DisplayHeight);

private bool _doStop;
private bool _doRefresh;
private int _i;

private readonly object _lockObject = new object();

public BitmapDisplayControl()
{
_rgb = new int[DisplayWidth * DisplayHeight];
SetStyle(ControlStyles.Opaque | ControlStyles.Selectable, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.SupportsTransparentBackColor, true);

BackColor = System.Drawing.Color.FromArgb(Colors[0]);
TabStop = false;
}

public event FrameProducedEventHandler OnFrameProduced;

bool IDisplay.Enabled
{
get => DisplayEnabled;
set => DisplayEnabled = value;
}

public bool DisplayEnabled { get; set; }

public void PutDmgPixel(int color)
{
_rgb[_i++] = Colors[color];
_i %= _rgb.Length;
}

public void PutColorPixel(int gbcRgb)
{
if (_i >= _rgb.Length)
{
return;
}
_rgb[_i++] = TranslateGbcRgb(gbcRgb);
}

public static int TranslateGbcRgb(int gbcRgb)
{
var r = (gbcRgb >> 0) & 0x1f;
var g = (gbcRgb >> 5) & 0x1f;
var b = (gbcRgb >> 10) & 0x1f;
var result = (r * 8) << 16;
result |= (g * 8) << 8;
result |= (b * 8) << 0;
return result;
}

public void RequestRefresh()
{
lock (_lockObject)
{
_doRefresh = true;
Monitor.PulseAll(_lockObject);
}
}

public void WaitForRefresh()
{
lock (_lockObject)
{
while (_doRefresh)
{
try
{
Monitor.Wait(_lockObject, 1);
}
catch (ThreadInterruptedException)
{
break;
}
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (InvokeRequired)
{
Invoke(new PaintEventHandler((_, __) => OnPaint(e)));
return;
}

base.OnPaint(e);

var width = ClientRectangle.Width;
var height = ClientRectangle.Height;
var aspectRatio = width * 1f / height;

if (aspectRatio <= AspectRatio)
{
height = (int)Math.Floor(width / AspectRatio);
}
else
{
width = (int)Math.Floor(height * AspectRatio);
}

var x = 0;
var y = 0;
if (width < ClientRectangle.Width)
{
x += (ClientRectangle.Width - width) / 2;
}
else
{
y += (ClientRectangle.Height - height) / 2;
}

try
{
if (DisplayEnabled)
{
_imageStream.Seek(0, SeekOrigin.Begin);
_imageBuffer.SaveAsBmp(_imageStream);
using var img = Image.FromStream(_imageStream);

e.Graphics.DrawImage(img, x, y, width, height);
}
else
{
using var brush = new SolidBrush(System.Drawing.Color.FromArgb(0xe6f8da));
e.Graphics.FillRectangle(brush, x, y, width, height);
}
}
catch (ObjectDisposedException) { }
}

public void SaveLastFrame(string path)
{
using var fs = File.OpenWrite(path);
_imageBuffer.SaveAsBmp(fs);
fs.Flush();
fs.Close();
}

public void Run(CancellationToken token)
{
_doStop = false;
_doRefresh = false;
DisplayEnabled = true;

while (!_doStop)
{
lock (_lockObject)
{
try
{
Monitor.Wait(_lockObject, 1);
}
catch (ThreadInterruptedException)
{
break;
}
}

if (_doRefresh)
{
FillAndDrawBuffer();

lock (_lockObject)
{
_i = 0;
_doRefresh = false;
Monitor.PulseAll(_lockObject);
}
}

_doStop = token.IsCancellationRequested;
}
}

private void FillAndDrawBuffer()
{
try
{
var pi = 0;
while (pi < _rgb.Length)
{
var (r, g, b) = ToRgb(_rgb[pi]);
_imageBuffer[pi % DisplayWidth, pi++ / DisplayWidth] = new Rgba32((byte)r, (byte)g, (byte)b, 255);
}

Invalidate();
}
catch (ObjectDisposedException) { }
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static (int, int, int) ToRgb(int pixel)
{
var b = pixel & 255;
var g = (pixel >> 8) & 255;
var r = (pixel >> 16) & 255;
return (r, g, b);
}
}
}
82 changes: 22 additions & 60 deletions CoreBoy.Windows/WinFormsEmulatorSurface.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading;
Expand All @@ -14,21 +13,27 @@ public partial class WinFormsEmulatorSurface : Form, IController
{
private IButtonListener _listener;

private byte[] _lastFrame;
private readonly MenuStrip _menu;
private readonly PictureBox _pictureBox;
private readonly BitmapDisplayControl _display;
private readonly Dictionary<Keys, Button> _controls;

private readonly Emulator _emulator;
private readonly GameboyOptions _gameboyOptions;
private CancellationTokenSource _cancellation;

private readonly object _updateLock = new object();

public WinFormsEmulatorSurface()
{
InitializeComponent();

Controls.Add(_display = new BitmapDisplayControl
{
BackColor = Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(230)))), ((int)(((byte)(248)))), ((int)(((byte)(218))))),
DisplayEnabled = false,
Dock = DockStyle.Fill,
Location = new Point(0, 44),
Size = new Size(800, 720)
});

Controls.Add(_menu = new MenuStrip
{
Items =
Expand All @@ -52,15 +57,6 @@ public WinFormsEmulatorSurface()
}
});

Controls.Add(_pictureBox = new PictureBox
{
Top = _menu.Height,
Width = BitmapDisplay.DisplayWidth * 5,
Height = BitmapDisplay.DisplayHeight * 5,
BackColor = Color.Black,
SizeMode = PictureBoxSizeMode.Zoom
});

_controls = new Dictionary<Keys, Button>
{
{Keys.Left, Button.Left},
Expand All @@ -73,21 +69,24 @@ public WinFormsEmulatorSurface()
{Keys.Back, Button.Select}
};

Height = _menu.Height + _pictureBox.Height + 50;
Width = _pictureBox.Width;
AutoScaleDimensions = new SizeF(192F, 192F);
AutoScaleMode = AutoScaleMode.Dpi;
ClientSize = new Size(_display.Width, _display.Height + _menu.Height);

_cancellation = new CancellationTokenSource();
_gameboyOptions = new GameboyOptions();
_emulator = new Emulator(_gameboyOptions);
_emulator = new Emulator(_gameboyOptions)
{
Display = _display
};

ConnectEmulatorToPanel();
}

private void ConnectEmulatorToPanel()
{
_emulator.Controller = this;
_emulator.Display.OnFrameProduced += UpdateDisplay;


KeyDown += WinFormsEmulatorSurface_KeyDown;
KeyUp += WinFormsEmulatorSurface_KeyUp;
Closed += (_, e) => { _cancellation.Cancel(); };
Expand All @@ -99,7 +98,7 @@ private void StartEmulation()
{
_emulator.Stop(_cancellation);
_cancellation = new CancellationTokenSource();
_pictureBox.Image = null;
_display.DisplayEnabled = false;
Thread.Sleep(100);
}

Expand Down Expand Up @@ -138,15 +137,7 @@ private void Screenshot()

if (success)
{
try
{
Monitor.Enter(_updateLock);
File.WriteAllBytes(sfd.FileName, _lastFrame);
}
finally
{
Monitor.Exit(_updateLock);
}
_display.SaveLastFrame(sfd.FileName);
}

_emulator.TogglePause();
Expand All @@ -172,39 +163,10 @@ private void WinFormsEmulatorSurface_KeyUp(object sender, KeyEventArgs e)

public void SetButtonListener(IButtonListener listener) => _listener = listener;

protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (_pictureBox == null) return;

_pictureBox.Width = Width;
_pictureBox.Height = Height - _menu.Height - 50;
}

public void UpdateDisplay(object _, byte[] frame)
{
if (!Monitor.TryEnter(_updateLock)) return;

try
{
_lastFrame = frame;
using var memoryStream = new MemoryStream(frame);
_pictureBox.Image = Image.FromStream(memoryStream);
}
catch
{
// YOLO
}
finally
{
Monitor.Exit(_updateLock);
}
}

protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_pictureBox.Dispose();
_display.Dispose();
}
}
}
6 changes: 6 additions & 0 deletions CoreBoy/Integer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ public static string ToHexString(int address)
{
return $"{address} - THIS SHOULD BE A HEX ADDRESS";
}

public static bool GetBit(this int byteValue, int position) => (byteValue & (1 << position)) != 0;

public static int SetBit(this int byteValue, int position, bool value) => value ? SetBit(byteValue, position) : ClearBit(byteValue, position);
public static int SetBit(this int byteValue, int position) => (byteValue | (1 << position)) & 0xff;
public static int ClearBit(this int byteValue, int position) => ~(1 << position) & byteValue & 0xff;
}
}
Loading