mirror of
https://github.com/livingcomputermuseum/sImlac.git
synced 2026-01-11 23:53:24 +00:00
879 lines
28 KiB
C#
879 lines
28 KiB
C#
/*
|
|
This file is part of sImlac.
|
|
|
|
sImlac is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
sImlac is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with sImlac. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Windows.Forms;
|
|
using System.Drawing;
|
|
using System.Threading;
|
|
|
|
using imlac.IO;
|
|
using SdlDotNet.Graphics;
|
|
|
|
namespace imlac
|
|
{
|
|
public enum DataSwitchMappingMode
|
|
{
|
|
Toggle, // Pressing a key toggles the switch (from 0 to 1 or from 1 to 0)
|
|
Momentary, // Holding a key down indicates a "1", release indicates "0"
|
|
MomentaryInverted, // Same as above, but inverted
|
|
}
|
|
|
|
public enum VKeys
|
|
{
|
|
// Keys that map to Imlac keyboard keys
|
|
Shift = 0x10,
|
|
Ctrl = 0x11,
|
|
Alt = 0x12,
|
|
|
|
End = 0x23,
|
|
DownArrow = 0x28,
|
|
RightArrow = 0x27,
|
|
UpArrow = 0x26,
|
|
LeftArrow = 0x25,
|
|
Tab = 0x9,
|
|
Return = 0xd,
|
|
PageUp = 0x21,
|
|
PageDown = 0x22,
|
|
Home = 0x24,
|
|
Pause = 0x91,
|
|
Escape = 0x1b,
|
|
Space = 0x20,
|
|
|
|
Comma = 0xbc,
|
|
Plus = 0xbb,
|
|
Period = 0xbe,
|
|
QuestionMark = 0xbf,
|
|
Zero = 0x30,
|
|
One = 0x31,
|
|
Two = 0x32,
|
|
Three = 0x33,
|
|
Four = 0x34,
|
|
Five = 0x35,
|
|
Six = 0x36,
|
|
Seven = 0x37,
|
|
Eight = 0x38,
|
|
Nine = 0x39,
|
|
Minus = 0xbd,
|
|
Semicolon = 0xba,
|
|
|
|
Keypad0 = 0x60,
|
|
Keypad2 = 0x62,
|
|
Keypad4 = 0x64,
|
|
Keypad5 = 0x65,
|
|
Keypad6 = 0x66,
|
|
DoubleQuote = 0xde,
|
|
|
|
A = 0x41,
|
|
B = 0x42,
|
|
C = 0x43,
|
|
D = 0x44,
|
|
E = 0x45,
|
|
F = 0x46,
|
|
G = 0x47,
|
|
H = 0x48,
|
|
I = 0x49,
|
|
J = 0x4a,
|
|
K = 0x4b,
|
|
L = 0x4c,
|
|
M = 0x4d,
|
|
N = 0x4e,
|
|
O = 0x4f,
|
|
P = 0x50,
|
|
Q = 0x51,
|
|
R = 0x52,
|
|
S = 0x53,
|
|
T = 0x54,
|
|
U = 0x55,
|
|
V = 0x56,
|
|
W = 0x57,
|
|
X = 0x58,
|
|
Y = 0x59,
|
|
Z = 0x5a,
|
|
|
|
Delete = 0x8,
|
|
|
|
// Additional keys, not used by the Imlac but available
|
|
// for data switch mapping.
|
|
F1 = 0x70,
|
|
F2 = 0x71,
|
|
F3 = 0x72,
|
|
F4 = 0x73,
|
|
F5 = 0x74,
|
|
F6 = 0x75,
|
|
F7 = 0x76,
|
|
F8 = 0x77,
|
|
F9 = 0x78,
|
|
F10 = 0x79,
|
|
F11 = 0x7a,
|
|
F12 = 0x7b,
|
|
|
|
Keypad1 = 0x61,
|
|
Keypad3 = 0x63,
|
|
Keypad7 = 0x67,
|
|
Keypad8 = 0x68,
|
|
Keypad9 = 0x69,
|
|
|
|
|
|
// hack to toggle fullscreen.
|
|
Insert = 0x2d,
|
|
|
|
// Special values for data switch mappings
|
|
None0 = 0, // No key mapped to data switch, DS for this bit is set to 0
|
|
None1 = 1, // Ditto, but for the value 1
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is used to filter keyboard messages from the App's message loop.
|
|
/// This is necessary because the "Video" object that SDL provides appears
|
|
/// to eat certain keystrokes and since I have no means to control it
|
|
/// (and I'm not really all that interested in compiling my own version)
|
|
/// this is the only alternative.
|
|
///
|
|
/// This class captures keystrokes for keys the emulator is interested in
|
|
/// and fires an event containing the keycode.
|
|
/// These keyboard messages are always passed on to the app.
|
|
/// </summary>
|
|
public class KeyboardFilter : IMessageFilter
|
|
{
|
|
public KeyboardFilter()
|
|
{
|
|
_keyModifiers = ImlacKeyModifiers.None;
|
|
_keyLatched = false;
|
|
_dataSwitches = 0x0; // ffff;
|
|
_latchedKeyCode = ImlacKey.Invalid;
|
|
_dataSwitchMappingMode = DataSwitchMappingMode.Toggle;
|
|
|
|
_keyLatchedLock = new ReaderWriterLockSlim();
|
|
}
|
|
|
|
static KeyboardFilter()
|
|
{
|
|
BuildKeyMappings();
|
|
}
|
|
|
|
public event EventHandler FullScreenToggle;
|
|
|
|
// TODO: should ensure consistency (threading)
|
|
|
|
public ImlacKey LatchedKey
|
|
{
|
|
get { return _latchedKeyCode; }
|
|
}
|
|
|
|
public ImlacKeyModifiers Modifiers
|
|
{
|
|
get { return _keyModifiers; }
|
|
}
|
|
|
|
public bool KeyLatched
|
|
{
|
|
get
|
|
{
|
|
_keyLatchedLock.EnterReadLock();
|
|
bool latched = _keyLatched;
|
|
_keyLatchedLock.ExitReadLock();
|
|
return latched;
|
|
}
|
|
set
|
|
{
|
|
_keyLatchedLock.EnterWriteLock();
|
|
_keyLatched = value;
|
|
_keyLatchedLock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
public ushort DataSwitches
|
|
{
|
|
get { return (ushort)_dataSwitches; }
|
|
}
|
|
|
|
public DataSwitchMappingMode DataSwitchMode
|
|
{
|
|
get { return _dataSwitchMappingMode; }
|
|
set { _dataSwitchMappingMode = value; }
|
|
}
|
|
|
|
public void MapDataSwitch(uint switchNumber, VKeys key)
|
|
{
|
|
_dataSwitchMappings[switchNumber] = key;
|
|
}
|
|
|
|
public VKeys GetDataSwitchMapping(uint switchNumber)
|
|
{
|
|
return _dataSwitchMappings[switchNumber];
|
|
}
|
|
|
|
public bool PreFilterMessage(ref Message m)
|
|
{
|
|
bool ret = false;
|
|
|
|
switch (m.Msg)
|
|
{
|
|
case WM_SYSKEYDOWN:
|
|
case WM_SOMETHINGDOWN:
|
|
//
|
|
// If this is a modifier key (Alt, Shift, Ctrl) then we track it separately
|
|
// (it is not tracked as a key)
|
|
//
|
|
switch ((VKeys)m.WParam.ToInt32())
|
|
{
|
|
case VKeys.Shift:
|
|
_keyModifiers |= ImlacKeyModifiers.Shift;
|
|
break;
|
|
|
|
case VKeys.Ctrl:
|
|
_keyModifiers |= ImlacKeyModifiers.Ctrl;
|
|
break;
|
|
|
|
case VKeys.Alt:
|
|
_keyModifiers |= ImlacKeyModifiers.Rept;
|
|
break;
|
|
|
|
default:
|
|
|
|
UpdateDataSwitches((VKeys)m.WParam.ToInt32(), true /* key down */);
|
|
|
|
//Console.WriteLine("{0:x}", m.WParam.ToInt32());
|
|
|
|
if ((VKeys)m.WParam.ToInt32() == VKeys.Insert)
|
|
{
|
|
if (FullScreenToggle != null)
|
|
{
|
|
FullScreenToggle(this, null);
|
|
}
|
|
}
|
|
|
|
_keyLatchedLock.EnterWriteLock();
|
|
|
|
_keyLatched = true;
|
|
_latchedKeyCode = TranslateKeyCode((VKeys)m.WParam.ToInt32());
|
|
|
|
_keyLatchedLock.ExitWriteLock();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_SYSKEYUP:
|
|
case WM_SOMETHINGUP:
|
|
//
|
|
// We only track keyboard modifiers and data switch toggles here.
|
|
//
|
|
switch ((VKeys)m.WParam.ToInt32())
|
|
{
|
|
case VKeys.Shift:
|
|
_keyModifiers &= (~ImlacKeyModifiers.Shift);
|
|
break;
|
|
|
|
case VKeys.Ctrl:
|
|
_keyModifiers &= (~ImlacKeyModifiers.Ctrl);
|
|
break;
|
|
|
|
case VKeys.Alt:
|
|
_keyModifiers &= (~ImlacKeyModifiers.Rept);
|
|
break;
|
|
|
|
default:
|
|
UpdateDataSwitches((VKeys)m.WParam.ToInt32(), false /* key up */);
|
|
|
|
_latchedKeyCode = ImlacKey.Invalid;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
private ImlacKey TranslateKeyCode(VKeys virtualKey)
|
|
{
|
|
if (_keyMappings.ContainsKey(virtualKey))
|
|
{
|
|
return _keyMappings[virtualKey];
|
|
}
|
|
else
|
|
{
|
|
return ImlacKey.Invalid;
|
|
}
|
|
}
|
|
|
|
|
|
private void UpdateDataSwitches(VKeys virtualKey, bool keyDown)
|
|
{
|
|
//
|
|
// If this is a key mapped to a front panel switch
|
|
// we will toggle the bit in the DS register based on whether
|
|
// the key is down or up and the specified mapping mode.
|
|
//
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
if (_dataSwitchMappings[i] == VKeys.None0)
|
|
{
|
|
_dataSwitches &= ~(0x1 << (15 - i));
|
|
}
|
|
else if (_dataSwitchMappings[i] == VKeys.None1)
|
|
{
|
|
_dataSwitches |= (0x1 << (15 - i));
|
|
}
|
|
else if (virtualKey == _dataSwitchMappings[i])
|
|
{
|
|
switch (_dataSwitchMappingMode)
|
|
{
|
|
case DataSwitchMappingMode.Momentary:
|
|
case DataSwitchMappingMode.MomentaryInverted:
|
|
if (_dataSwitchMappingMode == DataSwitchMappingMode.MomentaryInverted)
|
|
{
|
|
// Invert the sense
|
|
keyDown = !keyDown;
|
|
}
|
|
|
|
// toggle this bit
|
|
if (keyDown)
|
|
{
|
|
// or it in
|
|
_dataSwitches |= (0x1 << (15 - i));
|
|
}
|
|
else
|
|
{
|
|
// mask it out
|
|
_dataSwitches &= ~(0x1 << (15 - i));
|
|
}
|
|
break;
|
|
|
|
case DataSwitchMappingMode.Toggle:
|
|
if (keyDown)
|
|
{
|
|
// toggle it
|
|
_dataSwitches ^= (0x1 << (15 - i));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void BuildKeyMappings()
|
|
{
|
|
_keyMappings = new Dictionary<VKeys, ImlacKey>();
|
|
|
|
_keyMappings.Add(VKeys.End, ImlacKey.DataXmit);
|
|
_keyMappings.Add(VKeys.DownArrow, ImlacKey.Down);
|
|
_keyMappings.Add(VKeys.RightArrow, ImlacKey.Right);
|
|
_keyMappings.Add(VKeys.UpArrow, ImlacKey.Up);
|
|
_keyMappings.Add(VKeys.LeftArrow, ImlacKey.Left);
|
|
_keyMappings.Add(VKeys.Tab, ImlacKey.Tab);
|
|
_keyMappings.Add(VKeys.Return, ImlacKey.CR);
|
|
_keyMappings.Add(VKeys.PageUp, ImlacKey.FF);
|
|
_keyMappings.Add(VKeys.PageDown, ImlacKey.PageXmit);
|
|
_keyMappings.Add(VKeys.Home, ImlacKey.Home);
|
|
_keyMappings.Add(VKeys.Pause, ImlacKey.Brk);
|
|
_keyMappings.Add(VKeys.Escape, ImlacKey.Esc);
|
|
_keyMappings.Add(VKeys.Space, ImlacKey.Space);
|
|
|
|
_keyMappings.Add(VKeys.Comma, ImlacKey.Comma);
|
|
_keyMappings.Add(VKeys.Plus, ImlacKey.Minus);
|
|
_keyMappings.Add(VKeys.Period, ImlacKey.Period);
|
|
_keyMappings.Add(VKeys.QuestionMark, ImlacKey.Slash);
|
|
_keyMappings.Add(VKeys.Zero, ImlacKey.K0);
|
|
_keyMappings.Add(VKeys.One, ImlacKey.K1);
|
|
_keyMappings.Add(VKeys.Two, ImlacKey.K2);
|
|
_keyMappings.Add(VKeys.Three, ImlacKey.K3);
|
|
_keyMappings.Add(VKeys.Four, ImlacKey.K4);
|
|
_keyMappings.Add(VKeys.Five, ImlacKey.K5);
|
|
_keyMappings.Add(VKeys.Six, ImlacKey.K6);
|
|
_keyMappings.Add(VKeys.Seven, ImlacKey.K7);
|
|
_keyMappings.Add(VKeys.Eight, ImlacKey.K8);
|
|
_keyMappings.Add(VKeys.Nine, ImlacKey.K9);
|
|
_keyMappings.Add(VKeys.Minus, ImlacKey.Colon);
|
|
_keyMappings.Add(VKeys.Semicolon, ImlacKey.Semicolon);
|
|
|
|
_keyMappings.Add(VKeys.Keypad0, ImlacKey.D0);
|
|
_keyMappings.Add(VKeys.Keypad2, ImlacKey.D2);
|
|
_keyMappings.Add(VKeys.Keypad4, ImlacKey.D4);
|
|
_keyMappings.Add(VKeys.Keypad5, ImlacKey.D5);
|
|
_keyMappings.Add(VKeys.Keypad6, ImlacKey.D6);
|
|
_keyMappings.Add(VKeys.DoubleQuote, ImlacKey.Unlabeled);
|
|
|
|
_keyMappings.Add(VKeys.A, ImlacKey.A);
|
|
_keyMappings.Add(VKeys.B, ImlacKey.B);
|
|
_keyMappings.Add(VKeys.C, ImlacKey.C);
|
|
_keyMappings.Add(VKeys.D, ImlacKey.D);
|
|
_keyMappings.Add(VKeys.E, ImlacKey.E);
|
|
_keyMappings.Add(VKeys.F, ImlacKey.F);
|
|
_keyMappings.Add(VKeys.G, ImlacKey.G);
|
|
_keyMappings.Add(VKeys.H, ImlacKey.H);
|
|
_keyMappings.Add(VKeys.I, ImlacKey.I);
|
|
_keyMappings.Add(VKeys.J, ImlacKey.J);
|
|
_keyMappings.Add(VKeys.K, ImlacKey.K);
|
|
_keyMappings.Add(VKeys.L, ImlacKey.L);
|
|
_keyMappings.Add(VKeys.M, ImlacKey.M);
|
|
_keyMappings.Add(VKeys.N, ImlacKey.N);
|
|
_keyMappings.Add(VKeys.O, ImlacKey.O);
|
|
_keyMappings.Add(VKeys.P, ImlacKey.P);
|
|
_keyMappings.Add(VKeys.Q, ImlacKey.Q);
|
|
_keyMappings.Add(VKeys.R, ImlacKey.R);
|
|
_keyMappings.Add(VKeys.S, ImlacKey.S);
|
|
_keyMappings.Add(VKeys.T, ImlacKey.T);
|
|
_keyMappings.Add(VKeys.U, ImlacKey.U);
|
|
_keyMappings.Add(VKeys.V, ImlacKey.V);
|
|
_keyMappings.Add(VKeys.W, ImlacKey.W);
|
|
_keyMappings.Add(VKeys.X, ImlacKey.X);
|
|
_keyMappings.Add(VKeys.Y, ImlacKey.Y);
|
|
_keyMappings.Add(VKeys.Z, ImlacKey.Z);
|
|
|
|
_keyMappings.Add(VKeys.Delete, ImlacKey.Del);
|
|
}
|
|
|
|
private static Dictionary<VKeys, ImlacKey> _keyMappings;
|
|
|
|
private ImlacKey _latchedKeyCode;
|
|
private ImlacKeyModifiers _keyModifiers;
|
|
private bool _keyLatched;
|
|
|
|
private ReaderWriterLockSlim _keyLatchedLock;
|
|
|
|
// Data switch mappings:
|
|
// There are 16 switches mapped here, a value of None0 or None1 means
|
|
// that no key is mapped and to hardcode return value to 0 or 1
|
|
// The first entry corresponds to bit 0 (MSB)
|
|
// and the last entry corresponds to bit 15 (LSB)
|
|
private VKeys[] _dataSwitchMappings =
|
|
{
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
VKeys.None0,
|
|
};
|
|
|
|
private int _dataSwitches;
|
|
private DataSwitchMappingMode _dataSwitchMappingMode;
|
|
|
|
private const int WM_SOMETHINGUP = 0x105;
|
|
private const int WM_SOMETHINGDOWN = 0x104;
|
|
private const int WM_KEYUP = 0x103;
|
|
private const int WM_KEYDOWN = 0x102;
|
|
private const int WM_SYSKEYUP = 0x101;
|
|
private const int WM_SYSKEYDOWN = 0x100;
|
|
|
|
|
|
}
|
|
//
|
|
// Provides a console using SDL.
|
|
//
|
|
public class SDLConsole : IImlacConsole
|
|
{
|
|
public SDLConsole(float scaleFactor)
|
|
{
|
|
if (scaleFactor <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("scaleFactor");
|
|
}
|
|
|
|
_scaleFactor = scaleFactor;
|
|
_throttleFramerate = true;
|
|
|
|
_lock = new ReaderWriterLockSlim();
|
|
_swapLock = new ReaderWriterLockSlim();
|
|
|
|
_frame = 0;
|
|
|
|
_frameTimer = new FrameTimer(40);
|
|
_timer = new HighResTimer();
|
|
|
|
_fullScreen = false;
|
|
|
|
_displayList = new List<Vector>(_displayListSize);
|
|
_displayListIndex = 0;
|
|
|
|
//
|
|
// Prepopulate the display list with Vectors. Only those used in the current frame are
|
|
// actually rendered, we prepopulate the list to prevent having to cons up new ones
|
|
// constantly.
|
|
//
|
|
for (int i = 0; i < _displayListSize; i++)
|
|
{
|
|
_displayList.Add(new Vector(DrawingMode.Off, 1, 0, 0, 0, 0));
|
|
}
|
|
|
|
InvokeDisplayThread();
|
|
}
|
|
|
|
public bool IsKeyPressed
|
|
{
|
|
get { return _keyboardFilter.KeyLatched; }
|
|
}
|
|
|
|
public ImlacKey Key
|
|
{
|
|
get { return _keyboardFilter.LatchedKey; }
|
|
}
|
|
|
|
public ImlacKeyModifiers KeyModifiers
|
|
{
|
|
get { return _keyboardFilter.Modifiers; }
|
|
}
|
|
|
|
public void UnlatchKey()
|
|
{
|
|
_keyboardFilter.KeyLatched = false;
|
|
}
|
|
|
|
public ushort DataSwitches
|
|
{
|
|
get { return _keyboardFilter.DataSwitches; }
|
|
}
|
|
|
|
public bool ThrottleFramerate
|
|
{
|
|
get { return _throttleFramerate; }
|
|
set { _throttleFramerate = value; }
|
|
}
|
|
|
|
public bool DataSwitchMappingEnabled
|
|
{
|
|
get { return _dataSwitchMappingEnabled; }
|
|
set { _dataSwitchMappingEnabled = value; }
|
|
}
|
|
|
|
public DataSwitchMappingMode DataSwitchMode
|
|
{
|
|
get { return _keyboardFilter.DataSwitchMode; }
|
|
set { _keyboardFilter.DataSwitchMode = value; }
|
|
}
|
|
|
|
public bool FullScreen
|
|
{
|
|
get { return _fullScreen; }
|
|
set
|
|
{
|
|
if (value != _fullScreen)
|
|
{
|
|
_fullScreen = value;
|
|
UpdateScreenMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearDisplay()
|
|
{
|
|
_lock.EnterWriteLock();
|
|
_displayListIndex = 0;
|
|
_lock.ExitWriteLock();
|
|
RenderCurrent(true);
|
|
}
|
|
|
|
public void SetScale(float scale)
|
|
{
|
|
if (scale <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("scale");
|
|
}
|
|
|
|
_scaleFactor = scale;
|
|
UpdateDisplayScale();
|
|
}
|
|
|
|
public void MapDataSwitch(uint switchNumber, VKeys key)
|
|
{
|
|
_keyboardFilter.MapDataSwitch(switchNumber, key);
|
|
}
|
|
|
|
public VKeys GetDataSwitchMapping(uint switchNumber)
|
|
{
|
|
return _keyboardFilter.GetDataSwitchMapping(switchNumber);
|
|
}
|
|
|
|
private void UpdateDisplayScale()
|
|
{
|
|
_xResolution = (int)(2048.0 * _scaleFactor);
|
|
_yResolution = (int)(2048.0 * _scaleFactor);
|
|
|
|
Video.SetVideoMode((int)_xResolution, (int)_yResolution, 32, false, false, _fullScreen, true);
|
|
Video.WindowCaption = "Imlac PDS-1";
|
|
_displaySurface = Video.Screen.CreateCompatibleSurface((int)_xResolution, (int)_yResolution, true);
|
|
_displayBox = new SdlDotNet.Graphics.Primitives.Box(0, 0, (short)Video.Screen.Rectangle.Width, (short)Video.Screen.Rectangle.Height);
|
|
}
|
|
|
|
public void MoveAbsolute(uint x, uint y, DrawingMode mode)
|
|
{
|
|
//
|
|
// Take coordinates as an 11-bit quantity (0-2048) even though we may not be displaying the full resolution.
|
|
//
|
|
if (mode != DrawingMode.Off)
|
|
{
|
|
AddNewVector(mode, _x, _y, x, y);
|
|
}
|
|
|
|
_x = x;
|
|
_y = y;
|
|
}
|
|
|
|
public void DrawPoint(uint x, uint y)
|
|
{
|
|
_x = x;
|
|
_y = y;
|
|
|
|
AddNewVector(DrawingMode.Point, x, y, x, y);
|
|
}
|
|
|
|
public void FrameDone()
|
|
{
|
|
RenderCurrent(true);
|
|
//
|
|
// Sync to 40hz framerate
|
|
//
|
|
if (_throttleFramerate)
|
|
{
|
|
_frameTimer.WaitForFrame();
|
|
}
|
|
}
|
|
|
|
private void InvokeDisplayThread()
|
|
{
|
|
_displayThread = new System.Threading.Thread(new System.Threading.ThreadStart(DisplayThread));
|
|
_displayThread.Start();
|
|
|
|
_initEvent = new ManualResetEvent(false);
|
|
|
|
WaitHandle[] handles = { _initEvent };
|
|
|
|
WaitHandle.WaitAll(handles);
|
|
|
|
Thread.Sleep(500);
|
|
|
|
}
|
|
|
|
private void DisplayThread()
|
|
{
|
|
UpdateDisplayScale();
|
|
_initEvent.Set();
|
|
|
|
_keyboardFilter = new KeyboardFilter();
|
|
Application.AddMessageFilter(_keyboardFilter);
|
|
|
|
_keyboardFilter.FullScreenToggle += new EventHandler(OnFullScreenToggle);
|
|
|
|
Application.Run();
|
|
}
|
|
|
|
void OnFullScreenToggle(object sender, EventArgs e)
|
|
{
|
|
_fullScreen = !_fullScreen;
|
|
|
|
UpdateScreenMode();
|
|
}
|
|
|
|
public void RenderCurrent(bool completeFrame)
|
|
{
|
|
// Draw the current set of vectors
|
|
_lock.EnterReadLock();
|
|
_frame++;
|
|
|
|
if (_frame == 60)
|
|
{
|
|
double currentTime = _timer.GetCurrentTime();
|
|
double fps = _frame / ((currentTime - _lastTime));
|
|
_lastTime = currentTime;
|
|
_frame = 0;
|
|
|
|
Video.WindowCaption = String.Format("Imlac PDS-1 fps {0}", fps);
|
|
}
|
|
|
|
//
|
|
// If we're drawing a complete frame (not running in debug mode)
|
|
// fade out the last frame by drawing an alpha-blended black rectangle over the display.
|
|
// (slow persistence phosphor simulation!)
|
|
// Otherwise clear the display completely.
|
|
//
|
|
_displayBox.Draw(_displaySurface, completeFrame ? Color.FromArgb(32, Color.Black) : Color.Black, false, true);
|
|
|
|
// And draw in this frame's vectors
|
|
for (int i = 0; i < _displayListIndex; i++)
|
|
{
|
|
_displayList[i].Draw(_displaySurface);
|
|
}
|
|
|
|
_lock.ExitReadLock();
|
|
|
|
_swapLock.EnterReadLock();
|
|
Video.Screen.Blit(_displaySurface);
|
|
|
|
if (completeFrame)
|
|
{
|
|
_displayListIndex = 0;
|
|
}
|
|
|
|
Video.Screen.Update();
|
|
_swapLock.ExitReadLock();
|
|
}
|
|
|
|
private void AddNewVector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
|
|
{
|
|
//
|
|
// Scale the vector to the current scaling factor.
|
|
// The Imlac specifies 11 bits of resolution (2048 points in X and Y)
|
|
// which corresponds to a _scaleFactor of 1.0.
|
|
//
|
|
startX = (uint)(startX * _scaleFactor);
|
|
startY = (uint)(startY *_scaleFactor);
|
|
endX = (uint)(endX * _scaleFactor);
|
|
endY = (uint)(endY * _scaleFactor);
|
|
|
|
_lock.EnterWriteLock();
|
|
|
|
Vector newVector = _displayList[_displayListIndex];
|
|
newVector.Modify(mode, (short)startX, (short)(_yResolution - startY), (short)endX, (short)(_yResolution - endY));
|
|
_displayListIndex++;
|
|
|
|
_lock.ExitWriteLock();
|
|
}
|
|
|
|
private void UpdateScreenMode()
|
|
{
|
|
_swapLock.EnterWriteLock();
|
|
Video.SetVideoMode((int)_xResolution, (int)_yResolution, 32, false, false, _fullScreen, true);
|
|
_displaySurface = Video.Screen.CreateCompatibleSurface((int)_xResolution, (int)_yResolution, true);
|
|
_swapLock.ExitWriteLock();
|
|
}
|
|
|
|
private class Vector
|
|
{
|
|
public Vector(DrawingMode mode, int thickness, uint startX, uint startY, uint endX, uint endY)
|
|
{
|
|
_mode = mode;
|
|
_lines = new SdlDotNet.Graphics.Primitives.Line[thickness];
|
|
|
|
for (int i = 0; i < thickness; i++)
|
|
{
|
|
_lines[i] = new SdlDotNet.Graphics.Primitives.Line((short)(startX + i), (short)(startY + i), (short)(endX + i), (short)(endY + i));
|
|
}
|
|
|
|
UpdateColor();
|
|
}
|
|
|
|
public void Modify(DrawingMode mode, short startX, short startY, short endX, short endY)
|
|
{
|
|
if (_mode != mode)
|
|
{
|
|
_mode = mode;
|
|
UpdateColor();
|
|
}
|
|
|
|
for (int i = 0; i < _lines.Length; i++)
|
|
{
|
|
_lines[i].XPosition1 = (short)(startX + i);
|
|
_lines[i].XPosition2 = (short)(endX + i);
|
|
_lines[i].YPosition1 = (short)(startY + i);
|
|
_lines[i].YPosition2 = (short)(endY + i);
|
|
}
|
|
}
|
|
|
|
public void Draw(Surface displaySurface)
|
|
{
|
|
// TODO: handle dotted lines, line thickness options
|
|
for (int i = 0; i < _lines.Length; i++)
|
|
{
|
|
_lines[i].Draw(displaySurface, _color, true);
|
|
}
|
|
}
|
|
|
|
private void UpdateColor()
|
|
{
|
|
switch (_mode)
|
|
{
|
|
case DrawingMode.Dotted:
|
|
case DrawingMode.Normal:
|
|
_color = NormalColor;
|
|
break;
|
|
|
|
case DrawingMode.Point:
|
|
_color = PointColor;
|
|
break;
|
|
|
|
case DrawingMode.SGR1:
|
|
_color = SGRColor;
|
|
break;
|
|
|
|
case DrawingMode.Debug:
|
|
_color = DebugColor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private DrawingMode _mode;
|
|
private SdlDotNet.Graphics.Primitives.Line[] _lines;
|
|
private Color _color;
|
|
|
|
private static Color NormalColor = Color.FromArgb(196, Color.ForestGreen);
|
|
private static Color PointColor = Color.FromArgb(255, Color.ForestGreen);
|
|
private static Color SGRColor = Color.FromArgb(128, Color.ForestGreen);
|
|
private static Color DebugColor = Color.FromArgb(255, Color.OrangeRed);
|
|
}
|
|
|
|
|
|
private System.Threading.Thread _displayThread;
|
|
private Surface _displaySurface;
|
|
private SdlDotNet.Graphics.Primitives.Box _displayBox;
|
|
|
|
private ManualResetEvent _initEvent;
|
|
|
|
private uint _x;
|
|
private uint _y;
|
|
|
|
private int _xResolution;
|
|
private int _yResolution;
|
|
private float _scaleFactor;
|
|
|
|
private bool _fullScreen;
|
|
private bool _throttleFramerate;
|
|
private bool _dataSwitchMappingEnabled;
|
|
|
|
private int _displayListIndex;
|
|
private List<Vector> _displayList;
|
|
private const int _displayListSize = 100000; // Considerably more than a real Imlac could ever hope to draw in a single frame.
|
|
|
|
private System.Threading.ReaderWriterLockSlim _lock;
|
|
private System.Threading.ReaderWriterLockSlim _swapLock;
|
|
|
|
// keyboard input data
|
|
private KeyboardFilter _keyboardFilter;
|
|
|
|
private uint _frame;
|
|
private double _lastTime;
|
|
|
|
// Framerate management
|
|
FrameTimer _frameTimer;
|
|
HighResTimer _timer;
|
|
|
|
}
|
|
}
|