diff --git a/Assets/dll/OpenTK.dll.config b/Assets/dll/OpenTK.dll.config
deleted file mode 100755
index 7098d39e9c2..00000000000
--- a/Assets/dll/OpenTK.dll.config
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Assets/dll/SDL2.dll b/Assets/dll/SDL2.dll
index b4b67c0e778..935c1ea5349 100644
Binary files a/Assets/dll/SDL2.dll and b/Assets/dll/SDL2.dll differ
diff --git a/Assets/dll/Silk.NET.Core.dll.config b/Assets/dll/Silk.NET.Core.dll.config
new file mode 100644
index 00000000000..2ffd20170a4
--- /dev/null
+++ b/Assets/dll/Silk.NET.Core.dll.config
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Assets/dll/libSDL2.so b/Assets/dll/libSDL2.so
new file mode 100644
index 00000000000..55eeb1f939e
Binary files /dev/null and b/Assets/dll/libSDL2.so differ
diff --git a/BizHawk.sln b/BizHawk.sln
index ff76d747e51..0b8b899cad8 100644
--- a/BizHawk.sln
+++ b/BizHawk.sln
@@ -1,6 +1,7 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28729.10
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 16.0.28729.10
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Client.Common", "src\BizHawk.Client.Common\BizHawk.Client.Common.csproj", "{24A0AA3C-B25F-4197-B23D-476D6462DBA0}"
EndProject
@@ -20,11 +21,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Emulation", "Emulation", "{
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Emulation.Cores", "src\BizHawk.Emulation.Cores\BizHawk.Emulation.Cores.csproj", "{197D4314-8A9F-49BA-977D-54ACEFAEB6BA}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Bizware.BizwareGL", "src\BizHawk.Bizware.BizwareGL\BizHawk.Bizware.BizwareGL.csproj", "{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Bizware.Audio", "src\BizHawk.Bizware.Audio\BizHawk.Bizware.Audio.csproj", "{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.DirectX", "src\BizHawk.Bizware.DirectX\BizHawk.Bizware.DirectX.csproj", "{A914D063-9E4B-4086-B156-7B3F39E33DB2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Bizware.Input", "src\BizHawk.Bizware.Input\BizHawk.Bizware.Input.csproj", "{17E7D20D-198C-4728-ACEC-065DE834FF02}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.OpenTK3", "src\BizHawk.Bizware.OpenTK3\BizHawk.Bizware.OpenTK3.csproj", "{1FF433CC-96E1-4F14-B673-CDA7190169C9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Bizware.Graphics", "src\BizHawk.Bizware.Graphics\BizHawk.Bizware.Graphics.csproj", "{368BC91D-48CD-492A-B6CF-B5B77F7FE7D4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.Bizware.BizwareGL", "src\BizHawk.Bizware.BizwareGL\BizHawk.Bizware.BizwareGL.csproj", "{658BB7AA-74A1-496F-A6AA-B7D3DD9C7CBE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BizHawk.BizInvoke", "src\BizHawk.BizInvoke\BizHawk.BizInvoke.csproj", "{E5D76DC1-84A8-47AF-BE25-E76F06D2FBBC}"
EndProject
@@ -72,14 +75,18 @@ Global
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}.Release|Any CPU.Build.0 = Release|Any CPU
- {A914D063-9E4B-4086-B156-7B3F39E33DB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A914D063-9E4B-4086-B156-7B3F39E33DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A914D063-9E4B-4086-B156-7B3F39E33DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A914D063-9E4B-4086-B156-7B3F39E33DB2}.Release|Any CPU.Build.0 = Release|Any CPU
- {1FF433CC-96E1-4F14-B673-CDA7190169C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1FF433CC-96E1-4F14-B673-CDA7190169C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1FF433CC-96E1-4F14-B673-CDA7190169C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1FF433CC-96E1-4F14-B673-CDA7190169C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {17E7D20D-198C-4728-ACEC-065DE834FF02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17E7D20D-198C-4728-ACEC-065DE834FF02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17E7D20D-198C-4728-ACEC-065DE834FF02}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17E7D20D-198C-4728-ACEC-065DE834FF02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {368BC91D-48CD-492A-B6CF-B5B77F7FE7D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {368BC91D-48CD-492A-B6CF-B5B77F7FE7D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {368BC91D-48CD-492A-B6CF-B5B77F7FE7D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {368BC91D-48CD-492A-B6CF-B5B77F7FE7D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {658BB7AA-74A1-496F-A6AA-B7D3DD9C7CBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {658BB7AA-74A1-496F-A6AA-B7D3DD9C7CBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {658BB7AA-74A1-496F-A6AA-B7D3DD9C7CBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {658BB7AA-74A1-496F-A6AA-B7D3DD9C7CBE}.Release|Any CPU.Build.0 = Release|Any CPU
{E5D76DC1-84A8-47AF-BE25-E76F06D2FBBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5D76DC1-84A8-47AF-BE25-E76F06D2FBBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5D76DC1-84A8-47AF-BE25-E76F06D2FBBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/References/x64/SlimDX.dll b/References/x64/SlimDX.dll
deleted file mode 100644
index d2818f8ca4d..00000000000
Binary files a/References/x64/SlimDX.dll and /dev/null differ
diff --git a/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj b/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj
new file mode 100644
index 00000000000..2e9767c982b
--- /dev/null
+++ b/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj
@@ -0,0 +1,19 @@
+
+
+ netstandard2.0
+
+
+
+ true
+ disable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/BizHawk.Bizware.DirectX/DirectSoundSoundOutput.cs b/src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs
similarity index 67%
rename from src/BizHawk.Bizware.DirectX/DirectSoundSoundOutput.cs
rename to src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs
index 099f9aa9649..7a42a29f5de 100644
--- a/src/BizHawk.Bizware.DirectX/DirectSoundSoundOutput.cs
+++ b/src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs
@@ -2,13 +2,15 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Threading;
using BizHawk.Client.Common;
-using SlimDX.DirectSound;
-using SlimDX.Multimedia;
+using SharpDX;
+using SharpDX.DirectSound;
+using SharpDX.Multimedia;
-namespace BizHawk.Bizware.DirectX
+namespace BizHawk.Bizware.Audio
{
public sealed class DirectSoundSoundOutput : ISoundOutput
{
@@ -27,8 +29,8 @@ public DirectSoundSoundOutput(IHostAudioManager sound, IntPtr mainWindowHandle,
_sound = sound;
_retryCounter = 5;
- var deviceInfo = DirectSound.GetDevices().FirstOrDefault(d => d.Description == soundDevice);
- _device = deviceInfo != null ? new DirectSound(deviceInfo.DriverGuid) : new DirectSound();
+ var deviceInfo = DirectSound.GetDevices().Find(d => d.Description == soundDevice);
+ _device = deviceInfo != null ? new(deviceInfo.DriverGuid) : new();
_device.SetCooperativeLevel(mainWindowHandle, CooperativeLevel.Priority);
}
@@ -53,7 +55,9 @@ public static IEnumerable GetDeviceNames()
public int MaxSamplesDeficit { get; private set; }
- private bool IsPlaying => _deviceBuffer != null && (_deviceBuffer.Status & BufferStatus.BufferLost) == 0 && (_deviceBuffer.Status & BufferStatus.Playing) == BufferStatus.Playing;
+ private bool IsPlaying => _deviceBuffer != null &&
+ ((BufferStatus)_deviceBuffer.Status & BufferStatus.BufferLost) == 0 &&
+ ((BufferStatus)_deviceBuffer.Status & BufferStatus.Playing) == BufferStatus.Playing;
private void StartPlaying()
{
@@ -61,7 +65,7 @@ private void StartPlaying()
_filledBufferSizeBytes = 0;
_lastWriteTime = 0;
_lastWriteCursor = 0;
- int attempts = _retryCounter;
+ var attempts = _retryCounter;
while (!IsPlaying && attempts > 0)
{
attempts--;
@@ -69,15 +73,13 @@ private void StartPlaying()
{
if (_deviceBuffer == null)
{
- var format = new WaveFormat
- {
- SamplesPerSecond = _sound.SampleRate,
- BitsPerSample = (short) (_sound.BytesPerSample * 8),
- Channels = (short) _sound.ChannelCount,
- FormatTag = WaveFormatTag.Pcm,
- BlockAlignment = (short) _sound.BlockAlign,
- AverageBytesPerSecond = _sound.SampleRate * _sound.BlockAlign
- };
+ var format = WaveFormat.CreateCustomFormat(
+ tag: WaveFormatEncoding.Pcm,
+ sampleRate: _sound.SampleRate,
+ channels: _sound.ChannelCount,
+ averageBytesPerSecond: _sound.SampleRate * _sound.BlockAlign,
+ blockAlign: _sound.BlockAlign,
+ bitsPerSample: _sound.BytesPerSample * 8);
var desc = new SoundBufferDescription
{
@@ -87,23 +89,20 @@ private void StartPlaying()
BufferFlags.Software |
BufferFlags.GetCurrentPosition2 |
BufferFlags.ControlVolume,
- SizeInBytes = BufferSizeBytes
+ BufferBytes = BufferSizeBytes
};
- _deviceBuffer = new SecondarySoundBuffer(_device, desc);
+ _deviceBuffer = new(_device, desc);
}
_deviceBuffer.Play(0, PlayFlags.Looping);
}
- catch (DirectSoundException)
+ catch (SharpDXException)
{
- if (_deviceBuffer != null)
- {
- _deviceBuffer.Restore();
- }
+ _deviceBuffer?.Restore();
if (attempts > 0)
{
- System.Threading.Thread.Sleep(10);
+ Thread.Sleep(10);
}
}
}
@@ -125,10 +124,10 @@ public void ApplyVolumeSettings(double volume)
try
{
// I'm not sure if this is "technically" correct but it works okay
- int range = (int)Volume.Maximum - (int)Volume.Minimum;
- _deviceBuffer.Volume = (int)(Math.Pow(volume, 0.1) * range) + (int)Volume.Minimum;
+ const int range = Volume.Maximum - Volume.Minimum;
+ _deviceBuffer.Volume = (int)(Math.Pow(volume, 0.1) * range) + Volume.Minimum;
}
- catch (DirectSoundException)
+ catch (SharpDXException)
{
}
}
@@ -144,7 +143,7 @@ public void StartSound()
// severe glitches. At least on my Windows 8 machines, the distance between the
// play and write cursors can be up to 30 milliseconds, so that would be the
// absolute minimum we could use here.
- int minBufferFullnessMs = Math.Min(35 + ((_sound.ConfigBufferSizeMs - 60) / 2), 65);
+ var minBufferFullnessMs = Math.Min(35 + (_sound.ConfigBufferSizeMs - 60) / 2, 65);
MaxSamplesDeficit = BufferSizeSamples - _sound.MillisecondsToSamples(minBufferFullnessMs);
StartPlaying();
@@ -158,10 +157,11 @@ public void StopSound()
{
_deviceBuffer.Stop();
}
- catch (DirectSoundException)
+ catch (SharpDXException)
{
}
}
+
_deviceBuffer.Dispose();
_deviceBuffer = null;
BufferSizeSamples = 0;
@@ -169,21 +169,20 @@ public void StopSound()
public int CalculateSamplesNeeded()
{
- int samplesNeeded = 0;
+ var samplesNeeded = 0;
if (IsPlaying)
{
try
{
- long currentWriteTime = Stopwatch.GetTimestamp();
- int playCursor = _deviceBuffer.CurrentPlayPosition;
- int writeCursor = _deviceBuffer.CurrentWritePosition;
- bool isInitializing = _actualWriteOffsetBytes == -1;
- bool detectedUnderrun = false;
+ var currentWriteTime = Stopwatch.GetTimestamp();
+ _deviceBuffer.GetCurrentPosition(out var playCursor, out var writeCursor);
+ var isInitializing = _actualWriteOffsetBytes == -1;
+ var detectedUnderrun = false;
if (!isInitializing)
{
- double elapsedSeconds = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency;
- double bufferSizeSeconds = (double) BufferSizeSamples / _sound.SampleRate;
- int cursorDelta = CircularDistance(_lastWriteCursor, writeCursor, BufferSizeBytes);
+ var elapsedSeconds = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency;
+ var bufferSizeSeconds = (double) BufferSizeSamples / _sound.SampleRate;
+ var cursorDelta = CircularDistance(_lastWriteCursor, writeCursor, BufferSizeBytes);
cursorDelta += BufferSizeBytes * (int) Math.Round((elapsedSeconds - (cursorDelta / (double) (_sound.SampleRate * _sound.BlockAlign))) / bufferSizeSeconds);
_filledBufferSizeBytes -= cursorDelta;
detectedUnderrun = _filledBufferSizeBytes < 0;
@@ -201,7 +200,7 @@ public int CalculateSamplesNeeded()
_lastWriteTime = currentWriteTime;
_lastWriteCursor = writeCursor;
}
- catch (DirectSoundException)
+ catch (SharpDXException)
{
samplesNeeded = 0;
}
@@ -209,7 +208,7 @@ public int CalculateSamplesNeeded()
return samplesNeeded;
}
- private int CircularDistance(int start, int end, int size)
+ private static int CircularDistance(int start, int end, int size)
{
return (end - start + size) % size;
}
@@ -227,7 +226,7 @@ public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
_actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * _sound.BlockAlign)) % BufferSizeBytes;
_filledBufferSizeBytes += sampleCount * _sound.BlockAlign;
}
- catch (DirectSoundException)
+ catch (SharpDXException)
{
_deviceBuffer.Restore();
StartPlaying();
@@ -235,10 +234,7 @@ public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
}
else
{
- if (_deviceBuffer != null)
- {
- _deviceBuffer.Restore();
- }
+ _deviceBuffer?.Restore();
StartPlaying();
}
}
diff --git a/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs b/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs
new file mode 100644
index 00000000000..fc9eff3af3b
--- /dev/null
+++ b/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using BizHawk.Client.Common;
+
+using Silk.NET.Core.Native;
+using Silk.NET.OpenAL;
+using Silk.NET.OpenAL.Extensions.Creative;
+using Silk.NET.OpenAL.Extensions.Enumeration;
+
+namespace BizHawk.Bizware.Audio
+{
+ public class OpenALSoundOutput : ISoundOutput
+ {
+ private static readonly AL _al = AL.GetApi();
+ private static readonly ALContext _alc = ALContext.GetApi();
+
+ private static unsafe T GetExtensionOrNull() where T : NativeExtension
+ => _alc.TryGetExtension(null, out var ext) ? ext : null;
+
+ private static readonly EnumerateAll _enumAllExt = GetExtensionOrNull();
+ private static readonly Enumeration _enumExt = GetExtensionOrNull();
+
+ private bool _disposed;
+ private readonly IHostAudioManager _sound;
+ private AudioContext _context;
+ private uint _sourceID;
+ private BufferPool _bufferPool;
+ private int _currentSamplesQueued;
+ private short[] _tempSampleBuffer;
+
+ public OpenALSoundOutput(IHostAudioManager sound, string chosenDeviceName)
+ {
+ _sound = sound;
+ _context = new(
+ GetDeviceNames().Contains(chosenDeviceName) ? chosenDeviceName : null,
+ _sound.SampleRate
+ );
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+
+ _context.Dispose();
+ _context = null;
+
+ _disposed = true;
+ }
+
+ public static IEnumerable GetDeviceNames()
+ => _enumAllExt?.GetStringList(GetEnumerateAllContextStringList.AllDevicesSpecifier)
+ ?? _enumExt?.GetStringList(GetEnumerationContextStringList.DeviceSpecifiers)
+ ?? Enumerable.Empty();
+
+ private int BufferSizeSamples { get; set; }
+
+ public int MaxSamplesDeficit { get; private set; }
+
+ public void ApplyVolumeSettings(double volume)
+ {
+ _al.SetSourceProperty(_sourceID, SourceFloat.Gain, (float)volume);
+ }
+
+ public void StartSound()
+ {
+ BufferSizeSamples = _sound.MillisecondsToSamples(_sound.ConfigBufferSizeMs);
+ MaxSamplesDeficit = BufferSizeSamples;
+
+ _sourceID = _al.GenSource();
+
+ _bufferPool = new();
+ _currentSamplesQueued = 0;
+ }
+
+ public void StopSound()
+ {
+ _al.SourceStop(_sourceID);
+ _al.DeleteSource(_sourceID);
+
+ _bufferPool.Dispose();
+ _bufferPool = null;
+
+ BufferSizeSamples = 0;
+ }
+
+ public int CalculateSamplesNeeded()
+ {
+ var currentSamplesPlayed = GetSource(GetSourceInteger.SampleOffset);
+ var sourceState = GetSourceState();
+ var isInitializing = sourceState == SourceState.Initial;
+ var detectedUnderrun = sourceState == SourceState.Stopped;
+ if (detectedUnderrun)
+ {
+ // SampleOffset should reset to 0 when stopped; update the queued sample count to match
+ UnqueueProcessedBuffers();
+ currentSamplesPlayed = 0;
+ }
+ var samplesAwaitingPlayback = _currentSamplesQueued - currentSamplesPlayed;
+ var samplesNeeded = Math.Max(BufferSizeSamples - samplesAwaitingPlayback, 0);
+ if (isInitializing || detectedUnderrun)
+ {
+ _sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded);
+ }
+ return samplesNeeded;
+ }
+
+ public unsafe void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
+ {
+ if (sampleCount == 0) return;
+ UnqueueProcessedBuffers();
+ var byteCount = sampleCount * _sound.BlockAlign;
+ if (sampleOffset != 0)
+ {
+ AllocateTempSampleBuffer(sampleCount);
+ samples.AsSpan(sampleOffset * _sound.BlockAlign / 2, byteCount / 2)
+ .CopyTo(_tempSampleBuffer);
+ samples = _tempSampleBuffer;
+ }
+ var buffer = _bufferPool.Obtain(byteCount);
+ fixed (short* sptr = samples)
+ {
+ _al.BufferData(buffer.BufferID, BufferFormat.Stereo16, sptr, byteCount, _sound.SampleRate);
+ }
+ var bid = buffer.BufferID;
+ _al.SourceQueueBuffers(_sourceID, 1, &bid);
+ _currentSamplesQueued += sampleCount;
+ if (GetSourceState() != SourceState.Playing)
+ {
+ _al.SourcePlay(_sourceID);
+ }
+ }
+
+ private unsafe void UnqueueProcessedBuffers()
+ {
+ var releaseCount = GetSource(GetSourceInteger.BuffersProcessed);
+ var bids = stackalloc uint[releaseCount];
+ _al.SourceUnqueueBuffers(_sourceID, releaseCount, bids);
+ for (var i = 0; i < releaseCount; i++)
+ {
+ var releasedBuffer = _bufferPool.ReleaseOne();
+ _currentSamplesQueued -= releasedBuffer.Length / _sound.BlockAlign;
+ }
+ }
+
+ private int GetSource(GetSourceInteger param)
+ {
+ _al.GetSourceProperty(_sourceID, param, out var value);
+ return value;
+ }
+
+ private SourceState GetSourceState()
+ => (SourceState)GetSource(GetSourceInteger.SourceState);
+
+ private void AllocateTempSampleBuffer(int sampleCount)
+ {
+ var length = sampleCount * _sound.ChannelCount;
+ if (_tempSampleBuffer == null || _tempSampleBuffer.Length < length)
+ {
+ _tempSampleBuffer = new short[length];
+ }
+ }
+
+ private class BufferPool : IDisposable
+ {
+ private readonly Stack _availableItems = new();
+ private readonly Queue _obtainedItems = new();
+
+ public void Dispose()
+ {
+ foreach (var item in _availableItems.Concat(_obtainedItems))
+ {
+ _al.DeleteBuffer(item.BufferID);
+ }
+ _availableItems.Clear();
+ _obtainedItems.Clear();
+ }
+
+ public BufferPoolItem Obtain(int length)
+ {
+ var item = _availableItems.Count != 0 ? _availableItems.Pop() : new();
+ item.Length = length;
+ _obtainedItems.Enqueue(item);
+ return item;
+ }
+
+ public BufferPoolItem ReleaseOne()
+ {
+ var item = _obtainedItems.Dequeue();
+ _availableItems.Push(item);
+ return item;
+ }
+
+ public class BufferPoolItem
+ {
+ public uint BufferID { get; }
+ public int Length { get; set; }
+
+ public BufferPoolItem()
+ {
+ BufferID = _al.GenBuffer();
+ }
+ }
+ }
+ }
+}
diff --git a/src/BizHawk.Bizware.DirectX/XAudio2SoundOutput.cs b/src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs
similarity index 51%
rename from src/BizHawk.Bizware.DirectX/XAudio2SoundOutput.cs
rename to src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs
index bf0fe176a1a..caf89ffc932 100644
--- a/src/BizHawk.Bizware.DirectX/XAudio2SoundOutput.cs
+++ b/src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs
@@ -4,35 +4,50 @@
using BizHawk.Client.Common;
-using SlimDX;
-using SlimDX.Multimedia;
-using SlimDX.XAudio2;
+using Vortice.MediaFoundation;
+using Vortice.Multimedia;
+using Vortice.XAudio2;
-namespace BizHawk.Bizware.DirectX
+namespace BizHawk.Bizware.Audio
{
public sealed class XAudio2SoundOutput : ISoundOutput
{
private bool _disposed;
private readonly IHostAudioManager _sound;
- private XAudio2 _device;
- private MasteringVoice _masteringVoice;
- private SourceVoice _sourceVoice;
+ private readonly IXAudio2 _device;
+ private readonly IXAudio2MasteringVoice _masteringVoice;
+ private IXAudio2SourceVoice _sourceVoice;
private BufferPool _bufferPool;
private long _runningSamplesQueued;
- public XAudio2SoundOutput(IHostAudioManager sound, string chosenDeviceName)
+ private static string GetDeviceId(string deviceName)
{
- _sound = sound;
- _device = new XAudio2();
- for (int i = 0, l = _device.DeviceCount; i < l; i++)
+ if (string.IsNullOrEmpty(deviceName))
{
- if (_device.GetDeviceDetails(i).DisplayName == chosenDeviceName)
- {
- _masteringVoice = new MasteringVoice(_device, _sound.ChannelCount, _sound.SampleRate, i);
- return;
- }
+ return null;
}
- _masteringVoice = new MasteringVoice(_device, _sound.ChannelCount, _sound.SampleRate);
+
+ using var enumerator = new IMMDeviceEnumerator();
+ var devices = enumerator.EnumAudioEndpoints(DataFlow.Render);
+ var device = devices.FirstOrDefault(capDevice => capDevice.FriendlyName == deviceName);
+ if (device is null)
+ {
+ return null;
+ }
+
+ const string MMDEVAPI_TOKEN = @"\\?\SWD#MMDEVAPI#";
+ const string DEVINTERFACE_AUDIO_RENDER = "#{e6327cad-dcec-4949-ae8a-991e976a79d2}";
+ return $"{MMDEVAPI_TOKEN}{device.Id}{DEVINTERFACE_AUDIO_RENDER}";
+ }
+
+ public XAudio2SoundOutput(IHostAudioManager sound, string chosenDeviceName)
+ {
+ _sound = sound;
+ _device = XAudio2.XAudio2Create();
+ _masteringVoice = _device.CreateMasteringVoice(
+ inputChannels: _sound.ChannelCount,
+ inputSampleRate: _sound.SampleRate,
+ deviceId: GetDeviceId(chosenDeviceName));
}
public void Dispose()
@@ -40,20 +55,16 @@ public void Dispose()
if (_disposed) return;
_masteringVoice.Dispose();
- _masteringVoice = null;
-
_device.Dispose();
- _device = null;
_disposed = true;
}
public static IEnumerable GetDeviceNames()
{
- using XAudio2 device = new XAudio2();
- return Enumerable.Range(0, device.DeviceCount)
- .Select(n => device.GetDeviceDetails(n).DisplayName)
- .ToList(); // enumerate before local var device is disposed
+ using var enumerator = new IMMDeviceEnumerator();
+ var devices = enumerator.EnumAudioEndpoints(DataFlow.Render);
+ return devices.Select(capDevice => capDevice.FriendlyName);
}
private int BufferSizeSamples { get; set; }
@@ -70,19 +81,10 @@ public void StartSound()
BufferSizeSamples = _sound.MillisecondsToSamples(_sound.ConfigBufferSizeMs);
MaxSamplesDeficit = BufferSizeSamples;
- var format = new WaveFormat
- {
- SamplesPerSecond = _sound.SampleRate,
- BitsPerSample = (short) (_sound.BytesPerSample * 8),
- Channels = (short) _sound.ChannelCount,
- FormatTag = WaveFormatTag.Pcm,
- BlockAlignment = (short) _sound.BlockAlign,
- AverageBytesPerSecond = _sound.SampleRate * _sound.BlockAlign
- };
-
- _sourceVoice = new SourceVoice(_device, format);
+ var format = new WaveFormat(_sound.SampleRate, _sound.BytesPerSample * 8, _sound.ChannelCount);
+ _sourceVoice = _device.CreateSourceVoice(format);
- _bufferPool = new BufferPool();
+ _bufferPool = new();
_runningSamplesQueued = 0;
_sourceVoice.Start();
@@ -102,10 +104,10 @@ public void StopSound()
public int CalculateSamplesNeeded()
{
- bool isInitializing = _runningSamplesQueued == 0;
- bool detectedUnderrun = !isInitializing && _sourceVoice.State.BuffersQueued == 0;
- long samplesAwaitingPlayback = _runningSamplesQueued - _sourceVoice.State.SamplesPlayed;
- int samplesNeeded = (int)Math.Max(BufferSizeSamples - samplesAwaitingPlayback, 0);
+ var isInitializing = _runningSamplesQueued == 0;
+ var detectedUnderrun = !isInitializing && _sourceVoice.State.BuffersQueued == 0;
+ var samplesAwaitingPlayback = _runningSamplesQueued - (long)_sourceVoice.State.SamplesPlayed;
+ var samplesNeeded = (int)Math.Max(BufferSizeSamples - samplesAwaitingPlayback, 0);
if (isInitializing || detectedUnderrun)
{
_sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded);
@@ -117,9 +119,10 @@ public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
{
if (sampleCount == 0) return;
_bufferPool.Release(_sourceVoice.State.BuffersQueued);
- int byteCount = sampleCount * _sound.BlockAlign;
+ var byteCount = sampleCount * _sound.BlockAlign;
var item = _bufferPool.Obtain(byteCount);
- Buffer.BlockCopy(samples, sampleOffset * _sound.BlockAlign, item.Bytes, 0, byteCount);
+ samples.AsSpan(sampleOffset * _sound.BlockAlign / 2, byteCount / 2)
+ .CopyTo(item.AudioBuffer.AsSpan());
item.AudioBuffer.AudioBytes = byteCount;
_sourceVoice.SubmitSourceBuffer(item.AudioBuffer);
_runningSamplesQueued += sampleCount;
@@ -127,14 +130,13 @@ public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
private class BufferPool : IDisposable
{
- private readonly List _availableItems = new List();
- private readonly Queue _obtainedItems = new Queue();
+ private readonly List _availableItems = new();
+ private readonly Queue _obtainedItems = new();
public void Dispose()
{
- foreach (BufferPoolItem item in _availableItems.Concat(_obtainedItems))
+ foreach (var item in _availableItems.Concat(_obtainedItems))
{
- item.AudioBuffer.AudioData.Dispose();
item.AudioBuffer.Dispose();
}
_availableItems.Clear();
@@ -143,22 +145,23 @@ public void Dispose()
public BufferPoolItem Obtain(int length)
{
- BufferPoolItem item = GetAvailableItem(length) ?? new BufferPoolItem(length);
+ var item = GetAvailableItem(length) ?? new BufferPoolItem(length);
_obtainedItems.Enqueue(item);
return item;
}
private BufferPoolItem GetAvailableItem(int length)
{
- int foundIndex = -1;
- for (int i = 0; i < _availableItems.Count; i++)
+ var foundIndex = -1;
+ for (var i = 0; i < _availableItems.Count; i++)
{
if (_availableItems[i].MaxLength >= length && (foundIndex == -1 || _availableItems[i].MaxLength < _availableItems[foundIndex].MaxLength))
foundIndex = i;
}
if (foundIndex == -1) return null;
- BufferPoolItem item = _availableItems[foundIndex];
+ var item = _availableItems[foundIndex];
_availableItems.RemoveAt(foundIndex);
+ item.AudioBuffer.AudioBytes = item.MaxLength; // this might have shrunk from earlier use, set it back to MaxLength so AsSpan() works as expected
return item;
}
@@ -171,17 +174,12 @@ public void Release(int buffersQueued)
public class BufferPoolItem
{
public int MaxLength { get; }
- public byte[] Bytes { get; }
public AudioBuffer AudioBuffer { get; }
public BufferPoolItem(int length)
{
MaxLength = length;
- Bytes = new byte[MaxLength];
- AudioBuffer = new AudioBuffer
- {
- AudioData = new DataStream(Bytes, true, false)
- };
+ AudioBuffer = new(length, BufferFlags.None);
}
}
}
diff --git a/src/BizHawk.Bizware.BizwareGL/EDispMethod.cs b/src/BizHawk.Bizware.BizwareGL/EDispMethod.cs
index a6336b21c3c..81211d49c8b 100644
--- a/src/BizHawk.Bizware.BizwareGL/EDispMethod.cs
+++ b/src/BizHawk.Bizware.BizwareGL/EDispMethod.cs
@@ -4,6 +4,6 @@ public enum EDispMethod
{
OpenGL = 0,
GdiPlus = 1,
- SlimDX9 = 2,
+ D3D9 = 2
}
}
diff --git a/src/BizHawk.Bizware.BizwareGL/Texture2d.cs b/src/BizHawk.Bizware.BizwareGL/Texture2d.cs
index c27548c649b..3040ce0a109 100644
--- a/src/BizHawk.Bizware.BizwareGL/Texture2d.cs
+++ b/src/BizHawk.Bizware.BizwareGL/Texture2d.cs
@@ -65,8 +65,8 @@ public void SetFilterNearest()
public int IntWidth => (int)Width;
public int IntHeight => (int)Height;
- public Rectangle Rectangle => new Rectangle(0, 0, IntWidth, IntHeight);
- public Size Size => new Size(IntWidth, IntHeight);
+ public Rectangle Rectangle => new(0, 0, IntWidth, IntHeight);
+ public Size Size => new(IntWidth, IntHeight);
///
/// opengl sucks, man. seriously, screw this (textures from render targets are upside down)
diff --git a/src/BizHawk.Bizware.DirectX/BizHawk.Bizware.DirectX.csproj b/src/BizHawk.Bizware.DirectX/BizHawk.Bizware.DirectX.csproj
deleted file mode 100644
index b07e0aece4f..00000000000
--- a/src/BizHawk.Bizware.DirectX/BizHawk.Bizware.DirectX.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- net48
-
-
-
- true
- disable
-
-
-
-
-
-
-
-
diff --git a/src/BizHawk.Bizware.DirectX/GLControlWrapper_SlimDX9.cs b/src/BizHawk.Bizware.DirectX/GLControlWrapper_SlimDX9.cs
deleted file mode 100644
index fe1c09b6c85..00000000000
--- a/src/BizHawk.Bizware.DirectX/GLControlWrapper_SlimDX9.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System;
-using System.Windows.Forms;
-
-using BizHawk.Bizware.BizwareGL;
-using SlimDX.Direct3D9;
-
-namespace BizHawk.Bizware.DirectX
-{
- public sealed class GLControlWrapperSlimDX9 : Control, IGraphicsControl
- {
- public RenderTargetWrapper RenderTargetWrapper
- {
- get => throw new NotImplementedException();
- set => throw new NotImplementedException();
- }
-
- public GLControlWrapperSlimDX9(IGL_SlimDX9 sdx)
- {
- _sdx = sdx;
-
- SetStyle(ControlStyles.UserPaint, true);
- SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
- SetStyle(ControlStyles.Opaque, true);
- SetStyle(ControlStyles.UserMouse, true);
-
- Resize += GLControlWrapper_SlimDX_Resize;
- }
-
- public bool Vsync;
-
- private void GLControlWrapper_SlimDX_Resize(object sender, EventArgs e)
- {
- _sdx.RefreshControlSwapChain(this);
- }
-
- protected override void Dispose(bool disposing)
- {
- _sdx.FreeControlSwapChain(this);
-
- base.Dispose(disposing);
- }
-
- private readonly IGL_SlimDX9 _sdx;
-
- public Control Control => this;
- public SwapChain SwapChain;
-
- public void SetVsync(bool state)
- {
- Vsync = state;
- _sdx.RefreshControlSwapChain(this);
- }
-
- public void Begin()
- {
- _sdx.BeginControl(this);
- }
-
- public void End()
- {
- _sdx.EndControl(this);
- }
-
- public void SwapBuffers()
- {
- _sdx.SwapControl(this);
- }
- }
-}
\ No newline at end of file
diff --git a/src/BizHawk.Bizware.DirectX/GamePad360.cs b/src/BizHawk.Bizware.DirectX/GamePad360.cs
deleted file mode 100644
index 7b81ace4ac6..00000000000
--- a/src/BizHawk.Bizware.DirectX/GamePad360.cs
+++ /dev/null
@@ -1,250 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-
-using BizHawk.Common;
-using SlimDX.XInput;
-
-namespace BizHawk.Bizware.DirectX
-{
- internal sealed class GamePad360
- {
- // ********************************** Static interface **********************************
-
- private static readonly object SyncObj = new object();
- private static readonly List Devices = new List();
- private static readonly bool IsAvailable;
-
- private delegate uint XInputGetStateExProcDelegate(uint dwUserIndex, out XINPUT_STATE state);
-
- private static readonly XInputGetStateExProcDelegate XInputGetStateExProc;
-
- private struct XINPUT_GAMEPAD
- {
- public ushort wButtons;
- public byte bLeftTrigger;
- public byte bRightTrigger;
- public short sThumbLX;
- public short sThumbLY;
- public short sThumbRX;
- public short sThumbRY;
- }
-
- private struct XINPUT_STATE
- {
- public uint dwPacketNumber;
- public XINPUT_GAMEPAD Gamepad;
- }
-
- static GamePad360()
- {
- try
- {
- // some users won't even have xinput installed. in order to avoid spurious exceptions and possible instability, check for the library first
- var llManager = OSTailoredCode.LinkedLibManager;
- var libraryHandle = llManager.LoadOrZero("xinput1_3.dll");
- if (libraryHandle == IntPtr.Zero) libraryHandle = llManager.LoadOrZero("xinput1_4.dll");
- if (libraryHandle != IntPtr.Zero)
- {
- XInputGetStateExProc = (XInputGetStateExProcDelegate) Marshal.GetDelegateForFunctionPointer(
- Win32Imports.GetProcAddressOrdinal(libraryHandle, new IntPtr(100)),
- typeof(XInputGetStateExProcDelegate)
- );
- }
- else
- {
- libraryHandle = llManager.LoadOrZero("xinput9_1_0.dll");
- }
- IsAvailable = libraryHandle != IntPtr.Zero;
-
- // don't remove this code. it's important to catch errors on systems with broken xinput installs.
- if (IsAvailable) _ = new Controller(UserIndex.One).IsConnected;
- }
- catch
- {
- IsAvailable = false;
- }
- }
-
- public static void Initialize()
- {
- lock (SyncObj)
- {
- Devices.Clear();
-
- if (!IsAvailable)
- return;
-
- //now, at this point, SlimDX may be using one xinput, and we may be using another
- //i'm not sure how SlimDX picks its dll to bind to.
- //i'm not sure how troublesome this will be
- //maybe we should get rid of SlimDX for this altogether
-
- var c1 = new Controller(UserIndex.One);
- var c2 = new Controller(UserIndex.Two);
- var c3 = new Controller(UserIndex.Three);
- var c4 = new Controller(UserIndex.Four);
-
- if (c1.IsConnected) Devices.Add(new GamePad360(0, c1));
- if (c2.IsConnected) Devices.Add(new GamePad360(1, c2));
- if (c3.IsConnected) Devices.Add(new GamePad360(2, c3));
- if (c4.IsConnected) Devices.Add(new GamePad360(3, c4));
- }
- }
-
- public static IEnumerable EnumerateDevices()
- {
- lock (SyncObj)
- {
- foreach (var device in Devices)
- {
- yield return device;
- }
- }
- }
-
- public static void UpdateAll()
- {
- lock (SyncObj)
- {
- foreach (var device in Devices)
- {
- device.Update();
- }
- }
- }
-
- // ********************************** Instance Members **********************************
-
- private readonly Controller _controller;
- private readonly uint _index0;
- private XINPUT_STATE _state;
-
- public int PlayerNumber => (int)_index0 + 1;
- public bool IsConnected => _controller.IsConnected;
- public readonly string InputNamePrefix;
-
- private GamePad360(uint index0, Controller c)
- {
- this._index0 = index0;
- _controller = c;
- InputNamePrefix = $"X{PlayerNumber} ";
- InitializeButtons();
- Update();
- }
-
- public void Update()
- {
- if (_controller.IsConnected == false)
- return;
-
- if (XInputGetStateExProc != null)
- {
- _state = default;
- XInputGetStateExProc(_index0, out _state);
- }
- else
- {
- var slimState = _controller.GetState();
- _state.dwPacketNumber = slimState.PacketNumber;
- _state.Gamepad.wButtons = (ushort)slimState.Gamepad.Buttons;
- _state.Gamepad.sThumbLX = slimState.Gamepad.LeftThumbX;
- _state.Gamepad.sThumbLY = slimState.Gamepad.LeftThumbY;
- _state.Gamepad.sThumbRX = slimState.Gamepad.RightThumbX;
- _state.Gamepad.sThumbRY = slimState.Gamepad.RightThumbY;
- _state.Gamepad.bLeftTrigger = slimState.Gamepad.LeftTrigger;
- _state.Gamepad.bRightTrigger = slimState.Gamepad.RightTrigger;
- }
- }
-
- public IEnumerable<(string AxisID, float Value)> GetAxes()
- {
- var g = _state.Gamepad;
-
- //constant for adapting a +/- 32768 range to a +/-10000-based range
- const float f = 32768 / 10000.0f;
-
- //since our whole input framework really only understands whole axes, let's make the triggers look like an axis
- float lTrig = g.bLeftTrigger / 255.0f * 2 - 1;
- float rTrig = g.bRightTrigger / 255.0f * 2 - 1;
- lTrig *= 10000;
- rTrig *= 10000;
-
- yield return ("LeftThumbX", g.sThumbLX / f);
- yield return ("LeftThumbY", g.sThumbLY / f);
- yield return ("RightThumbX", g.sThumbRX / f);
- yield return ("RightThumbY", g.sThumbRY / f);
- yield return ("LeftTrigger", lTrig);
- yield return ("RightTrigger", rTrig);
- }
-
- public int NumButtons { get; private set; }
-
- private readonly List _names = new List();
- private readonly List> _actions = new List>();
-
- private void InitializeButtons()
- {
- const int dzp = 20000;
- const int dzn = -20000;
- const int dzt = 40;
-
- AddItem("A", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.A) != 0);
- AddItem("B", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.B) != 0);
- AddItem("X", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.X) != 0);
- AddItem("Y", () => (_state.Gamepad.wButtons & unchecked((ushort)GamepadButtonFlags.Y)) != 0);
- AddItem("Guide", () => (_state.Gamepad.wButtons & 1024) != 0);
-
- AddItem("Start", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.Start) != 0);
- AddItem("Back", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.Back) != 0);
- AddItem("LeftThumb", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.LeftThumb) != 0);
- AddItem("RightThumb", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.RightThumb) != 0);
- AddItem("LeftShoulder", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.LeftShoulder) != 0);
- AddItem("RightShoulder", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.RightShoulder) != 0);
-
- AddItem("DpadUp", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.DPadUp) != 0);
- AddItem("DpadDown", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.DPadDown) != 0);
- AddItem("DpadLeft", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.DPadLeft) != 0);
- AddItem("DpadRight", () => (_state.Gamepad.wButtons & (ushort)GamepadButtonFlags.DPadRight) != 0);
-
- AddItem("LStickUp", () => _state.Gamepad.sThumbLY >= dzp);
- AddItem("LStickDown", () => _state.Gamepad.sThumbLY <= dzn);
- AddItem("LStickLeft", () => _state.Gamepad.sThumbLX <= dzn);
- AddItem("LStickRight", () => _state.Gamepad.sThumbLX >= dzp);
-
- AddItem("RStickUp", () => _state.Gamepad.sThumbRY >= dzp);
- AddItem("RStickDown", () => _state.Gamepad.sThumbRY <= dzn);
- AddItem("RStickLeft", () => _state.Gamepad.sThumbRX <= dzn);
- AddItem("RStickRight", () => _state.Gamepad.sThumbRX >= dzp);
-
- AddItem("LeftTrigger", () => _state.Gamepad.bLeftTrigger > dzt);
- AddItem("RightTrigger", () => _state.Gamepad.bRightTrigger > dzt);
- }
-
- private void AddItem(string name, Func pressed)
- {
- _names.Add(name);
- _actions.Add(pressed);
- NumButtons++;
- }
-
- public string ButtonName(int index) => _names[index];
-
- public bool Pressed(int index) => _actions[index]();
-
- /// and are in 0..
- public void SetVibration(int left, int right)
- {
- static ushort Conv(int i) => unchecked((ushort) ((i >> 15) & 0xFFFF));
-
- try
- {
- _controller.SetVibration(new() { LeftMotorSpeed = Conv(left), RightMotorSpeed = Conv(right) });
- }
- catch (XInputException)
- {
- // Ignored, most likely the controller disconnected
- }
- }
- }
-}
diff --git a/src/BizHawk.Bizware.DirectX/IGL_SlimDX9.cs b/src/BizHawk.Bizware.DirectX/IGL_SlimDX9.cs
deleted file mode 100644
index f82081d145f..00000000000
--- a/src/BizHawk.Bizware.DirectX/IGL_SlimDX9.cs
+++ /dev/null
@@ -1,972 +0,0 @@
-using System;
-using System.IO;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Threading;
-
-using BizHawk.Bizware.BizwareGL;
-using BizHawk.Bizware.OpenTK3;
-using BizHawk.Common;
-using BizHawk.Common.StringExtensions;
-
-using SlimDX.Direct3D9;
-
-using PrimitiveType = SlimDX.Direct3D9.PrimitiveType;
-using sd = System.Drawing;
-using sdi = System.Drawing.Imaging;
-using swf = System.Windows.Forms;
-
-// todo - do a better job selecting shader model? base on caps somehow? try several and catch compilation exceptions (yuck, exceptions)
-namespace BizHawk.Bizware.DirectX
-{
- public sealed class IGL_SlimDX9 : IGL
- {
- public EDispMethod DispMethodEnum => EDispMethod.SlimDX9;
-
- private const int D3DERR_DEVICELOST = -2005530520;
- private const int D3DERR_DEVICENOTRESET = -2005530519;
-
- private static Direct3D _d3d;
- internal Device Dev;
- private readonly OpenTK.INativeWindow _offscreenNativeWindow;
-
- // rendering state
- private IntPtr _pVertexData;
- private Pipeline _currPipeline;
- private GLControlWrapperSlimDX9 _currentControl;
-
- public string API => "D3D9";
-
- public IGL_SlimDX9()
- {
- if (_d3d == null)
- {
- _d3d = new Direct3D();
- }
-
- OpenTKConfigurator.EnsureConfigurated();
-
- // make an 'offscreen context' so we can at least do things without having to create a window
- _offscreenNativeWindow = new OpenTK.NativeWindow { ClientSize = new Size(8, 8) };
-
- CreateDevice();
- CreateRenderStates();
- }
-
- public void AlternateVsyncPass(int pass)
- {
- for (; ; )
- {
- var status = Dev.GetRasterStatus(0);
- if (status.InVBlank && pass == 0) return; // wait for vblank to begin
- if (!status.InVBlank && pass == 1) return; // wait for vblank to end
- // STOP! think you can use System.Threading.SpinWait? No, it's way too slow.
- // (on my system, the vblank is something like 24 of 1074 scanlines @ 60hz ~= 0.35ms which is an awfully small window to nail)
- }
- }
-
- private void DestroyDevice()
- {
- if (Dev != null)
- {
- Dev.Dispose();
- Dev = null;
- }
- }
-
- private PresentParameters MakePresentParameters()
- {
- return new PresentParameters
- {
- BackBufferWidth = 8,
- BackBufferHeight = 8,
- BackBufferCount = 2,
- DeviceWindowHandle = _offscreenNativeWindow.WindowInfo.Handle,
- PresentationInterval = PresentInterval.Immediate,
- EnableAutoDepthStencil = false
- };
- }
-
- private void ResetDevice(GLControlWrapperSlimDX9 control)
- {
- SuspendRenderTargets();
- FreeControlSwapChain(control);
- for (; ; )
- {
- var result = Dev.TestCooperativeLevel();
- if (result.IsSuccess)
- break;
- if (result.Code == D3DERR_DEVICENOTRESET)
- {
- try
- {
- var pp = MakePresentParameters();
- Dev.Reset(pp);
- break;
- }
- catch { }
- }
-
- Thread.Sleep(100);
- }
-
- RefreshControlSwapChain(control);
- ResumeRenderTargets();
- }
-
- public void CreateDevice()
- {
- DestroyDevice();
-
- var pp = MakePresentParameters();
-
- var flags = CreateFlags.SoftwareVertexProcessing;
- if ((_d3d.GetDeviceCaps(0, DeviceType.Hardware).DeviceCaps & DeviceCaps.HWTransformAndLight) != 0)
- {
- flags = CreateFlags.HardwareVertexProcessing;
- }
-
- flags |= CreateFlags.FpuPreserve;
- Dev = new Device(_d3d, 0, DeviceType.Hardware, pp.DeviceWindowHandle, flags, pp);
- }
-
- void IDisposable.Dispose()
- {
- DestroyDevice();
- _d3d.Dispose();
- }
-
- public void Clear(ClearBufferMask mask)
- {
- ClearFlags flags = ClearFlags.None;
- if ((mask & ClearBufferMask.ColorBufferBit) != 0) flags |= ClearFlags.Target;
- if ((mask & ClearBufferMask.DepthBufferBit) != 0) flags |= ClearFlags.ZBuffer;
- if ((mask & ClearBufferMask.StencilBufferBit) != 0) flags |= ClearFlags.Stencil;
- Dev.Clear(flags, _clearColor, 0.0f, 0);
- }
-
- private int _clearColor;
- public void SetClearColor(Color color)
- {
- _clearColor = color.ToArgb();
- }
-
- public IBlendState CreateBlendState(
- BlendingFactorSrc colorSource,
- BlendEquationMode colorEquation,
- BlendingFactorDest colorDest,
- BlendingFactorSrc alphaSource,
- BlendEquationMode alphaEquation,
- BlendingFactorDest alphaDest)
- {
- return new CacheBlendState(true, colorSource, colorEquation, colorDest, alphaSource, alphaEquation, alphaDest);
- }
-
- public void FreeTexture(Texture2d tex) {
- var tw = (TextureWrapper)tex.Opaque;
- tw.Texture.Dispose();
- }
-
- private class ShaderWrapper // Disposable fields cleaned up by Internal_FreeShader
- {
- public ShaderBytecode bytecode;
- public VertexShader vs;
- public PixelShader ps;
- public Shader IGLShader;
- }
-
- /// is and compilation error occurred
- public Shader CreateFragmentShader(string source, string entry, bool required)
- {
- try
- {
- var sw = new ShaderWrapper();
-
- string errors = null;
- ShaderBytecode byteCode;
-
- try
- {
- string profile = "ps_3_0";
-
- // ShaderFlags.EnableBackwardsCompatibility - used this once upon a time (please leave a note about why)
- byteCode = ShaderBytecode.Compile(source, null, null, entry, profile, ShaderFlags.UseLegacyD3DX9_31Dll, out errors);
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException($"Error compiling shader: {errors}", ex);
- }
-
- sw.ps = new PixelShader(Dev, byteCode);
- sw.bytecode = byteCode;
-
- Shader s = new Shader(this, sw, true);
- sw.IGLShader = s;
-
- return s;
- }
- catch (Exception ex)
- {
- if (required)
- throw;
- var s = new Shader(this, null, false) { Errors = ex.ToString() };
- return s;
- }
- }
-
- /// is and compilation error occurred
- public Shader CreateVertexShader(string source, string entry, bool required)
- {
- try
- {
- var sw = new ShaderWrapper();
- string errors = null;
- ShaderBytecode byteCode;
-
- try
- {
- string profile = "vs_3_0";
- byteCode = ShaderBytecode.Compile(source, null, null, entry, profile, ShaderFlags.EnableBackwardsCompatibility, out errors);
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException($"Error compiling shader: {errors}", ex);
- }
-
- sw.vs = new VertexShader(Dev, byteCode);
- sw.bytecode = byteCode;
-
- Shader s = new Shader(this, sw, true);
- sw.IGLShader = s;
- return s;
- }
- catch(Exception ex)
- {
- if (required)
- throw;
- var s = new Shader(this, null, false) { Errors = ex.ToString() };
- return s;
- }
- }
-
- private BlendOperation ConvertBlendOp(BlendEquationMode glMode)
- {
- return glMode switch
- {
- BlendEquationMode.FuncAdd => BlendOperation.Add,
- BlendEquationMode.FuncSubtract => BlendOperation.Subtract,
- BlendEquationMode.Max => BlendOperation.Maximum,
- BlendEquationMode.Min => BlendOperation.Minimum,
- BlendEquationMode.FuncReverseSubtract => BlendOperation.ReverseSubtract,
- _ => throw new InvalidOperationException()
- };
- }
-
- private Blend ConvertBlendArg(BlendingFactorDest glMode) => ConvertBlendArg((BlendingFactorSrc) glMode);
-
- private Blend ConvertBlendArg(BlendingFactorSrc glMode) => glMode switch
- {
- BlendingFactorSrc.Zero => Blend.Zero,
- BlendingFactorSrc.One => Blend.One,
- BlendingFactorSrc.SrcColor => Blend.SourceColor,
- BlendingFactorSrc.OneMinusSrcColor => Blend.InverseSourceColor,
- BlendingFactorSrc.SrcAlpha => Blend.SourceAlpha,
- BlendingFactorSrc.OneMinusSrcAlpha => Blend.InverseSourceAlpha,
- BlendingFactorSrc.DstAlpha => Blend.DestinationAlpha,
- BlendingFactorSrc.OneMinusDstAlpha => Blend.InverseDestinationAlpha,
- BlendingFactorSrc.DstColor => Blend.DestinationColor,
- BlendingFactorSrc.OneMinusDstColor => Blend.InverseDestinationColor,
- BlendingFactorSrc.SrcAlphaSaturate => Blend.SourceAlphaSaturated,
- BlendingFactorSrc.ConstantColor => Blend.BlendFactor,
- BlendingFactorSrc.OneMinusConstantColor => Blend.InverseBlendFactor,
- BlendingFactorSrc.ConstantAlpha => throw new NotSupportedException(),
- BlendingFactorSrc.OneMinusConstantAlpha => throw new NotSupportedException(),
- BlendingFactorSrc.Src1Alpha => throw new NotSupportedException(),
- BlendingFactorSrc.Src1Color => throw new NotSupportedException(),
- BlendingFactorSrc.OneMinusSrc1Color => throw new NotSupportedException(),
- BlendingFactorSrc.OneMinusSrc1Alpha => throw new NotSupportedException(),
- _ => throw new InvalidOperationException()
- };
-
- public void SetBlendState(IBlendState rsBlend)
- {
- var myBs = (CacheBlendState)rsBlend;
- if (myBs.Enabled)
- {
- Dev.SetRenderState(RenderState.AlphaBlendEnable, true);
- Dev.SetRenderState(RenderState.SeparateAlphaBlendEnable, true);
-
- Dev.SetRenderState(RenderState.BlendOperation, ConvertBlendOp(myBs.colorEquation));
- Dev.SetRenderState(RenderState.SourceBlend, ConvertBlendArg(myBs.colorSource));
- Dev.SetRenderState(RenderState.DestinationBlend, ConvertBlendArg(myBs.colorDest));
-
- Dev.SetRenderState(RenderState.BlendOperationAlpha, ConvertBlendOp(myBs.alphaEquation));
- Dev.SetRenderState(RenderState.SourceBlendAlpha, ConvertBlendArg(myBs.alphaSource));
- Dev.SetRenderState(RenderState.DestinationBlendAlpha, ConvertBlendArg(myBs.alphaDest));
- }
- else Dev.SetRenderState(RenderState.AlphaBlendEnable, false);
- if (rsBlend == _rsBlendNoneOpaque)
- {
- // make sure constant color is set correctly
- Dev.SetRenderState(RenderState.BlendFactor, -1); // white
- }
- }
-
- private void CreateRenderStates()
- {
- _rsBlendNoneVerbatim = new CacheBlendState(
- false,
- BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
- BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
-
- _rsBlendNoneOpaque = new CacheBlendState(
- false,
- BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
- BlendingFactorSrc.ConstantAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
-
- _rsBlendNormal = new CacheBlendState(
- true,
- BlendingFactorSrc.SrcAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.OneMinusSrcAlpha,
- BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
- }
-
- private CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal;
-
- public IBlendState BlendNoneCopy => _rsBlendNoneVerbatim;
- public IBlendState BlendNoneOpaque => _rsBlendNoneOpaque;
- public IBlendState BlendNormal => _rsBlendNormal;
-
- ///
- /// is and either or is unavailable (their property is ), or
- /// one of 's items has an unsupported value in , , or
- ///
- public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
- {
- if (!vertexShader.Available || !fragmentShader.Available)
- {
- string errors = $"Vertex Shader:\r\n {vertexShader.Errors} \r\n-------\r\nFragment Shader:\r\n{fragmentShader.Errors}";
- if (required)
- {
- throw new InvalidOperationException($"Couldn't build required GL pipeline:\r\n{errors}");
- }
-
- var pipeline = new Pipeline(this, null, false, null, null, null) { Errors = errors };
- return pipeline;
- }
-
- var ves = new VertexElement[vertexLayout.Items.Count];
- int stride = 0;
- foreach (var (i, item) in vertexLayout.Items)
- {
- DeclarationType declType;
- switch (item.AttribType)
- {
- case VertexAttribPointerType.Float:
- if (item.Components == 1) declType = DeclarationType.Float1;
- else if (item.Components == 2) declType = DeclarationType.Float2;
- else if (item.Components == 3) declType = DeclarationType.Float3;
- else if (item.Components == 4) declType = DeclarationType.Float4;
- else throw new NotSupportedException();
- stride += 4 * item.Components;
- break;
- default:
- throw new NotSupportedException();
- }
-
- DeclarationUsage usage;
- byte usageIndex = 0;
- switch(item.Usage)
- {
- case AttribUsage.Position:
- usage = DeclarationUsage.Position;
- break;
- case AttribUsage.Texcoord0:
- usage = DeclarationUsage.TextureCoordinate;
- break;
- case AttribUsage.Texcoord1:
- usage = DeclarationUsage.TextureCoordinate;
- usageIndex = 1;
- break;
- case AttribUsage.Color0:
- usage = DeclarationUsage.Color;
- break;
- default:
- throw new NotSupportedException();
- }
-
- ves[i] = new VertexElement(0, (short) item.Offset, declType, DeclarationMethod.Default, usage, usageIndex);
- }
-
- var pw = new PipelineWrapper
- {
- VertexDeclaration = new VertexDeclaration(Dev, ves),
- VertexShader = vertexShader.Opaque as ShaderWrapper,
- FragmentShader = fragmentShader.Opaque as ShaderWrapper,
- VertexStride = stride
- };
-
- //scan uniforms from constant tables
- //handles must be disposed later (with the pipeline probably)
- var uniforms = new List();
- var fs = pw.FragmentShader;
- var vs = pw.VertexShader;
- var fsct = fs.bytecode.ConstantTable;
- var vsct = vs.bytecode.ConstantTable;
- foreach(var ct in new[]{fsct,vsct})
- {
- var todo = new Queue>();
- int n = ct.Description.Constants;
- for (int i = 0; i < n; i++)
- {
- var handle = ct.GetConstant(null, i);
- todo.Enqueue(Tuple.Create("", handle));
- }
-
- while(todo.Count != 0)
- {
- var tuple = todo.Dequeue();
- var prefix = tuple.Item1;
- var handle = tuple.Item2;
- var descr = ct.GetConstantDescription(handle);
-
- //Console.WriteLine($"D3D UNIFORM: {descr.Name}");
-
- if (descr.StructMembers != 0)
- {
- string newPrefix = $"{prefix}{descr.Name}.";
- for (int j = 0; j < descr.StructMembers; j++)
- {
- var subHandle = ct.GetConstant(handle, j);
- todo.Enqueue(Tuple.Create(newPrefix, subHandle));
- }
- continue;
- }
-
- var ui = new UniformInfo();
- var uw = new UniformWrapper();
-
- ui.Opaque = uw;
- string name = prefix + descr.Name;
-
- //uniforms done through the entry point signature have $ in their names which isn't helpful, so get rid of that
- name = name.RemovePrefix('$');
-
- ui.Name = name;
- uw.Description = descr;
- uw.EffectHandle = handle;
- uw.FS = (ct == fsct);
- uw.CT = ct;
- if (descr.Type == ParameterType.Sampler2D)
- {
- ui.IsSampler = true;
- ui.SamplerIndex = descr.RegisterIndex;
- uw.SamplerIndex = descr.RegisterIndex;
- }
-
- uniforms.Add(ui);
- }
- }
-
- pw.fsct = fsct;
- pw.vsct = vsct;
-
- return new Pipeline(this, pw, true, vertexLayout, uniforms,memo);
- }
-
- public void FreePipeline(Pipeline pipeline)
- {
- var pw = pipeline.Opaque as PipelineWrapper;
-
- // unavailable pipelines will have no opaque
- if (pw == null)
- {
- return;
- }
-
- pw.VertexDeclaration.Dispose();
- pw.FragmentShader.IGLShader.Release();
- pw.VertexShader.IGLShader.Release();
- }
-
- public void Internal_FreeShader(Shader shader)
- {
- var sw = (ShaderWrapper)shader.Opaque;
- sw.bytecode.Dispose();
- sw.ps?.Dispose();
- sw.vs?.Dispose();
- }
-
- private class UniformWrapper
- {
- public EffectHandle EffectHandle;
- public ConstantDescription Description;
- public bool FS;
- public ConstantTable CT;
- public int SamplerIndex;
- }
-
- private class PipelineWrapper // Disposable fields cleaned up by FreePipeline
- {
- public VertexDeclaration VertexDeclaration;
- public ShaderWrapper VertexShader, FragmentShader;
- public int VertexStride;
- public ConstantTable fsct, vsct;
- }
-
- private class TextureWrapper
- {
- public Texture Texture;
- public TextureAddress WrapClamp = TextureAddress.Clamp;
- public TextureFilter MinFilter = TextureFilter.Point, MagFilter = TextureFilter.Point;
- }
-
- public VertexLayout CreateVertexLayout() => new VertexLayout(this, new IntPtr(0));
-
- public void BindPipeline(Pipeline pipeline)
- {
- _currPipeline = pipeline;
-
- if (pipeline == null)
- {
- // unbind? i don't know
- return;
- }
-
- var pw = (PipelineWrapper)pipeline.Opaque;
- Dev.PixelShader = pw.FragmentShader.ps;
- Dev.VertexShader = pw.VertexShader.vs;
- Dev.VertexDeclaration = pw.VertexDeclaration;
-
- //not helpful...
- //pw.vsct.SetDefaults(dev);
- //pw.fsct.SetDefaults(dev);
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, bool value)
- {
- if (uniform.Owner == null)
- {
- return; // uniform was optimized out
- }
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, value);
- }
- }
-
- public void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4 mat, bool transpose)
- {
- if (uniform.Owner == null)
- {
- return; // uniform was optimized out
- }
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, mat.ToSlimDXMatrix(!transpose));
- }
- }
-
- public void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4 mat, bool transpose)
- {
- if (uniform.Owner == null)
- {
- return; // uniform was optimized out
- }
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, mat.ToSlimDXMatrix(!transpose));
- }
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
- {
- if (uniform.Owner == null)
- {
- return; // uniform was optimized out
- }
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, value.ToSlimDXVector4());
- }
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
- {
- if (uniform.Owner == null) return; // uniform was optimized out
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, value.ToSlimDXVector2());
- }
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, float value)
- {
- if (uniform.Owner == null) return; // uniform was optimized out
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, value);
- }
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
- {
- if (uniform.Owner == null) return; // uniform was optimized out
- var v = new SlimDX.Vector4[values.Length];
- for (int i = 0; i < values.Length; i++)
- {
- v[i] = values[i].ToSlimDXVector4();
- }
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- uw.CT.SetValue(Dev, uw.EffectHandle, v);
- }
- }
-
- public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
- {
- if (uniform.Owner == null) return; // uniform was optimized out
- var tw = tex.Opaque as TextureWrapper;
-
- foreach (var ui in uniform.UniformInfos)
- {
- var uw = (UniformWrapper)ui.Opaque;
- Dev.SetTexture(uw.SamplerIndex, tw.Texture);
-
- Dev.SetSamplerState(uw.SamplerIndex, SamplerState.AddressU, tw.WrapClamp);
- Dev.SetSamplerState(uw.SamplerIndex, SamplerState.AddressV, tw.WrapClamp);
- Dev.SetSamplerState(uw.SamplerIndex, SamplerState.MinFilter, tw.MinFilter);
- Dev.SetSamplerState(uw.SamplerIndex, SamplerState.MagFilter, tw.MagFilter);
- }
- }
-
- public void SetTextureWrapMode(Texture2d tex, bool clamp)
- {
- var tw = (TextureWrapper)tex.Opaque;
- tw.WrapClamp = clamp ? TextureAddress.Clamp : TextureAddress.Wrap;
- }
-
- public void SetMinFilter(Texture2d texture, TextureMinFilter minFilter)
- => ((TextureWrapper) texture.Opaque).MinFilter = minFilter == TextureMinFilter.Linear
- ? TextureFilter.Linear
- : TextureFilter.Point;
-
- public void SetMagFilter(Texture2d texture, TextureMagFilter magFilter)
- => ((TextureWrapper) texture.Opaque).MagFilter = magFilter == TextureMagFilter.Linear
- ? TextureFilter.Linear
- : TextureFilter.Point;
-
- public Texture2d LoadTexture(Bitmap bitmap)
- {
- using var bmp = new BitmapBuffer(bitmap, new BitmapLoadOptions());
- return (this as IGL).LoadTexture(bmp);
- }
-
- public Texture2d LoadTexture(Stream stream)
- {
- using var bmp = new BitmapBuffer(stream, new BitmapLoadOptions());
- return (this as IGL).LoadTexture(bmp);
- }
-
- public Texture2d CreateTexture(int width, int height) => null;
-
- public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
- {
- // not needed 1st pass (except for GL cores)
- // TODO - need to rip the texture data. we had code for that somewhere...
- return null;
- }
-
- /// GDI+ call returned unexpected data
- public void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
- {
- sdi.BitmapData bmpData = bmp.LockBits();
- var tw = tex.Opaque as TextureWrapper;
- var dr = tw.Texture.LockRectangle(0, LockFlags.None);
-
- // TODO - do we need to handle odd sizes, weird pitches here?
- if (bmp.Width * 4 != bmpData.Stride)
- {
- throw new InvalidOperationException();
- }
-
- dr.Data.WriteRange(bmpData.Scan0, bmp.Width * bmp.Height * 4);
- dr.Data.Close();
-
- tw.Texture.UnlockRectangle(0);
- bmp.UnlockBits(bmpData);
- }
-
- public Texture2d LoadTexture(BitmapBuffer bmp)
- {
- var tex = new Texture(Dev, bmp.Width, bmp.Height, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);
- var tw = new TextureWrapper { Texture = tex };
- var ret = new Texture2d(this, tw, bmp.Width, bmp.Height);
- LoadTextureData(ret, bmp);
- return ret;
- }
-
- /// SlimDX call returned unexpected data
- public BitmapBuffer ResolveTexture2d(Texture2d tex)
- {
- //TODO - lazy create and cache resolving target in RT
- var target = new Texture(Dev, tex.IntWidth, tex.IntHeight, 1, Usage.None, Format.A8R8G8B8, Pool.SystemMemory);
- var tw = tex.Opaque as TextureWrapper;
- Dev.GetRenderTargetData(tw.Texture.GetSurfaceLevel(0), target.GetSurfaceLevel(0));
- var dr = target.LockRectangle(0, LockFlags.ReadOnly);
- if (dr.Pitch != tex.IntWidth * 4) throw new InvalidOperationException();
- int[] pixels = new int[tex.IntWidth * tex.IntHeight];
- dr.Data.ReadRange(pixels, 0, tex.IntWidth * tex.IntHeight);
- var bb = new BitmapBuffer(tex.IntWidth, tex.IntHeight, pixels);
- target.UnlockRectangle(0);
- target.Dispose(); // buffer churn warning
- return bb;
- }
-
- public Texture2d LoadTexture(string path)
- {
- //not needed 1st pass ??
- //todo
- //using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
- // return (this as IGL).LoadTexture(fs);
- return null;
- }
-
- public Matrix4 CreateGuiProjectionMatrix(int w, int h)
- {
- return CreateGuiProjectionMatrix(new Size(w, h));
- }
-
- public Matrix4 CreateGuiViewMatrix(int w, int h, bool autoFlip)
- {
- return CreateGuiViewMatrix(new Size(w, h), autoFlip);
- }
-
- public Matrix4 CreateGuiProjectionMatrix(Size dims)
- {
- Matrix4 ret = Matrix4.Identity;
- ret.Row0.X = 2.0f / (float)dims.Width;
- ret.Row1.Y = 2.0f / (float)dims.Height;
- return ret;
- }
-
- public Matrix4 CreateGuiViewMatrix(Size dims, bool autoFlip)
- {
- Matrix4 ret = Matrix4.Identity;
- ret.Row1.Y = -1.0f;
- ret.Row3.X = -(float)dims.Width * 0.5f - 0.5f;
- ret.Row3.Y = (float)dims.Height * 0.5f + 0.5f;
-
- // auto-flipping isn't needed on d3d
- return ret;
- }
-
- public void SetViewport(int x, int y, int width, int height)
- {
- Dev.Viewport = new Viewport(x, y, width, height);
- Dev.ScissorRect = new Rectangle(x, y, width, height);
- }
-
- public void SetViewport(int width, int height)
- {
- SetViewport(0, 0, width, height);
- }
-
- public void SetViewport(Size size)
- {
- SetViewport(size.Width, size.Height);
- }
-
- public void SetViewport(swf.Control control)
- {
- var r = control.ClientRectangle;
- SetViewport(r.Left, r.Top, r.Width, r.Height);
- }
-
- public void BeginControl(GLControlWrapperSlimDX9 control)
- {
- _currentControl = control;
-
- // this dispose isn't strictly needed but it seems benign
- var surface = _currentControl.SwapChain.GetBackBuffer(0);
- Dev.SetRenderTarget(0, surface);
- surface.Dispose();
- }
-
- /// does not match control passed to
- public void EndControl(GLControlWrapperSlimDX9 control)
- {
- if (control != _currentControl)
- {
- throw new InvalidOperationException();
- }
-
- var surface = _currentControl.SwapChain.GetBackBuffer(0);
- Dev.SetRenderTarget(0, surface);
- surface.Dispose();
-
- _currentControl = null;
- }
-
- public void SwapControl(GLControlWrapperSlimDX9 control)
- {
- EndControl(control);
-
- try
- {
- var result = control.SwapChain.Present(Present.None);
- //var rs = dev.GetRasterStatus(0);
- }
- catch(Direct3D9Exception ex)
- {
- if (ex.ResultCode.Code == D3DERR_DEVICELOST)
- ResetDevice(control);
- }
- }
-
- private readonly HashSet _renderTargets = new HashSet();
-
- public void FreeRenderTarget(RenderTarget rt)
- {
- var tw = (TextureWrapper)rt.Texture2d.Opaque;
- tw.Texture.Dispose();
- tw.Texture = null;
- _renderTargets.Remove(rt);
- }
-
- public RenderTarget CreateRenderTarget(int w, int h)
- {
- var tw = new TextureWrapper { Texture = CreateRenderTargetTexture(w, h) };
- var tex = new Texture2d(this, tw, w, h);
- var rt = new RenderTarget(this, tw, tex);
- _renderTargets.Add(rt);
- return rt;
- }
-
- private Texture CreateRenderTargetTexture(int w, int h)
- {
- return new Texture(Dev, w, h, 1, Usage.RenderTarget, Format.A8R8G8B8, Pool.Default);
- }
-
- private void SuspendRenderTargets()
- {
- foreach (var rt in _renderTargets)
- {
- var tw = rt.Opaque as TextureWrapper;
- tw.Texture.Dispose();
- tw.Texture = null;
- }
- }
-
- private void ResumeRenderTargets()
- {
- foreach (var rt in _renderTargets)
- {
- var tw = (TextureWrapper)rt.Opaque;
- tw.Texture = CreateRenderTargetTexture(rt.Texture2d.IntWidth, rt.Texture2d.IntHeight);
- }
- }
-
- public void BindRenderTarget(RenderTarget rt)
- {
- if (rt == null)
- {
- // this dispose is needed for correct device resets, I have no idea why
- // don't try caching it either
- var surface = _currentControl.SwapChain.GetBackBuffer(0);
- Dev.SetRenderTarget(0, surface);
- surface.Dispose();
-
- Dev.DepthStencilSurface = null;
- return;
- }
-
- // dispose doesn't seem necessary for reset here...
- var tw = rt.Opaque as TextureWrapper;
- Dev.SetRenderTarget(0, tw.Texture.GetSurfaceLevel(0));
- Dev.DepthStencilSurface = null;
- }
-
- public void FreeControlSwapChain(GLControlWrapperSlimDX9 control)
- {
- if (control.SwapChain != null)
- {
- control.SwapChain.Dispose();
- control.SwapChain = null;
- }
- }
-
- public void RefreshControlSwapChain(GLControlWrapperSlimDX9 control)
- {
- FreeControlSwapChain(control);
-
- var pp = new PresentParameters
- {
- BackBufferWidth = Math.Max(8,control.ClientSize.Width),
- BackBufferHeight = Math.Max(8, control.ClientSize.Height),
- BackBufferCount = 1,
- BackBufferFormat = Format.X8R8G8B8,
- DeviceWindowHandle = control.Handle,
- Windowed = true,
- PresentationInterval = control.Vsync ? PresentInterval.One : PresentInterval.Immediate
- };
-
- control.SwapChain = new SwapChain(Dev, pp);
- }
-
- public IGraphicsControl Internal_CreateGraphicsControl()
- {
- var ret = new GLControlWrapperSlimDX9(this);
- RefreshControlSwapChain(ret);
- return ret;
- }
-
- /// is not
- public unsafe void DrawArrays(BizwareGL.PrimitiveType mode, int first, int count)
- {
- var pt = PrimitiveType.TriangleStrip;
-
- if (mode != BizwareGL.PrimitiveType.TriangleStrip)
- {
- throw new NotSupportedException();
- }
-
- //for tristrip
- int primCount = count - 2;
-
- var pw = (PipelineWrapper)_currPipeline.Opaque;
- int stride = pw.VertexStride;
- byte* ptr = (byte*)_pVertexData.ToPointer() + first * stride;
-
- Dev.DrawUserPrimitives(pt, primCount, (void*)ptr, (uint)stride);
- }
-
- public void BindArrayData(IntPtr pData) => _pVertexData = pData;
-
- public void BeginScene()
- {
- Dev.BeginScene();
- Dev.SetRenderState(RenderState.CullMode, Cull.None);
- Dev.SetRenderState(RenderState.ZEnable, false);
- Dev.SetRenderState(RenderState.ZWriteEnable, false);
- Dev.SetRenderState(RenderState.Lighting, false);
- }
-
- public void EndScene()
- {
- Dev.EndScene();
- }
- }
-}
diff --git a/src/BizHawk.Bizware.DirectX/IPCKeyInput.cs b/src/BizHawk.Bizware.DirectX/IPCKeyInput.cs
deleted file mode 100644
index 4ad4fd55c26..00000000000
--- a/src/BizHawk.Bizware.DirectX/IPCKeyInput.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.IO.Pipes;
-
-using BizHawk.Client.Common;
-
-// this is not a very safe or pretty protocol, I'm not proud of it
-namespace BizHawk.Bizware.DirectX
-{
- internal static class IPCKeyInput
- {
- public static void Initialize()
- {
- var t = new Thread(IPCThread) { IsBackground = true };
- t.Start();
- }
-
-
- private static readonly List PendingEventList = new List();
- private static readonly List EventList = new List();
-
- private static void IPCThread()
- {
- string pipeName = $"bizhawk-pid-{System.Diagnostics.Process.GetCurrentProcess().Id}-IPCKeyInput";
-
-
- for (; ; )
- {
- using var pipe = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024);
- try
- {
- pipe.WaitForConnection();
-
- BinaryReader br = new BinaryReader(pipe);
-
- for (; ; )
- {
- int e = br.ReadInt32();
- bool pressed = (e & 0x80000000) != 0;
- lock (PendingEventList)
- PendingEventList.Add(new KeyEvent(KeyInput.KeyEnumMap[e & 0x7FFFFFFF], pressed));
- }
- }
- catch { }
- }
- }
-
- public static IEnumerable Update()
- {
- EventList.Clear();
-
- lock (PendingEventList)
- {
- EventList.AddRange(PendingEventList);
- PendingEventList.Clear();
- }
-
- return EventList;
- }
- }
-}
diff --git a/src/BizHawk.Bizware.DirectX/IndirectX.cs b/src/BizHawk.Bizware.DirectX/IndirectX.cs
deleted file mode 100644
index 3133b93c114..00000000000
--- a/src/BizHawk.Bizware.DirectX/IndirectX.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-#nullable enable
-
-using System;
-using System.Collections.Generic;
-
-using BizHawk.Bizware.BizwareGL;
-using BizHawk.Client.Common;
-
-namespace BizHawk.Bizware.DirectX
-{
- /// An indirection, so that types from the SlimDX assembly don't need to be resolved if DirectX/XAudio2 features are never used.
- public static class IndirectX
- {
- public static IGL CreateD3DGLImpl()
- => new IGL_SlimDX9();
-
- public static ISoundOutput CreateDSSoundOutput(IHostAudioManager sound, IntPtr mainWindowHandle, string chosenDeviceName)
- => new DirectSoundSoundOutput(sound, mainWindowHandle, chosenDeviceName);
-
- public static ISoundOutput CreateXAudio2SoundOutput(IHostAudioManager sound, string chosenDeviceName)
- => new XAudio2SoundOutput(sound, chosenDeviceName);
-
- public static IEnumerable GetDSSinkNames()
- => DirectSoundSoundOutput.GetDeviceNames();
-
- public static IEnumerable GetXAudio2SinkNames()
- => XAudio2SoundOutput.GetDeviceNames();
- }
-}
diff --git a/src/BizHawk.Bizware.DirectX/Keyboard.cs b/src/BizHawk.Bizware.DirectX/Keyboard.cs
deleted file mode 100644
index 05b1726a0ca..00000000000
--- a/src/BizHawk.Bizware.DirectX/Keyboard.cs
+++ /dev/null
@@ -1,531 +0,0 @@
-#nullable enable
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-using BizHawk.Client.Common;
-
-using SlimDX;
-using SlimDX.DirectInput;
-
-using static BizHawk.Common.Win32Imports;
-
-using DInputKey = SlimDX.DirectInput.Key;
-using WinFormsKey = System.Windows.Forms.Keys;
-
-namespace BizHawk.Bizware.DirectX
-{
- internal static class KeyInput
- {
- private static DirectInput? _directInput;
-
- private static Keyboard? _keyboard;
-
- private static readonly object _lockObj = new object();
-
- public static void Initialize(IntPtr mainFormHandle)
- {
- lock (_lockObj)
- {
- Cleanup();
-
- _directInput = new DirectInput();
-
- _keyboard = new Keyboard(_directInput);
- _keyboard.SetCooperativeLevel(mainFormHandle, CooperativeLevel.Background | CooperativeLevel.Nonexclusive);
- _keyboard.Properties.BufferSize = 8;
- }
- }
-
- public static void Cleanup()
- {
- lock (_lockObj)
- {
- _keyboard?.Dispose();
- _keyboard = null;
- _directInput?.Dispose();
- _directInput = null;
- }
- }
-
- public static IEnumerable Update(Config config)
- {
- DistinctKey Mapped(DInputKey k) => KeyEnumMap[(int) (config.HandleAlternateKeyboardLayouts ? MapToRealKeyViaScanCode(k) : k)];
-
- lock (_lockObj)
- {
- if (_keyboard == null || _keyboard.Acquire().IsFailure || _keyboard.Poll().IsFailure) return Enumerable.Empty();
-
- var eventList = new List();
- while (true)
- {
- var events = _keyboard.GetBufferedData();
- if (Result.Last.IsFailure || events.Count == 0) return eventList;
-
- foreach (var e in events)
- {
- foreach (var k in e.PressedKeys) eventList.Add(new KeyEvent(Mapped(k), pressed: true));
- foreach (var k in e.ReleasedKeys) eventList.Add(new KeyEvent(Mapped(k), pressed: false));
- }
- }
- }
- }
-
- private static WinFormsKey MapWin32VirtualScanCodeToVirtualKey(uint scanCode)
- {
- const uint MAPVK_VSC_TO_VK_EX = 0x03;
- return (WinFormsKey) MapVirtualKey(scanCode, MAPVK_VSC_TO_VK_EX);
- }
-
- private static DInputKey MapToRealKeyViaScanCode(DInputKey key)
- {
- var scanCode = key switch
- {
- DInputKey.D0 => 0x000BU,
- DInputKey.D1 => 0x0002U,
- DInputKey.D2 => 0x0003U,
- DInputKey.D3 => 0x0004U,
- DInputKey.D4 => 0x0005U,
- DInputKey.D5 => 0x0006U,
- DInputKey.D6 => 0x0007U,
- DInputKey.D7 => 0x0008U,
- DInputKey.D8 => 0x0009U,
- DInputKey.D9 => 0x000AU,
- DInputKey.A => 0x001EU,
- DInputKey.B => 0x0030U,
- DInputKey.C => 0x002EU,
- DInputKey.D => 0x0020U,
- DInputKey.E => 0x0012U,
- DInputKey.F => 0x0021U,
- DInputKey.G => 0x0022U,
- DInputKey.H => 0x0023U,
- DInputKey.I => 0x0017U,
- DInputKey.J => 0x0024U,
- DInputKey.K => 0x0025U,
- DInputKey.L => 0x0026U,
- DInputKey.M => 0x0032U,
- DInputKey.N => 0x0031U,
- DInputKey.O => 0x0018U,
- DInputKey.P => 0x0019U,
- DInputKey.Q => 0x0010U,
- DInputKey.R => 0x0013U,
- DInputKey.S => 0x001FU,
- DInputKey.T => 0x0014U,
- DInputKey.U => 0x0016U,
- DInputKey.V => 0x002FU,
- DInputKey.W => 0x0011U,
- DInputKey.X => 0x002DU,
- DInputKey.Y => 0x0015U,
- DInputKey.Z => 0x002CU,
-// DInputKey.AbntC1 => 0x73U,
-// DInputKey.AbntC2 => 0x7EU,
- DInputKey.Apostrophe => 0x0028U,
- DInputKey.Applications => 0xE05DU,
-// DInputKey.AT => 0x91U,
-// DInputKey.AX => 0x96U,
- DInputKey.Backspace => 0x000EU,
- DInputKey.Backslash => 0x002BU,
-// DInputKey.Calculator => 0xA1U,
- DInputKey.CapsLock => 0x003AU,
-// DInputKey.Colon => 0x92U,
- DInputKey.Comma => 0x0033U,
- DInputKey.Convert => 0x0079U,
- DInputKey.Delete => 0x0053U,
- DInputKey.DownArrow => 0x0050U,
- DInputKey.End => 0x004FU,
- DInputKey.Equals => 0x000DU,
- DInputKey.Escape => 0x0001U,
- DInputKey.F1 => 0x003BU,
- DInputKey.F2 => 0x003CU,
- DInputKey.F3 => 0x003DU,
- DInputKey.F4 => 0x003EU,
- DInputKey.F5 => 0x003FU,
- DInputKey.F6 => 0x0040U,
- DInputKey.F7 => 0x0041U,
- DInputKey.F8 => 0x0042U,
- DInputKey.F9 => 0x0043U,
- DInputKey.F10 => 0x0044U,
- DInputKey.F11 => 0x0057U,
- DInputKey.F12 => 0x0058U,
- DInputKey.F13 => 0x0064U,
- DInputKey.F14 => 0x0065U,
- DInputKey.F15 => 0x0066U,
- DInputKey.Grave => 0x0029U,
- DInputKey.Home => 0x0047U,
- DInputKey.Insert => 0x0052U,
-// DInputKey.Kana => 0x70U,
-// DInputKey.Kanji => 0x94U,
- DInputKey.LeftBracket => 0x001AU,
- DInputKey.LeftControl => 0x001DU,
- DInputKey.LeftArrow => 0x004BU,
- DInputKey.LeftAlt => 0x0038U,
- DInputKey.LeftShift => 0x002AU,
- DInputKey.LeftWindowsKey => 0xE05BU,
- DInputKey.Mail => 0xE06CU,
- DInputKey.MediaSelect => 0xE06DU,
- DInputKey.MediaStop => 0xE024U,
- DInputKey.Minus => 0x000CU,
- DInputKey.Mute => 0xE020U,
-// DInputKey.MyComputer => 0xEBU,
- DInputKey.NextTrack => 0xE019U,
-// DInputKey.NoConvert => 0x7BU,
- DInputKey.NumberLock => 0x0045U,
-// DInputKey.NumberPad0 => 0x52U,
-// DInputKey.NumberPad1 => 0x4FU,
-// DInputKey.NumberPad2 => 0x50U,
-// DInputKey.NumberPad3 => 0x51U,
-// DInputKey.NumberPad4 => 0x4BU,
-// DInputKey.NumberPad5 => 0x4CU,
-// DInputKey.NumberPad6 => 0x4DU,
-// DInputKey.NumberPad7 => 0x47U,
-// DInputKey.NumberPad8 => 0x48U,
-// DInputKey.NumberPad9 => 0x49U,
-// DInputKey.NumberPadComma => 0xB3U,
-// DInputKey.NumberPadEnter => 0x9CU,
-// DInputKey.NumberPadEquals => 0x8DU,
- DInputKey.NumberPadMinus => 0x004AU,
-// DInputKey.NumberPadPeriod => 0x53U,
- DInputKey.NumberPadPlus => 0x004EU,
- DInputKey.NumberPadSlash => 0xE035U,
- DInputKey.NumberPadStar => 0x0037U,
- DInputKey.Oem102 => 0x0056U,
- DInputKey.PageDown => 0x0051U,
- DInputKey.PageUp => 0x0049U,
- DInputKey.Pause => 0xE11DU,
- DInputKey.Period => 0x0034U,
- DInputKey.PlayPause => 0xE022U,
-// DInputKey.Power => 0xDEU,
- DInputKey.PreviousTrack => 0xE010U,
- DInputKey.RightBracket => 0x001BU,
- DInputKey.RightControl => 0xE01DU,
- DInputKey.Return => 0x001CU,
- DInputKey.RightArrow => 0x004DU,
- DInputKey.RightAlt => 0xE038U,
- DInputKey.RightShift => 0x0036U,
- DInputKey.RightWindowsKey => 0xE05CU,
- DInputKey.ScrollLock => 0x0046U,
- DInputKey.Semicolon => 0x0027U,
- DInputKey.Slash => 0x0035U,
- DInputKey.Sleep => 0xE05FU,
- DInputKey.Space => 0x0039U,
-// DInputKey.Stop => 0x95U,
- DInputKey.PrintScreen => 0x0054U,
- DInputKey.Tab => 0x000FU,
-// DInputKey.Underline => 0x93U,
-// DInputKey.Unlabeled => 0x97U,
- DInputKey.UpArrow => 0x0048U,
- DInputKey.VolumeDown => 0xE02EU,
- DInputKey.VolumeUp => 0xE030U,
-// DInputKey.Wake => 0x00E3U,
- DInputKey.WebBack => 0xE06AU,
- DInputKey.WebFavorites => 0xE066U,
- DInputKey.WebForward => 0xE069U,
- DInputKey.WebHome => 0xE032U,
- DInputKey.WebRefresh => 0xE067U,
- DInputKey.WebSearch => 0xE065U,
- DInputKey.WebStop => 0xE068U,
-// DInputKey.Yen => 0x7DU,
- _ => 0U
- };
- if (scanCode == 0U) return DInputKey.Unknown;
- return MapWin32VirtualScanCodeToVirtualKey(scanCode) switch
- {
- WinFormsKey.D0 => DInputKey.D0,
- WinFormsKey.D1 => DInputKey.D1,
- WinFormsKey.D2 => DInputKey.D2,
- WinFormsKey.D3 => DInputKey.D3,
- WinFormsKey.D4 => DInputKey.D4,
- WinFormsKey.D5 => DInputKey.D5,
- WinFormsKey.D6 => DInputKey.D6,
- WinFormsKey.D7 => DInputKey.D7,
- WinFormsKey.D8 => DInputKey.D8,
- WinFormsKey.D9 => DInputKey.D9,
- WinFormsKey.A => DInputKey.A,
- WinFormsKey.B => DInputKey.B,
- WinFormsKey.C => DInputKey.C,
- WinFormsKey.D => DInputKey.D,
- WinFormsKey.E => DInputKey.E,
- WinFormsKey.F => DInputKey.F,
- WinFormsKey.G => DInputKey.G,
- WinFormsKey.H => DInputKey.H,
- WinFormsKey.I => DInputKey.I,
- WinFormsKey.J => DInputKey.J,
- WinFormsKey.K => DInputKey.K,
- WinFormsKey.L => DInputKey.L,
- WinFormsKey.M => DInputKey.M,
- WinFormsKey.N => DInputKey.N,
- WinFormsKey.O => DInputKey.O,
- WinFormsKey.P => DInputKey.P,
- WinFormsKey.Q => DInputKey.Q,
- WinFormsKey.R => DInputKey.R,
- WinFormsKey.S => DInputKey.S,
- WinFormsKey.T => DInputKey.T,
- WinFormsKey.U => DInputKey.U,
- WinFormsKey.V => DInputKey.V,
- WinFormsKey.W => DInputKey.W,
- WinFormsKey.X => DInputKey.X,
- WinFormsKey.Y => DInputKey.Y,
- WinFormsKey.Z => DInputKey.Z,
-// WinFormsKey. => DInputKey.AbntC1,
-// WinFormsKey. => DInputKey.AbntC2,
- WinFormsKey.OemQuotes => DInputKey.Apostrophe,
- WinFormsKey.Apps => DInputKey.Applications,
-// WinFormsKey. => DInputKey.AT,
-// WinFormsKey. => DInputKey.AX,
- WinFormsKey.Back => DInputKey.Backspace,
- WinFormsKey.OemPipe => DInputKey.Backslash,
-// WinFormsKey. => DInputKey.Calculator,
- WinFormsKey.Capital => DInputKey.CapsLock,
-// WinFormsKey. => DInputKey.Colon,
- WinFormsKey.Oemcomma => DInputKey.Comma,
- WinFormsKey.IMEConvert => DInputKey.Convert,
- WinFormsKey.Delete => DInputKey.Delete,
- WinFormsKey.Down => DInputKey.DownArrow,
- WinFormsKey.End => DInputKey.End,
- WinFormsKey.Oemplus => DInputKey.Equals,
- WinFormsKey.Escape => DInputKey.Escape,
- WinFormsKey.F1 => DInputKey.F1,
- WinFormsKey.F2 => DInputKey.F2,
- WinFormsKey.F3 => DInputKey.F3,
- WinFormsKey.F4 => DInputKey.F4,
- WinFormsKey.F5 => DInputKey.F5,
- WinFormsKey.F6 => DInputKey.F6,
- WinFormsKey.F7 => DInputKey.F7,
- WinFormsKey.F8 => DInputKey.F8,
- WinFormsKey.F9 => DInputKey.F9,
- WinFormsKey.F10 => DInputKey.F10,
- WinFormsKey.F11 => DInputKey.F11,
- WinFormsKey.F12 => DInputKey.F12,
- WinFormsKey.F13 => DInputKey.F13,
- WinFormsKey.F14 => DInputKey.F14,
- WinFormsKey.F15 => DInputKey.F15,
- WinFormsKey.Oemtilde => DInputKey.Grave,
- WinFormsKey.Home => DInputKey.Home,
- WinFormsKey.Insert => DInputKey.Insert,
-// WinFormsKey.KanaMode => DInputKey.Kana,
-// WinFormsKey.KanjiMode => DInputKey.Kanji,
- WinFormsKey.OemOpenBrackets => DInputKey.LeftBracket,
- WinFormsKey.LControlKey => DInputKey.LeftControl,
- WinFormsKey.Left => DInputKey.LeftArrow,
- WinFormsKey.LMenu => DInputKey.LeftAlt,
- WinFormsKey.LShiftKey => DInputKey.LeftShift,
- WinFormsKey.LWin => DInputKey.LeftWindowsKey,
- WinFormsKey.LaunchMail => DInputKey.Mail,
- WinFormsKey.SelectMedia => DInputKey.MediaSelect,
- WinFormsKey.MediaStop => DInputKey.MediaStop,
- WinFormsKey.OemMinus => DInputKey.Minus,
- WinFormsKey.VolumeMute => DInputKey.Mute,
-// WinFormsKey. => DInputKey.MyComputer,
- WinFormsKey.MediaNextTrack => DInputKey.NextTrack,
-// WinFormsKey.IMENonconvert => DInputKey.NoConvert,
- WinFormsKey.NumLock => DInputKey.NumberLock,
-// WinFormsKey.NumPad0 => DInputKey.NumberPad0,
-// WinFormsKey.NumPad1 => DInputKey.NumberPad1,
-// WinFormsKey.NumPad2 => DInputKey.NumberPad2,
-// WinFormsKey.NumPad3 => DInputKey.NumberPad3,
-// WinFormsKey.NumPad4 => DInputKey.NumberPad4,
-// WinFormsKey.NumPad5 => DInputKey.NumberPad5,
-// WinFormsKey.NumPad6 => DInputKey.NumberPad6,
-// WinFormsKey.NumPad7 => DInputKey.NumberPad7,
-// WinFormsKey.NumPad8 => DInputKey.NumberPad8,
-// WinFormsKey.NumPad9 => DInputKey.NumberPad9,
-// WinFormsKey. => DInputKey.NumberPadComma,
-// WinFormsKey. => DInputKey.NumberPadEnter,
-// WinFormsKey. => DInputKey.NumberPadEquals,
- WinFormsKey.Subtract => DInputKey.NumberPadMinus,
-// WinFormsKey.Decimal => DInputKey.NumberPadPeriod,
- WinFormsKey.Add => DInputKey.NumberPadPlus,
- WinFormsKey.Divide => DInputKey.NumberPadSlash,
- WinFormsKey.Multiply => DInputKey.NumberPadStar,
- WinFormsKey.OemBackslash => DInputKey.Oem102,
- WinFormsKey.Next => DInputKey.PageDown,
- WinFormsKey.Prior => DInputKey.PageUp,
- WinFormsKey.Pause => DInputKey.Pause,
- WinFormsKey.OemPeriod => DInputKey.Period,
- WinFormsKey.MediaPlayPause => DInputKey.PlayPause,
-// WinFormsKey. => DInputKey.Power,
- WinFormsKey.MediaPreviousTrack => DInputKey.PreviousTrack,
- WinFormsKey.OemCloseBrackets => DInputKey.RightBracket,
- WinFormsKey.RControlKey => DInputKey.RightControl,
- WinFormsKey.Return => DInputKey.Return,
- WinFormsKey.Right => DInputKey.RightArrow,
- WinFormsKey.RMenu => DInputKey.RightAlt,
- WinFormsKey.RShiftKey => DInputKey.RightShift,
- WinFormsKey.RWin => DInputKey.RightWindowsKey,
- WinFormsKey.Scroll => DInputKey.ScrollLock,
- WinFormsKey.OemSemicolon => DInputKey.Semicolon,
- WinFormsKey.OemQuestion => DInputKey.Slash,
- WinFormsKey.Sleep => DInputKey.Sleep,
- WinFormsKey.Space => DInputKey.Space,
-// WinFormsKey. => DInputKey.Stop,
- WinFormsKey.PrintScreen => DInputKey.PrintScreen,
- WinFormsKey.Tab => DInputKey.Tab,
-// WinFormsKey. => DInputKey.Underline,
-// WinFormsKey. => DInputKey.Unlabeled,
- WinFormsKey.Up => DInputKey.UpArrow,
- WinFormsKey.VolumeDown => DInputKey.VolumeDown,
- WinFormsKey.VolumeUp => DInputKey.VolumeUp,
-// WinFormsKey. => DInputKey.Wake,
- WinFormsKey.BrowserBack => DInputKey.WebBack,
- WinFormsKey.BrowserFavorites => DInputKey.WebFavorites,
- WinFormsKey.BrowserForward => DInputKey.WebForward,
- WinFormsKey.BrowserHome => DInputKey.WebHome,
- WinFormsKey.BrowserRefresh => DInputKey.WebRefresh,
- WinFormsKey.BrowserSearch => DInputKey.WebSearch,
- WinFormsKey.BrowserStop => DInputKey.WebStop,
-// WinFormsKey. => DInputKey.Yen,
- _ => DInputKey.Unknown
- };
- }
-
- internal static readonly IReadOnlyList KeyEnumMap = new List
- {
- DistinctKey.D0,
- DistinctKey.D1,
- DistinctKey.D2,
- DistinctKey.D3,
- DistinctKey.D4,
- DistinctKey.D5,
- DistinctKey.D6,
- DistinctKey.D7,
- DistinctKey.D8,
- DistinctKey.D9,
- DistinctKey.A,
- DistinctKey.B,
- DistinctKey.C,
- DistinctKey.D,
- DistinctKey.E,
- DistinctKey.F,
- DistinctKey.G,
- DistinctKey.H,
- DistinctKey.I,
- DistinctKey.J,
- DistinctKey.K,
- DistinctKey.L,
- DistinctKey.M,
- DistinctKey.N,
- DistinctKey.O,
- DistinctKey.P,
- DistinctKey.Q,
- DistinctKey.R,
- DistinctKey.S,
- DistinctKey.T,
- DistinctKey.U,
- DistinctKey.V,
- DistinctKey.W,
- DistinctKey.X,
- DistinctKey.Y,
- DistinctKey.Z,
- DistinctKey.AbntC1,
- DistinctKey.AbntC2,
- DistinctKey.OemQuotes,
- DistinctKey.Apps,
- DistinctKey.Unknown, // AT
- DistinctKey.Unknown, // AX
- DistinctKey.Back,
- DistinctKey.OemPipe, // Backslash
- DistinctKey.Unknown, // Calculator
- DistinctKey.CapsLock,
- DistinctKey.Unknown, // Colon
- DistinctKey.OemComma,
- DistinctKey.ImeConvert,
- DistinctKey.Delete,
- DistinctKey.Down,
- DistinctKey.End,
- DistinctKey.OemPlus,
- DistinctKey.Escape,
- DistinctKey.F1,
- DistinctKey.F2,
- DistinctKey.F3,
- DistinctKey.F4,
- DistinctKey.F5,
- DistinctKey.F6,
- DistinctKey.F7,
- DistinctKey.F8,
- DistinctKey.F9,
- DistinctKey.F10,
- DistinctKey.F11,
- DistinctKey.F12,
- DistinctKey.F13,
- DistinctKey.F14,
- DistinctKey.F15,
- DistinctKey.OemTilde,
- DistinctKey.Home,
- DistinctKey.Insert,
- DistinctKey.KanaMode,
- DistinctKey.KanjiMode,
- DistinctKey.OemOpenBrackets,
- DistinctKey.LeftCtrl,
- DistinctKey.Left,
- DistinctKey.LeftAlt,
- DistinctKey.LeftShift,
- DistinctKey.LWin,
- DistinctKey.LaunchMail,
- DistinctKey.SelectMedia,
- DistinctKey.MediaStop,
- DistinctKey.OemMinus,
- DistinctKey.VolumeMute,
- DistinctKey.Unknown, // MyComputer
- DistinctKey.MediaNextTrack,
- DistinctKey.ImeNonConvert,
- DistinctKey.NumLock,
- DistinctKey.NumPad0,
- DistinctKey.NumPad1,
- DistinctKey.NumPad2,
- DistinctKey.NumPad3,
- DistinctKey.NumPad4,
- DistinctKey.NumPad5,
- DistinctKey.NumPad6,
- DistinctKey.NumPad7,
- DistinctKey.NumPad8,
- DistinctKey.NumPad9,
- DistinctKey.Separator,
- DistinctKey.NumPadEnter,
- DistinctKey.OemPlus, // NumberPadEquals
- DistinctKey.Subtract,
- DistinctKey.Decimal,
- DistinctKey.Add,
- DistinctKey.Divide,
- DistinctKey.Multiply,
- DistinctKey.OemBackslash, // Oem102
- DistinctKey.PageDown,
- DistinctKey.PageUp,
- DistinctKey.Pause,
- DistinctKey.OemPeriod,
- DistinctKey.MediaPlayPause,
- DistinctKey.Unknown, // Power
- DistinctKey.MediaPreviousTrack,
- DistinctKey.OemCloseBrackets,
- DistinctKey.RightCtrl,
- DistinctKey.Return,
- DistinctKey.Right,
- DistinctKey.RightAlt,
- DistinctKey.RightShift,
- DistinctKey.RWin,
- DistinctKey.Scroll,
- DistinctKey.OemSemicolon,
- DistinctKey.OemQuestion, // Slash
- DistinctKey.Sleep,
- DistinctKey.Space,
- DistinctKey.MediaStop,
- DistinctKey.PrintScreen,
- DistinctKey.Tab,
- DistinctKey.Unknown, // Underline
- DistinctKey.Unknown, // Unlabeled
- DistinctKey.Up,
- DistinctKey.VolumeDown,
- DistinctKey.VolumeUp,
- DistinctKey.Sleep, // Wake
- DistinctKey.BrowserBack,
- DistinctKey.BrowserFavorites,
- DistinctKey.BrowserForward,
- DistinctKey.BrowserHome,
- DistinctKey.BrowserRefresh,
- DistinctKey.BrowserSearch,
- DistinctKey.BrowserStop,
- DistinctKey.Unknown, // Yen
- DistinctKey.Unknown // Unknown
- };
- }
-}
diff --git a/src/BizHawk.Bizware.DirectX/SlimDXExtensions.cs b/src/BizHawk.Bizware.DirectX/SlimDXExtensions.cs
deleted file mode 100644
index c224b03fc6d..00000000000
--- a/src/BizHawk.Bizware.DirectX/SlimDXExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using SlimDX;
-
-namespace BizHawk.Bizware.DirectX
-{
- internal static class Extensions
- {
- public static Matrix ToSlimDXMatrix(this BizwareGL.Matrix4 m, bool transpose)
- {
- Matrix ret = new()
- {
- M11 = m.Row0.X, M12 = m.Row0.Y, M13 = m.Row0.Z, M14 = m.Row0.W,
- M21 = m.Row1.X, M22 = m.Row1.Y, M23 = m.Row1.Z, M24 = m.Row1.W,
- M31 = m.Row2.X, M32 = m.Row2.Y, M33 = m.Row2.Z, M34 = m.Row2.W,
- M41 = m.Row3.X, M42 = m.Row3.Y, M43 = m.Row3.Z, M44 = m.Row3.W
- };
- // Transpose call could be inlined to reduce 2 sets of copies to 1
- return transpose ? Matrix.Transpose(ret) : ret;
- }
-
- public static Vector2 ToSlimDXVector2(this BizwareGL.Vector2 v) => new(v.X, v.Y);
-
- public static Vector4 ToSlimDXVector4(this BizwareGL.Vector4 v) => new(v.X, v.Y, v.Z, v.W);
- }
-}
diff --git a/src/BizHawk.Bizware.Graphics/BizHawk.Bizware.Graphics.csproj b/src/BizHawk.Bizware.Graphics/BizHawk.Bizware.Graphics.csproj
new file mode 100644
index 00000000000..505dae064ec
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/BizHawk.Bizware.Graphics.csproj
@@ -0,0 +1,17 @@
+
+
+ net48
+
+
+
+ true
+ disable
+
+
+
+
+
+
+
+
+
diff --git a/src/BizHawk.Bizware.Graphics/D3D9/D3D9Control.cs b/src/BizHawk.Bizware.Graphics/D3D9/D3D9Control.cs
new file mode 100644
index 00000000000..a205de7cfde
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/D3D9/D3D9Control.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Windows.Forms;
+
+using BizHawk.Bizware.BizwareGL;
+using BizHawk.Common;
+
+using SharpDX.Direct3D9;
+
+namespace BizHawk.Bizware.Graphics
+{
+ internal sealed class D3D9Control : Control, IGraphicsControl
+ {
+ private readonly IGL_D3D9 _owner;
+ internal SwapChain SwapChain;
+ internal bool Vsync;
+
+ public D3D9Control(IGL_D3D9 owner)
+ {
+ _owner = owner;
+
+ SetStyle(ControlStyles.Opaque, true);
+ SetStyle(ControlStyles.UserPaint, true);
+ SetStyle(ControlStyles.AllPaintingInWmPaint, true);
+ SetStyle(ControlStyles.UserMouse, true);
+ DoubleBuffered = false;
+ }
+
+ public RenderTargetWrapper RenderTargetWrapper
+ {
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
+ }
+
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+ _owner.RefreshControlSwapChain(this);
+ }
+
+ protected override void OnHandleDestroyed(EventArgs e)
+ {
+ base.OnHandleDestroyed(e);
+ IGL_D3D9.FreeControlSwapChain(this);
+ }
+
+ protected override void OnResize(EventArgs e)
+ {
+ base.OnResize(e);
+ _owner.RefreshControlSwapChain(this);
+ }
+
+ public void SetVsync(bool state)
+ {
+ Vsync = state;
+ _owner.RefreshControlSwapChain(this);
+ }
+
+ public void Begin()
+ => _owner.BeginControl(this);
+
+ public void End()
+ => _owner.EndControl(this);
+
+ public void SwapBuffers()
+ => _owner.SwapControl(this);
+ }
+}
\ No newline at end of file
diff --git a/src/BizHawk.Bizware.Graphics/D3D9/IGL_D3D9.cs b/src/BizHawk.Bizware.Graphics/D3D9/IGL_D3D9.cs
new file mode 100644
index 00000000000..f1bbb4a735f
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/D3D9/IGL_D3D9.cs
@@ -0,0 +1,984 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using BizHawk.Bizware.BizwareGL;
+using BizHawk.Common;
+using BizHawk.Common.StringExtensions;
+
+using SharpDX;
+using SharpDX.Direct3D9;
+using SharpDX.Mathematics.Interop;
+
+using static SDL2.SDL;
+
+using BizPrimitiveType = BizHawk.Bizware.BizwareGL.PrimitiveType;
+using D3D9PrimitiveType = SharpDX.Direct3D9.PrimitiveType;
+
+// todo - do a better job selecting shader model? base on caps somehow? try several and catch compilation exceptions (yuck, exceptions)
+namespace BizHawk.Bizware.Graphics
+{
+ ///
+ /// Direct3D9 implementation of the BizwareGL.IGL interface
+ ///
+ public sealed class IGL_D3D9 : IGL
+ {
+ public EDispMethod DispMethodEnum => EDispMethod.D3D9;
+
+ private const int D3DERR_DEVICELOST = unchecked((int)0x88760868);
+ private const int D3DERR_DEVICENOTRESET = unchecked((int)0x88760869);
+
+ private Device _device;
+
+ private IntPtr _offscreenSdl2Window;
+ private IntPtr OffscreenNativeWindow;
+
+ // rendering state
+ private IntPtr _pVertexData;
+ private Pipeline _currPipeline;
+ private D3D9Control _currentControl;
+
+ // misc state
+ private RawColorBGRA _clearColor;
+ private CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal;
+ private readonly HashSet _renderTargets = new();
+
+ public string API => "D3D9";
+
+ static IGL_D3D9()
+ {
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ throw new($"Failed to init SDL video, SDL error: {SDL_GetError()}");
+ }
+ }
+
+ public IGL_D3D9()
+ {
+ if (OSTailoredCode.IsUnixHost)
+ {
+ throw new NotSupportedException("D3D9 is Windows only");
+ }
+
+ // make an 'offscreen context' so we can at least do things without having to create a window
+ _offscreenSdl2Window = SDL_CreateWindow(null, 0, 0, 1, 1, SDL_WindowFlags.SDL_WINDOW_HIDDEN);
+ if (_offscreenSdl2Window == IntPtr.Zero)
+ {
+ throw new($"Failed to create offscreen SDL window, SDL error: {SDL_GetError()}");
+ }
+
+ // get the native window handle
+ var wminfo = default(SDL_SysWMinfo);
+ SDL_GetVersion(out wminfo.version);
+ SDL_GetWindowWMInfo(_offscreenSdl2Window, ref wminfo);
+ if (wminfo.subsystem != SDL_SYSWM_TYPE.SDL_SYSWM_WINDOWS)
+ {
+ throw new($"SDL_SysWMinfo did not report SDL_SYSWM_WINDOWS? Something went wrong... SDL error: {SDL_GetError()}");
+ }
+
+ OffscreenNativeWindow = wminfo.info.win.window;
+
+ CreateDevice();
+ CreateRenderStates();
+ }
+
+ public void AlternateVsyncPass(int pass)
+ {
+ while (true)
+ {
+ var status = _device.GetRasterStatus(0);
+ if (status.InVBlank && pass == 0) return; // wait for vblank to begin
+ if (!status.InVBlank && pass == 1) return; // wait for vblank to end
+ // STOP! think you can use System.Threading.SpinWait? No, it's way too slow.
+ // (on my system, the vblank is something like 24 of 1074 scanlines @ 60hz ~= 0.35ms which is an awfully small window to nail)
+ }
+ }
+
+ private void CreateDevice()
+ {
+ // this object is only used for creating a device, it's not needed afterwards
+ using var d3d9 = new Direct3D();
+
+ var pp = MakePresentParameters();
+
+ var flags = (d3d9.GetDeviceCaps(0, DeviceType.Hardware).DeviceCaps & DeviceCaps.HWTransformAndLight) != 0
+ ? CreateFlags.HardwareVertexProcessing
+ : CreateFlags.SoftwareVertexProcessing;
+
+ flags |= CreateFlags.FpuPreserve;
+ _device = new(d3d9, 0, DeviceType.Hardware, pp.DeviceWindowHandle, flags, pp);
+ }
+
+ private void DestroyDevice()
+ {
+ if (_device != null)
+ {
+ _device.Dispose();
+ _device = null;
+ }
+ }
+
+ private PresentParameters MakePresentParameters()
+ {
+ return new()
+ {
+ BackBufferCount = 1,
+ SwapEffect = SwapEffect.Discard,
+ DeviceWindowHandle = OffscreenNativeWindow,
+ Windowed = true,
+ PresentationInterval = PresentInterval.Immediate,
+ EnableAutoDepthStencil = false
+ };
+ }
+
+ private void ResetDevice(D3D9Control control)
+ {
+ SuspendRenderTargets();
+ FreeControlSwapChain(control);
+
+ while (true)
+ {
+ var result = _device.TestCooperativeLevel();
+ if (result.Success)
+ {
+ break;
+ }
+
+ if (result.Code == D3DERR_DEVICENOTRESET)
+ {
+ try
+ {
+ var pp = MakePresentParameters();
+ _device.Reset(pp);
+ break;
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ Thread.Sleep(100);
+ }
+
+ RefreshControlSwapChain(control);
+ ResumeRenderTargets();
+ }
+
+ public void Dispose()
+ {
+ DestroyDevice();
+
+ if (_offscreenSdl2Window != IntPtr.Zero)
+ {
+ SDL_DestroyWindow(_offscreenSdl2Window);
+ _offscreenSdl2Window = OffscreenNativeWindow = IntPtr.Zero;
+ }
+ }
+
+ public void Clear(ClearBufferMask mask)
+ {
+ var flags = ClearFlags.None;
+ if ((mask & ClearBufferMask.ColorBufferBit) != 0) flags |= ClearFlags.Target;
+ if ((mask & ClearBufferMask.DepthBufferBit) != 0) flags |= ClearFlags.ZBuffer;
+ if ((mask & ClearBufferMask.StencilBufferBit) != 0) flags |= ClearFlags.Stencil;
+ _device.Clear(flags, _clearColor, 0.0f, 0);
+ }
+
+ public void SetClearColor(Color color)
+ => _clearColor = color.ToSharpDXColor();
+
+ public IBlendState CreateBlendState(
+ BlendingFactorSrc colorSource,
+ BlendEquationMode colorEquation,
+ BlendingFactorDest colorDest,
+ BlendingFactorSrc alphaSource,
+ BlendEquationMode alphaEquation,
+ BlendingFactorDest alphaDest)
+ => new CacheBlendState(true, colorSource, colorEquation, colorDest, alphaSource, alphaEquation, alphaDest);
+
+ public void FreeTexture(Texture2d tex)
+ {
+ var tw = (TextureWrapper)tex.Opaque;
+ tw.Texture.Dispose();
+ }
+
+ private class ShaderWrapper // Disposable fields cleaned up by Internal_FreeShader
+ {
+ public ShaderBytecode Bytecode;
+ public VertexShader VS;
+ public PixelShader PS;
+ public Shader IGLShader;
+ }
+
+ /// is and compilation error occurred
+ public Shader CreateFragmentShader(string source, string entry, bool required)
+ {
+ try
+ {
+ var sw = new ShaderWrapper();
+
+ // ShaderFlags.EnableBackwardsCompatibility - used this once upon a time (please leave a note about why)
+ var result = ShaderBytecode.Compile(
+ shaderSource: source,
+ entryPoint: entry,
+ profile: "ps_3_0",
+ shaderFlags: ShaderFlags.UseLegacyD3DX9_31Dll);
+
+ sw.PS = new(_device, result);
+ sw.Bytecode = result;
+
+ var s = new Shader(this, sw, true);
+ sw.IGLShader = s;
+
+ return s;
+ }
+ catch (Exception ex)
+ {
+ if (required)
+ {
+ throw;
+ }
+
+ return new(this, null, false) { Errors = ex.ToString() };
+ }
+ }
+
+ /// is and compilation error occurred
+ public Shader CreateVertexShader(string source, string entry, bool required)
+ {
+ try
+ {
+ var sw = new ShaderWrapper();
+
+ var result = ShaderBytecode.Compile(
+ shaderSource: source,
+ entryPoint: entry,
+ profile: "vs_3_0",
+ shaderFlags: ShaderFlags.UseLegacyD3DX9_31Dll);
+
+ sw.VS = new(_device, result);
+ sw.Bytecode = result;
+
+ var s = new Shader(this, sw, true);
+ sw.IGLShader = s;
+
+ return s;
+ }
+ catch (Exception ex)
+ {
+ if (required)
+ {
+ throw;
+ }
+
+ return new(this, null, false) { Errors = ex.ToString() };
+ }
+ }
+
+ private static BlendOperation ConvertBlendOp(BlendEquationMode glMode)
+ => glMode switch
+ {
+ BlendEquationMode.FuncAdd => BlendOperation.Add,
+ BlendEquationMode.FuncSubtract => BlendOperation.Subtract,
+ BlendEquationMode.Max => BlendOperation.Maximum,
+ BlendEquationMode.Min => BlendOperation.Minimum,
+ BlendEquationMode.FuncReverseSubtract => BlendOperation.ReverseSubtract,
+ _ => throw new InvalidOperationException()
+ };
+
+ private static Blend ConvertBlendArg(BlendingFactorDest glMode)
+ => ConvertBlendArg((BlendingFactorSrc)glMode);
+
+ private static Blend ConvertBlendArg(BlendingFactorSrc glMode)
+ => glMode switch
+ {
+ BlendingFactorSrc.Zero => Blend.Zero,
+ BlendingFactorSrc.One => Blend.One,
+ BlendingFactorSrc.SrcColor => Blend.SourceColor,
+ BlendingFactorSrc.OneMinusSrcColor => Blend.InverseSourceColor,
+ BlendingFactorSrc.SrcAlpha => Blend.SourceAlpha,
+ BlendingFactorSrc.OneMinusSrcAlpha => Blend.InverseSourceAlpha,
+ BlendingFactorSrc.DstAlpha => Blend.DestinationAlpha,
+ BlendingFactorSrc.OneMinusDstAlpha => Blend.InverseDestinationAlpha,
+ BlendingFactorSrc.DstColor => Blend.DestinationColor,
+ BlendingFactorSrc.OneMinusDstColor => Blend.InverseDestinationColor,
+ BlendingFactorSrc.SrcAlphaSaturate => Blend.SourceAlphaSaturated,
+ BlendingFactorSrc.ConstantColor => Blend.BlendFactor,
+ BlendingFactorSrc.OneMinusConstantColor => Blend.InverseBlendFactor,
+ BlendingFactorSrc.ConstantAlpha => throw new NotSupportedException(),
+ BlendingFactorSrc.OneMinusConstantAlpha => throw new NotSupportedException(),
+ BlendingFactorSrc.Src1Alpha => throw new NotSupportedException(),
+ BlendingFactorSrc.Src1Color => throw new NotSupportedException(),
+ BlendingFactorSrc.OneMinusSrc1Color => throw new NotSupportedException(),
+ BlendingFactorSrc.OneMinusSrc1Alpha => throw new NotSupportedException(),
+ _ => throw new InvalidOperationException()
+ };
+
+ public void SetBlendState(IBlendState rsBlend)
+ {
+ var myBs = (CacheBlendState)rsBlend;
+ if (myBs.Enabled)
+ {
+ _device.SetRenderState(RenderState.AlphaBlendEnable, true);
+ _device.SetRenderState(RenderState.SeparateAlphaBlendEnable, true);
+
+ _device.SetRenderState(RenderState.BlendOperation, ConvertBlendOp(myBs.colorEquation));
+ _device.SetRenderState(RenderState.SourceBlend, ConvertBlendArg(myBs.colorSource));
+ _device.SetRenderState(RenderState.DestinationBlend, ConvertBlendArg(myBs.colorDest));
+
+ _device.SetRenderState(RenderState.BlendOperationAlpha, ConvertBlendOp(myBs.alphaEquation));
+ _device.SetRenderState(RenderState.SourceBlendAlpha, ConvertBlendArg(myBs.alphaSource));
+ _device.SetRenderState(RenderState.DestinationBlendAlpha, ConvertBlendArg(myBs.alphaDest));
+ }
+ else
+ {
+ _device.SetRenderState(RenderState.AlphaBlendEnable, false);
+ }
+
+ if (rsBlend == _rsBlendNoneOpaque)
+ {
+ // make sure constant color is set correctly
+ _device.SetRenderState(RenderState.BlendFactor, -1); // white
+ }
+ }
+
+ private void CreateRenderStates()
+ {
+ _rsBlendNoneVerbatim = new(
+ false,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
+
+ _rsBlendNoneOpaque = new(
+ false,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
+ BlendingFactorSrc.ConstantAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
+
+ _rsBlendNormal = new(
+ true,
+ BlendingFactorSrc.SrcAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.OneMinusSrcAlpha,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
+ }
+
+ public IBlendState BlendNoneCopy => _rsBlendNoneVerbatim;
+ public IBlendState BlendNoneOpaque => _rsBlendNoneOpaque;
+ public IBlendState BlendNormal => _rsBlendNormal;
+
+ ///
+ /// is and either or is unavailable (their property is ), or
+ /// one of 's items has an unsupported value in , , or
+ ///
+ public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
+ {
+ if (!vertexShader.Available || !fragmentShader.Available)
+ {
+ var errors = $"Vertex Shader:\r\n {vertexShader.Errors} \r\n-------\r\nFragment Shader:\r\n{fragmentShader.Errors}";
+ if (required)
+ {
+ throw new InvalidOperationException($"Couldn't build required GL pipeline:\r\n{errors}");
+ }
+
+ return new(this, null, false, null, null, null) { Errors = errors };
+ }
+
+ var ves = new VertexElement[vertexLayout.Items.Count + 1];
+ var stride = 0;
+ foreach (var (i, item) in vertexLayout.Items)
+ {
+ DeclarationType declType;
+ // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
+ switch (item.AttribType)
+ {
+ case VertexAttribPointerType.Float:
+ declType = item.Components switch
+ {
+ 1 => DeclarationType.Float1,
+ 2 => DeclarationType.Float2,
+ 3 => DeclarationType.Float3,
+ 4 => DeclarationType.Float4,
+ _ => throw new InvalidOperationException()
+ };
+ stride += 4 * item.Components;
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+
+ DeclarationUsage usage;
+ byte usageIndex = 0;
+ // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
+ switch (item.Usage)
+ {
+ case AttribUsage.Position:
+ usage = DeclarationUsage.Position;
+ break;
+ case AttribUsage.Texcoord0:
+ usage = DeclarationUsage.TextureCoordinate;
+ break;
+ case AttribUsage.Texcoord1:
+ usage = DeclarationUsage.TextureCoordinate;
+ usageIndex = 1;
+ break;
+ case AttribUsage.Color0:
+ usage = DeclarationUsage.Color;
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+
+ ves[i] = new(0, (short)item.Offset, declType, DeclarationMethod.Default, usage, usageIndex);
+ }
+
+ // must be placed at the end
+ ves[vertexLayout.Items.Count] = VertexElement.VertexDeclarationEnd;
+
+ var pw = new PipelineWrapper
+ {
+ VertexDeclaration = new(_device, ves),
+ VertexShader = (ShaderWrapper)vertexShader.Opaque,
+ FragmentShader = (ShaderWrapper)fragmentShader.Opaque,
+ VertexStride = stride
+ };
+
+ // scan uniforms from reflection
+ var uniforms = new List();
+ var vsct = pw.VertexShader.Bytecode.ConstantTable;
+ var psct = pw.FragmentShader.Bytecode.ConstantTable;
+ foreach (var ct in new[] { vsct, psct })
+ {
+ var todo = new Queue<(string, EffectHandle)>();
+ var n = ct.Description.Constants;
+ for (var i = 0; i < n; i++)
+ {
+ var handle = ct.GetConstant(null, i);
+ todo.Enqueue((string.Empty, handle));
+ }
+
+ while (todo.Count != 0)
+ {
+ var (prefix, handle) = todo.Dequeue();
+ var descr = ct.GetConstantDescription(handle);
+
+ // Console.WriteLine($"D3D UNIFORM: {descr.Name}");
+
+ if (descr.StructMembers != 0)
+ {
+ var newPrefix = $"{prefix}{descr.Name}.";
+ for (var j = 0; j < descr.StructMembers; j++)
+ {
+ var subHandle = ct.GetConstant(handle, j);
+ todo.Enqueue((newPrefix, subHandle));
+ }
+
+ continue;
+ }
+
+ var ui = new UniformInfo();
+ var uw = new UniformWrapper();
+
+ ui.Opaque = uw;
+ var name = prefix + descr.Name;
+
+ // uniforms done through the entry point signature have $ in their names which isn't helpful, so get rid of that
+ name = name.RemovePrefix('$');
+
+ ui.Name = name;
+ uw.EffectHandle = handle;
+ uw.CT = ct;
+
+ if (descr.Type == ParameterType.Sampler2D)
+ {
+ ui.IsSampler = true;
+ ui.SamplerIndex = descr.RegisterIndex;
+ }
+
+ uniforms.Add(ui);
+ }
+ }
+
+ return new(this, pw, true, vertexLayout, uniforms, memo);
+ }
+
+ public void FreePipeline(Pipeline pipeline)
+ {
+ // unavailable pipelines will have no opaque
+ if (pipeline.Opaque is not PipelineWrapper pw)
+ {
+ return;
+ }
+
+ pw.VertexDeclaration.Dispose();
+ pw.FragmentShader.IGLShader.Release();
+ pw.VertexShader.IGLShader.Release();
+ }
+
+ public void Internal_FreeShader(Shader shader)
+ {
+ var sw = (ShaderWrapper)shader.Opaque;
+ sw.Bytecode.Dispose();
+ sw.PS?.Dispose();
+ sw.VS?.Dispose();
+ }
+
+ private class UniformWrapper
+ {
+ public EffectHandle EffectHandle;
+ public ConstantTable CT;
+ }
+
+ private class PipelineWrapper // Disposable fields cleaned up by FreePipeline
+ {
+ public VertexDeclaration VertexDeclaration;
+ public ShaderWrapper VertexShader, FragmentShader;
+ public int VertexStride;
+ }
+
+ private class TextureWrapper
+ {
+ public Texture Texture;
+ public TextureAddress WrapClamp = TextureAddress.Clamp;
+ public TextureFilter MinFilter = TextureFilter.Point, MagFilter = TextureFilter.Point;
+ }
+
+ public VertexLayout CreateVertexLayout() => new(this, new IntPtr(0));
+
+ public void BindPipeline(Pipeline pipeline)
+ {
+ _currPipeline = pipeline;
+
+ if (pipeline == null)
+ {
+ // unbind? i don't know
+ return;
+ }
+
+ var pw = (PipelineWrapper)pipeline.Opaque;
+ _device.PixelShader = pw.FragmentShader.PS;
+ _device.VertexShader = pw.VertexShader.VS;
+ _device.VertexDeclaration = pw.VertexDeclaration;
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, bool value)
+ {
+ foreach (var ui in uniform.UniformInfos)
+ {
+ var uw = (UniformWrapper)ui.Opaque;
+ uw.CT.SetValue(_device, uw.EffectHandle, value);
+ }
+ }
+
+ public void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4 mat, bool transpose)
+ => SetPipelineUniformMatrix(uniform, ref mat, transpose);
+
+ public void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4 mat, bool transpose)
+ {
+ foreach (var ui in uniform.UniformInfos)
+ {
+ var uw = (UniformWrapper)ui.Opaque;
+ uw.CT.SetValue(_device, uw.EffectHandle, mat.ToSharpDXMatrix(!transpose));
+ }
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
+ {
+ foreach (var ui in uniform.UniformInfos)
+ {
+ var uw = (UniformWrapper)ui.Opaque;
+ uw.CT.SetValue(_device, uw.EffectHandle, value.ToSharpDXVector4());
+ }
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
+ {
+ foreach (var ui in uniform.UniformInfos)
+ {
+ var uw = (UniformWrapper)ui.Opaque;
+ uw.CT.SetValue(_device, uw.EffectHandle, value.ToSharpDXVector2());
+ }
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, float value)
+ {
+ foreach (var ui in uniform.UniformInfos)
+ {
+ var uw = (UniformWrapper)ui.Opaque;
+ uw.CT.SetValue(_device, uw.EffectHandle, value);
+ }
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
+ {
+ var v = Array.ConvertAll(values, v => v.ToSharpDXVector4());
+ foreach (var ui in uniform.UniformInfos)
+ {
+ var uw = (UniformWrapper)ui.Opaque;
+ uw.CT.SetValue(_device, uw.EffectHandle, v);
+ }
+ }
+
+ public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
+ {
+ if (uniform.Owner == null)
+ {
+ return; // uniform was optimized out
+ }
+
+ var tw = (TextureWrapper)tex.Opaque;
+ foreach (var ui in uniform.UniformInfos)
+ {
+ if (!ui.IsSampler)
+ {
+ throw new InvalidOperationException("Uniform was not a texture/sampler");
+ }
+
+ _device.SetTexture(ui.SamplerIndex, tw.Texture);
+
+ _device.SetSamplerState(ui.SamplerIndex, SamplerState.AddressU, (int)tw.WrapClamp);
+ _device.SetSamplerState(ui.SamplerIndex, SamplerState.AddressV, (int)tw.WrapClamp);
+ _device.SetSamplerState(ui.SamplerIndex, SamplerState.MinFilter, (int)tw.MinFilter);
+ _device.SetSamplerState(ui.SamplerIndex, SamplerState.MagFilter, (int)tw.MagFilter);
+ }
+ }
+
+ public void SetTextureWrapMode(Texture2d tex, bool clamp)
+ {
+ var tw = (TextureWrapper)tex.Opaque;
+ tw.WrapClamp = clamp ? TextureAddress.Clamp : TextureAddress.Wrap;
+ }
+
+ public void SetMinFilter(Texture2d texture, TextureMinFilter minFilter)
+ => ((TextureWrapper)texture.Opaque).MinFilter = minFilter == TextureMinFilter.Linear
+ ? TextureFilter.Linear
+ : TextureFilter.Point;
+
+ public void SetMagFilter(Texture2d texture, TextureMagFilter magFilter)
+ => ((TextureWrapper)texture.Opaque).MagFilter = magFilter == TextureMagFilter.Linear
+ ? TextureFilter.Linear
+ : TextureFilter.Point;
+
+ public Texture2d LoadTexture(Bitmap bitmap)
+ {
+ using var bmp = new BitmapBuffer(bitmap, new());
+ return LoadTexture(bmp);
+ }
+
+ public Texture2d LoadTexture(Stream stream)
+ {
+ using var bmp = new BitmapBuffer(stream, new());
+ return LoadTexture(bmp);
+ }
+
+ public Texture2d CreateTexture(int width, int height)
+ {
+ var tex = new Texture(_device, width, height, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);
+ var tw = new TextureWrapper { Texture = tex };
+ return new(this, tw, width, height);
+ }
+
+ public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
+ {
+ // not needed 1st pass (except for GL cores)
+ // TODO - need to rip the texture data. we had code for that somewhere...
+ return null;
+ }
+
+ /// GDI+ call returned unexpected data
+ public unsafe void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
+ {
+ var tw = (TextureWrapper)tex.Opaque;
+ var bmpData = bmp.LockBits();
+
+ try
+ {
+ var dr = tw.Texture.LockRectangle(0, LockFlags.None);
+
+ // TODO - do we need to handle odd sizes, weird pitches here?
+ if (bmp.Width * 4 != bmpData.Stride || bmpData.Stride != dr.Pitch)
+ {
+ throw new InvalidOperationException();
+ }
+
+ var srcSpan = new ReadOnlySpan(bmpData.Scan0.ToPointer(), bmpData.Stride * bmp.Height);
+ var dstSpan = new Span(dr.DataPointer.ToPointer(), dr.Pitch * bmp.Height);
+ srcSpan.CopyTo(dstSpan);
+ }
+ finally
+ {
+ tw.Texture.UnlockRectangle(0);
+ bmp.UnlockBits(bmpData);
+ }
+ }
+
+ public Texture2d LoadTexture(BitmapBuffer bmp)
+ {
+ var ret = CreateTexture(bmp.Width, bmp.Height);
+ LoadTextureData(ret, bmp);
+ return ret;
+ }
+
+ /// Vortice call returned unexpected data
+ public BitmapBuffer ResolveTexture2d(Texture2d tex)
+ {
+ // TODO - lazy create and cache resolving target in RT
+ using var target = new Texture(_device, tex.IntWidth, tex.IntHeight, 1, Usage.None, Format.A8R8G8B8, Pool.SystemMemory);
+ var tw = (TextureWrapper)tex.Opaque;
+
+ _device.GetRenderTargetData(tw.Texture.GetSurfaceLevel(0), target.GetSurfaceLevel(0));
+
+ try
+ {
+ var dr = target.LockRectangle(0, LockFlags.ReadOnly);
+
+ if (dr.Pitch != tex.IntWidth * 4)
+ {
+ throw new InvalidOperationException();
+ }
+
+ var pixels = new int[tex.IntWidth * tex.IntHeight];
+ Marshal.Copy(dr.DataPointer, pixels, 0, tex.IntWidth * tex.IntHeight);
+ return new(tex.IntWidth, tex.IntHeight, pixels);
+ }
+ finally
+ {
+ target.UnlockRectangle(0);
+ }
+ }
+
+ public Texture2d LoadTexture(string path)
+ {
+ using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return LoadTexture(fs);
+ }
+
+ public Matrix4 CreateGuiProjectionMatrix(int w, int h)
+ {
+ return CreateGuiProjectionMatrix(new(w, h));
+ }
+
+ public Matrix4 CreateGuiViewMatrix(int w, int h, bool autoFlip)
+ {
+ return CreateGuiViewMatrix(new(w, h), autoFlip);
+ }
+
+ public Matrix4 CreateGuiProjectionMatrix(Size dims)
+ {
+ var ret = Matrix4.Identity;
+ ret.Row0.X = 2.0f / dims.Width;
+ ret.Row1.Y = 2.0f / dims.Height;
+ return ret;
+ }
+
+ public Matrix4 CreateGuiViewMatrix(Size dims, bool autoFlip)
+ {
+ var ret = Matrix4.Identity;
+ ret.Row1.Y = -1.0f;
+ ret.Row3.X = -dims.Width * 0.5f - 0.5f;
+ ret.Row3.Y = dims.Height * 0.5f + 0.5f;
+
+ // auto-flipping isn't needed on d3d
+ return ret;
+ }
+
+ public void SetViewport(int x, int y, int width, int height)
+ {
+ _device.Viewport = new() { X = x, Y = y, Width = width, Height = height, MinDepth = 0, MaxDepth = 1 };
+ _device.ScissorRect = new(x, y, x + width, y + height);
+ }
+
+ public void SetViewport(int width, int height)
+ {
+ SetViewport(0, 0, width, height);
+ }
+
+ public void SetViewport(Size size)
+ {
+ SetViewport(size.Width, size.Height);
+ }
+
+ internal void BeginControl(D3D9Control control)
+ {
+ _currentControl = control;
+
+ // this dispose isn't strictly needed but it seems benign
+ using var surface = control.SwapChain.GetBackBuffer(0);
+ _device.SetRenderTarget(0, surface);
+ }
+
+ /// does not match control passed to
+ internal void EndControl(D3D9Control control)
+ {
+ if (control != _currentControl)
+ {
+ throw new InvalidOperationException($"{nameof(control)} does not match control passed to {nameof(BeginControl)}");
+ }
+
+ using var surface = control.SwapChain.GetBackBuffer(0);
+ _device.SetRenderTarget(0, surface);
+
+ _currentControl = null;
+ }
+
+ internal void SwapControl(D3D9Control control)
+ {
+ EndControl(control);
+
+ try
+ {
+ control.SwapChain.Present(Present.None);
+ }
+ catch (SharpDXException ex)
+ {
+ if (ex.ResultCode.Code == D3DERR_DEVICELOST)
+ {
+ ResetDevice(control);
+ }
+ }
+ }
+
+ public void FreeRenderTarget(RenderTarget rt)
+ {
+ var tw = (TextureWrapper)rt.Texture2d.Opaque;
+ tw.Texture.Dispose();
+ tw.Texture = null;
+ _renderTargets.Remove(rt);
+ }
+
+ public RenderTarget CreateRenderTarget(int w, int h)
+ {
+ var tw = new TextureWrapper { Texture = CreateRenderTargetTexture(w, h) };
+ var tex = new Texture2d(this, tw, w, h);
+ var rt = new RenderTarget(this, tw, tex);
+ _renderTargets.Add(rt);
+ return rt;
+ }
+
+ private Texture CreateRenderTargetTexture(int w, int h)
+ {
+ return new(_device, w, h, 1, Usage.RenderTarget, Format.A8R8G8B8, Pool.Default);
+ }
+
+ private void SuspendRenderTargets()
+ {
+ foreach (var tw in _renderTargets.Select(rt => (TextureWrapper)rt.Opaque))
+ {
+ tw.Texture.Dispose();
+ tw.Texture = null;
+ }
+ }
+
+ private void ResumeRenderTargets()
+ {
+ foreach (var rt in _renderTargets)
+ {
+ var tw = (TextureWrapper)rt.Opaque;
+ tw.Texture = CreateRenderTargetTexture(rt.Texture2d.IntWidth, rt.Texture2d.IntHeight);
+ }
+ }
+
+ public void BindRenderTarget(RenderTarget rt)
+ {
+ if (rt == null)
+ {
+ // this dispose is needed for correct device resets, I have no idea why
+ // don't try caching it either
+ using var surface = _currentControl.SwapChain.GetBackBuffer(0);
+ _device.SetRenderTarget(0, surface);
+ _device.DepthStencilSurface = null;
+ return;
+ }
+
+ // dispose doesn't seem necessary for reset here...
+ var tw = (TextureWrapper)rt.Opaque;
+ _device.SetRenderTarget(0, tw.Texture.GetSurfaceLevel(0));
+ _device.DepthStencilSurface = null;
+ }
+
+ internal static void FreeControlSwapChain(D3D9Control control)
+ {
+ control.SwapChain?.Dispose();
+ control.SwapChain = null;
+ }
+
+ internal void RefreshControlSwapChain(D3D9Control control)
+ {
+ FreeControlSwapChain(control);
+
+ var pp = new PresentParameters
+ {
+ BackBufferWidth = Math.Max(1, control.ClientSize.Width),
+ BackBufferHeight = Math.Max(1, control.ClientSize.Height),
+ BackBufferCount = 1,
+ BackBufferFormat = Format.X8R8G8B8,
+ SwapEffect = SwapEffect.Discard,
+ DeviceWindowHandle = control.Handle,
+ Windowed = true,
+ PresentationInterval = control.Vsync ? PresentInterval.One : PresentInterval.Immediate
+ };
+
+ control.SwapChain = new(_device, pp);
+ }
+
+ public IGraphicsControl Internal_CreateGraphicsControl()
+ {
+ var ret = new D3D9Control(this);
+ ret.CreateControl();
+ return ret;
+ }
+
+ private delegate void DrawPrimitiveUPDelegate(Device device, D3D9PrimitiveType primitiveType, int primitiveCount, IntPtr vertexStreamZeroDataRef, int vertexStreamZeroStride);
+
+ private static readonly Lazy _drawPrimitiveUP = new(() =>
+ {
+ var mi = typeof(Device).GetMethod("DrawPrimitiveUP", BindingFlags.Instance | BindingFlags.NonPublic);
+ return (DrawPrimitiveUPDelegate)Delegate.CreateDelegate(typeof(DrawPrimitiveUPDelegate), mi!);
+ });
+
+ private void DrawPrimitiveUP(D3D9PrimitiveType primitiveType, int primitiveCount, IntPtr vertexStreamZeroDataRef, int vertexStreamZeroStride)
+ => _drawPrimitiveUP.Value(_device, primitiveType, primitiveCount, vertexStreamZeroDataRef, vertexStreamZeroStride);
+
+ /// is not
+ public void DrawArrays(BizPrimitiveType mode, int first, int count)
+ {
+ if (mode != BizPrimitiveType.TriangleStrip)
+ {
+ throw new NotSupportedException();
+ }
+
+ // for tristrip
+ var primCount = count - 2;
+
+ var pw = (PipelineWrapper)_currPipeline.Opaque;
+ var stride = pw.VertexStride;
+ var ptr = _pVertexData + first * stride;
+
+ // this is stupid, sharpdx only public exposes DrawUserPrimatives
+ // why is this bad? it takes in an array of T
+ // and uses the size of T to determine stride
+ // since stride for us is just completely variable, this is no good
+ // DrawPrimitiveUP is internal so we have to use this hack to use it directly
+
+ DrawPrimitiveUP(D3D9PrimitiveType.TriangleStrip, primCount, ptr, stride);
+ }
+
+ public void BindArrayData(IntPtr pData)
+ => _pVertexData = pData;
+
+ public void BeginScene()
+ {
+ _device.BeginScene();
+ _device.SetRenderState(RenderState.CullMode, Cull.None);
+ _device.SetRenderState(RenderState.ZEnable, false);
+ _device.SetRenderState(RenderState.ZWriteEnable, false);
+ _device.SetRenderState(RenderState.Lighting, false);
+ }
+
+ public void EndScene()
+ => _device.EndScene();
+ }
+}
diff --git a/src/BizHawk.Bizware.Graphics/D3D9/SharpDXExtensions.cs b/src/BizHawk.Bizware.Graphics/D3D9/SharpDXExtensions.cs
new file mode 100644
index 00000000000..a0b086d2d87
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/D3D9/SharpDXExtensions.cs
@@ -0,0 +1,38 @@
+using System.Drawing;
+
+using BizHawk.Bizware.BizwareGL;
+
+using SharpDX.Mathematics.Interop;
+
+namespace BizHawk.Bizware.Graphics
+{
+ internal static class SharpDXExtensions
+ {
+ // SharpDX RawMatrix and BizwareGL Matrix are identical in structure
+ public static RawMatrix ToSharpDXMatrix(this Matrix4 m, bool transpose)
+ {
+ // Transpose call could be inlined to reduce 2 sets of copies to 1
+ if (transpose)
+ {
+ m = Matrix4.Transpose(in m);
+ }
+
+ return new()
+ {
+ M11 = m.Row0.X, M12 = m.Row0.Y, M13 = m.Row0.Z, M14 = m.Row0.W,
+ M21 = m.Row1.X, M22 = m.Row1.Y, M23 = m.Row1.Z, M24 = m.Row1.W,
+ M31 = m.Row2.X, M32 = m.Row2.Y, M33 = m.Row2.Z, M34 = m.Row2.W,
+ M41 = m.Row3.X, M42 = m.Row3.Y, M43 = m.Row3.Z, M44 = m.Row3.W
+ };
+ }
+
+ public static RawVector2 ToSharpDXVector2(this Vector2 v)
+ => new(v.X, v.Y);
+
+ public static RawVector4 ToSharpDXVector4(this Vector4 v)
+ => new(v.X, v.Y, v.Z, v.W);
+
+ public static RawColorBGRA ToSharpDXColor(this Color c)
+ => new(c.B, c.G, c.R, c.A);
+ }
+}
diff --git a/src/BizHawk.Bizware.OpenTK3/GuiRenderer.cs b/src/BizHawk.Bizware.Graphics/GuiRenderer.cs
similarity index 79%
rename from src/BizHawk.Bizware.OpenTK3/GuiRenderer.cs
rename to src/BizHawk.Bizware.Graphics/GuiRenderer.cs
index 67b26890ef5..1c3efcb6b9b 100644
--- a/src/BizHawk.Bizware.OpenTK3/GuiRenderer.cs
+++ b/src/BizHawk.Bizware.Graphics/GuiRenderer.cs
@@ -3,13 +3,14 @@
//why this stupid assert on the blendstate. just set one by default, geeze.
using System;
+#if DEBUG
using System.Diagnostics;
+#endif
+using System.Drawing;
using BizHawk.Bizware.BizwareGL;
-using sd = System.Drawing;
-
-namespace BizHawk.Bizware.OpenTK3
+namespace BizHawk.Bizware.Graphics
{
///
/// A simple renderer useful for rendering GUI stuff.
@@ -24,13 +25,13 @@ public GuiRenderer(IGL owner)
Owner = owner;
VertexLayout = owner.CreateVertexLayout();
- VertexLayout.DefineVertexAttribute("aPosition", 0, 2, VertexAttribPointerType.Float, AttribUsage.Position, false, 32, 0);
+ VertexLayout.DefineVertexAttribute("aPosition", 0, 2, VertexAttribPointerType.Float, AttribUsage.Position, false, 32);
VertexLayout.DefineVertexAttribute("aTexcoord", 1, 2, VertexAttribPointerType.Float, AttribUsage.Texcoord0, false, 32, 8);
VertexLayout.DefineVertexAttribute("aColor", 2, 4, VertexAttribPointerType.Float, AttribUsage.Texcoord1, false, 32, 16);
VertexLayout.Close();
- _Projection = new MatrixStack();
- _Modelview = new MatrixStack();
+ _Projection = new();
+ _Modelview = new();
string psProgram, vsProgram;
@@ -50,7 +51,13 @@ public GuiRenderer(IGL owner)
CurrPipeline = DefaultPipeline = Owner.CreatePipeline(VertexLayout, vs, ps, true, "xgui");
}
- private readonly Vector4[] CornerColors = { new(1.0f, 1.0f, 1.0f, 1.0f), new(1.0f, 1.0f, 1.0f, 1.0f), new(1.0f, 1.0f, 1.0f, 1.0f), new(1.0f, 1.0f, 1.0f, 1.0f) };
+ private readonly Vector4[] CornerColors =
+ {
+ new(1.0f, 1.0f, 1.0f, 1.0f),
+ new(1.0f, 1.0f, 1.0f, 1.0f),
+ new(1.0f, 1.0f, 1.0f, 1.0f),
+ new(1.0f, 1.0f, 1.0f, 1.0f)
+ };
public void SetCornerColor(int which, Vector4 color)
{
@@ -63,7 +70,7 @@ public void SetCornerColors(Vector4[] colors)
{
Flush(); //don't really need to flush with current implementation. we might as well roll modulate color into it too.
if (colors.Length != 4) throw new ArgumentException("array must be size 4", nameof(colors));
- for (int i = 0; i < 4; i++)
+ for (var i = 0; i < 4; i++)
CornerColors[i] = colors[i];
}
@@ -93,10 +100,10 @@ public void SetDefaultPipeline()
public void SetModulateColorWhite()
{
- SetModulateColor(sd.Color.White);
+ SetModulateColor(Color.White);
}
- public void SetModulateColor(sd.Color color)
+ public void SetModulateColor(Color color)
{
Flush();
CurrPipeline["uModulateColor"].Set(new Vector4(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f));
@@ -131,7 +138,7 @@ public MatrixStack Modelview
}
}
- public void Begin(sd.Size size) { Begin(size.Width, size.Height); }
+ public void Begin(Size size) { Begin(size.Width, size.Height); }
public void Begin(int width, int height)
{
@@ -220,42 +227,60 @@ public void Draw(Texture2d art, float x, float y, float width, float height)
private void DrawInternal(Texture2d tex, float x, float y, float w, float h)
{
- Art art = new Art((ArtManager)null);
- art.Width = w;
- art.Height = h;
+ var art = new Art((ArtManager)null)
+ {
+ Width = w,
+ Height = h
+ };
+
art.u0 = art.v0 = 0;
art.u1 = art.v1 = 1;
art.BaseTexture = tex;
+
DrawInternal(art, x, y, w, h, false, tex.IsUpsideDown);
}
private unsafe void DrawInternal(Art art, float x, float y, float w, float h, bool fx, bool fy)
{
- //TEST: d3d shouldn't ever use this, it was a gl hack. maybe we can handle it some other way in gl (fix the projection? take a render-to-texture arg to the gui view transforms?)
- fy = false;
-
float u0, v0, u1, v1;
- if (fx) { u0 = art.u1; u1 = art.u0; }
- else { u0 = art.u0; u1 = art.u1; }
- if (fy) { v0 = art.v1; v1 = art.v0; }
- else { v0 = art.v0; v1 = art.v1; }
-
- float[] data = new float[32] {
- x,y, u0,v0, CornerColors[0].X, CornerColors[0].Y, CornerColors[0].Z, CornerColors[0].W,
- x+art.Width,y, u1,v0, CornerColors[1].X, CornerColors[1].Y, CornerColors[1].Z, CornerColors[1].W,
- x,y+art.Height, u0,v1, CornerColors[2].X, CornerColors[2].Y, CornerColors[2].Z, CornerColors[2].W,
- x+art.Width,y+art.Height, u1,v1, CornerColors[3].X, CornerColors[3].Y, CornerColors[3].Z, CornerColors[3].W,
- };
- Texture2d tex = art.BaseTexture;
-
- PrepDrawSubrectInternal(tex);
+ if (fx)
+ {
+ u0 = art.u1;
+ u1 = art.u0;
+ }
+ else
+ {
+ u0 = art.u0;
+ u1 = art.u1;
+ }
- fixed (float* pData = &data[0])
+ if (fy)
+ {
+ v0 = art.v1;
+ v1 = art.v0;
+ }
+ else
{
- Owner.BindArrayData(new(pData));
- Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
+ v0 = art.v0;
+ v1 = art.v1;
}
+
+ var data = stackalloc float[32]
+ {
+ x, y, u0, v0,
+ CornerColors[0].X, CornerColors[0].Y, CornerColors[0].Z, CornerColors[0].W,
+ x + w, y, u1, v0,
+ CornerColors[1].X, CornerColors[1].Y, CornerColors[1].Z, CornerColors[1].W,
+ x, y + h, u0, v1,
+ CornerColors[2].X, CornerColors[2].Y, CornerColors[2].Z, CornerColors[2].W,
+ x + w, y + h, u1, v1,
+ CornerColors[3].X, CornerColors[3].Y, CornerColors[3].Z, CornerColors[3].W,
+ };
+
+ PrepDrawSubrectInternal(art.BaseTexture);
+ Owner.BindArrayData(new(data));
+ Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private void PrepDrawSubrectInternal(Texture2d tex)
@@ -264,31 +289,24 @@ private void PrepDrawSubrectInternal(Texture2d tex)
{
sTexture = tex;
CurrPipeline["uSampler0"].Set(tex);
- if (sTexture == null)
- {
- CurrPipeline["uSamplerEnable"].Set(false);
- }
- else
- {
- CurrPipeline["uSamplerEnable"].Set(true);
- }
+ CurrPipeline["uSamplerEnable"].Set(tex != null);
}
if (_Projection.IsDirty)
{
- CurrPipeline["um44Projection"].Set(ref _Projection.Top, false);
+ CurrPipeline["um44Projection"].Set(ref _Projection.Top);
_Projection.IsDirty = false;
}
if (_Modelview.IsDirty)
{
- CurrPipeline["um44Modelview"].Set(ref _Modelview.Top, false);
+ CurrPipeline["um44Modelview"].Set(ref _Modelview.Top);
_Modelview.IsDirty = false;
}
}
private unsafe void EmitRectangleInternal(float x, float y, float w, float h, float u0, float v0, float u1, float v1)
{
- float* pData = stackalloc float[32];
+ var pData = stackalloc float[32];
pData[0] = x;
pData[1] = y;
pData[2] = u0;
@@ -351,7 +369,7 @@ private void DrawSubrectInternal(Texture2d tex, float x, float y, float w, float
//shaders are hand-coded for each platform to make sure they stay as fast as possible
- public readonly string DefaultShader_d3d9 = @"
+ public const string DefaultShader_d3d9 = @"
//vertex shader uniforms
float4x4 um44Modelview, um44Projection;
float4 uModulateColor;
@@ -399,8 +417,9 @@ float4 psmain(PS_INPUT src) : COLOR
}
";
- public readonly string DefaultVertexShader_gl = @"
-#version 110 //opengl 2.0 ~ 2004
+ public const string DefaultVertexShader_gl = @"
+//opengl 2.0 ~ 2004
+#version 110
uniform mat4 um44Modelview, um44Projection;
uniform vec4 uModulateColor;
@@ -423,7 +442,8 @@ void main()
}";
public readonly string DefaultPixelShader_gl = @"
-#version 110 //opengl 2.0 ~ 2004
+//opengl 2.0 ~ 2004
+#version 110
uniform bool uSamplerEnable;
uniform sampler2D uSampler0;
diff --git a/src/BizHawk.Bizware.Graphics/OpenGL/IGL_OpenGL.cs b/src/BizHawk.Bizware.Graphics/OpenGL/IGL_OpenGL.cs
new file mode 100644
index 00000000000..fa23167641a
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/OpenGL/IGL_OpenGL.cs
@@ -0,0 +1,864 @@
+// regarding binding and vertex arrays:
+// http://stackoverflow.com/questions/8704801/glvertexattribpointer-clarification
+// http://stackoverflow.com/questions/9536973/oes-vertex-array-object-and-client-state
+// http://www.opengl.org/wiki/Vertex_Specification
+
+// etc
+// glBindAttribLocation (programID, 0, "vertexPosition_modelspace");
+
+using System;
+using System.Drawing;
+using System.IO;
+using System.Collections.Generic;
+
+using BizHawk.Bizware.BizwareGL;
+using BizHawk.Common;
+
+using Silk.NET.OpenGL.Legacy;
+
+using BizClearBufferMask = BizHawk.Bizware.BizwareGL.ClearBufferMask;
+using BizPrimitiveType = BizHawk.Bizware.BizwareGL.PrimitiveType;
+
+using BizShader = BizHawk.Bizware.BizwareGL.Shader;
+
+using BizTextureMagFilter = BizHawk.Bizware.BizwareGL.TextureMagFilter;
+using BizTextureMinFilter = BizHawk.Bizware.BizwareGL.TextureMinFilter;
+
+using GLClearBufferMask = Silk.NET.OpenGL.Legacy.ClearBufferMask;
+using GLPrimitiveType = Silk.NET.OpenGL.Legacy.PrimitiveType;
+using GLVertexAttribPointerType = Silk.NET.OpenGL.Legacy.VertexAttribPointerType;
+
+namespace BizHawk.Bizware.Graphics
+{
+ ///
+ /// OpenGL implementation of the BizwareGL.IGL interface
+ ///
+ public class IGL_OpenGL : IGL
+ {
+ public EDispMethod DispMethodEnum => EDispMethod.OpenGL;
+
+ private readonly SDL2OpenGLContext OffscreenContext;
+ private SDL2OpenGLContext ActiveContext;
+ private GL GL => ActiveContext.GL;
+
+ private readonly int _majorVersion, _minorVersion;
+ private readonly bool _forwardCompatible;
+
+ // rendering state
+ private Pipeline _currPipeline;
+ private RenderTarget _currRenderTarget;
+
+ public string API => "OPENGL";
+
+ public int Version
+ {
+ get
+ {
+ // doesnt work on older than gl3 maybe
+ // int major, minor;
+ // // other overloads may not exist...
+ // GL.GetInteger(GetPName.MajorVersion, out major);
+ // GL.GetInteger(GetPName.MinorVersion, out minor);
+
+ // supposedly the standard dictates that whatever junk is in the version string, some kind of version is at the beginning
+ // can be either in major_number.minor_number or major_number.minor_number.release_number, with vendor specific info afterwards
+ var versionString = GL.GetStringS(StringName.Version);
+ var versionParts = versionString.Split('.');
+ var major = int.Parse(versionParts[0]);
+ var minor = int.Parse(versionParts[1][0].ToString());
+ // getting a release number is too hard and not needed now
+ return major * 100 + minor * 10;
+ }
+ }
+
+ public IGL_OpenGL(int majorVersion, int minorVersion, bool forwardCompatible)
+ {
+ // create an offscreen context, so things can be done without a control
+ ActiveContext = OffscreenContext = new(majorVersion, minorVersion, forwardCompatible);
+
+ _majorVersion = majorVersion;
+ _minorVersion = minorVersion;
+ _forwardCompatible = forwardCompatible;
+
+ // misc initialization
+ CreateRenderStates();
+ }
+
+ public void BeginScene()
+ {
+ }
+
+ public void EndScene()
+ {
+ }
+
+ public void Dispose()
+ {
+ OffscreenContext.Dispose();
+ }
+
+ public void Clear(BizClearBufferMask mask)
+ {
+ GL.Clear((GLClearBufferMask)mask); // these are the same enum
+ }
+
+ public void SetClearColor(Color color)
+ {
+ GL.ClearColor(color);
+ }
+
+ public IGraphicsControl Internal_CreateGraphicsControl()
+ {
+ var ret = new OpenGLControl(_majorVersion, _minorVersion, _forwardCompatible, ContextChangeCallback);
+ ret.CreateControl(); // DisplayManager relies on this context being active for creating the GuiRenderer
+ return ret;
+ }
+
+ public uint GenTexture() => GL.GenTexture();
+
+ public void FreeTexture(Texture2d tex)
+ {
+ GL.DeleteTexture((uint)tex.Opaque);
+ }
+
+ public BizShader CreateFragmentShader(string source, string entry, bool required)
+ {
+ return CreateShader(ShaderType.FragmentShader, source, required);
+ }
+
+ public BizShader CreateVertexShader(string source, string entry, bool required)
+ {
+ return CreateShader(ShaderType.VertexShader, source, required);
+ }
+
+ public IBlendState CreateBlendState(
+ BlendingFactorSrc colorSource,
+ BlendEquationMode colorEquation,
+ BlendingFactorDest colorDest,
+ BlendingFactorSrc alphaSource,
+ BlendEquationMode alphaEquation,
+ BlendingFactorDest alphaDest)
+ {
+ return new CacheBlendState(true, colorSource, colorEquation, colorDest, alphaSource, alphaEquation, alphaDest);
+ }
+
+ public void SetBlendState(IBlendState rsBlend)
+ {
+ var mybs = (CacheBlendState)rsBlend;
+ if (mybs.Enabled)
+ {
+ GL.Enable(EnableCap.Blend);
+ // these are all casts to copies of the same enum
+ GL.BlendEquationSeparate(
+ (BlendEquationModeEXT)mybs.colorEquation,
+ (BlendEquationModeEXT)mybs.alphaEquation);
+ GL.BlendFuncSeparate(
+ (BlendingFactor)mybs.colorSource,
+ (BlendingFactor)mybs.colorDest,
+ (BlendingFactor)mybs.alphaSource,
+ (BlendingFactor)mybs.alphaDest);
+ }
+ else
+ {
+ GL.Disable(EnableCap.Blend);
+ }
+
+ if (rsBlend == _rsBlendNoneOpaque)
+ {
+ //make sure constant color is set correctly
+ GL.BlendColor(Color.FromArgb(255, 255, 255, 255));
+ }
+ }
+
+ public IBlendState BlendNoneCopy => _rsBlendNoneVerbatim;
+ public IBlendState BlendNoneOpaque => _rsBlendNoneOpaque;
+ public IBlendState BlendNormal => _rsBlendNormal;
+
+ private class ShaderWrapper
+ {
+ public uint sid;
+ }
+
+ private class PipelineWrapper
+ {
+ public uint pid;
+ public BizShader FragmentShader, VertexShader;
+ public List SamplerLocs;
+ }
+
+ ///
+ /// is and either or is unavailable (their property is ), or
+ /// glLinkProgram call did not produce expected result
+ ///
+ public Pipeline CreatePipeline(VertexLayout vertexLayout, BizShader vertexShader, BizShader fragmentShader, bool required, string memo)
+ {
+ // if the shaders aren't available, the pipeline isn't either
+ if (!vertexShader.Available || !fragmentShader.Available)
+ {
+ var errors = $"Vertex Shader:\r\n {vertexShader.Errors} \r\n-------\r\nFragment Shader:\r\n{fragmentShader.Errors}";
+ if (required)
+ {
+ throw new InvalidOperationException($"Couldn't build required GL pipeline:\r\n{errors}");
+ }
+
+ return new(this, null, false, null, null, null) { Errors = errors };
+ }
+
+ var success = true;
+
+ var vsw = (ShaderWrapper)vertexShader.Opaque;
+ var fsw = (ShaderWrapper)fragmentShader.Opaque;
+
+ var pid = GL.CreateProgram();
+ GL.AttachShader(pid, vsw.sid);
+ _ = GL.GetError();
+ GL.AttachShader(pid, fsw.sid);
+ _ = GL.GetError();
+
+ GL.LinkProgram(pid);
+ _ = GL.GetError();
+
+ var errcode = (ErrorCode)GL.GetError();
+ var resultLog = GL.GetProgramInfoLog(pid);
+
+ if (errcode != ErrorCode.NoError)
+ {
+ if (required)
+ {
+ throw new InvalidOperationException($"Error creating pipeline (error returned from glLinkProgram): {errcode}\r\n\r\n{resultLog}");
+ }
+
+ success = false;
+ }
+
+ GL.GetProgram(pid, ProgramPropertyARB.LinkStatus, out var linkStatus);
+ if (linkStatus == 0)
+ {
+ if (required)
+ {
+ throw new InvalidOperationException($"Error creating pipeline (link status false returned from glLinkProgram): \r\n\r\n{resultLog}");
+ }
+
+ success = false;
+#if DEBUG
+ resultLog = GL.GetProgramInfoLog(pid);
+ Util.DebugWriteLine(resultLog);
+#endif
+ }
+
+ // need to work on validation. apparently there are some weird caveats to glValidate which make it complicated and possibly excuses (barely) the intel drivers' dysfunctional operation
+ // "A sampler points to a texture unit used by fixed function with an incompatible target"
+ //
+ // info:
+ // http://www.opengl.org/sdk/docs/man/xhtml/glValidateProgram.xml
+ // This function mimics the validation operation that OpenGL implementations must perform when rendering commands are issued while programmable shaders are part of current state.
+ // glValidateProgram checks to see whether the executables contained in program can execute given the current OpenGL state
+ // This function is typically useful only during application development.
+ //
+ // So, this is no big deal. we shouldn't be calling validate right now anyway.
+ // conclusion: glValidate is very complicated and is of virtually no use unless your draw calls are returning errors and you want to know why
+ // GL.ValidateProgram(pid);
+ // errcode = (ErrorCode)GL.GetError();
+ // resultLog = GL.GetProgramInfoLog(pid);
+ // if (errcode != ErrorCode.NoError)
+ // throw new InvalidOperationException($"Error creating pipeline (error returned from glValidateProgram): {errcode}\r\n\r\n{resultLog}");
+ // GL.GetProgram(pid, GetProgramParameterName.ValidateStatus, out var validateStatus);
+ // if (validateStatus == 0)
+ // throw new InvalidOperationException($"Error creating pipeline (validateStatus status false returned from glValidateProgram): \r\n\r\n{resultLog}");
+
+ // set the program to active, in case we need to set sampler uniforms on it
+ GL.UseProgram(pid);
+
+#if false
+ //get all the attributes (not needed)
+ var attributes = new List();
+ GL.GetProgram(pid, ProgramPropertyARB.ActiveAttributes, out var nAttributes);
+ for (uint i = 0; i < nAttributes; i++)
+ {
+ GL.GetActiveAttrib(pid, i, 1024, out _, out _, out AttributeType _, out string name);
+ attributes.Add(new() { Handle = new(i), Name = name });
+ }
+#endif
+
+ // get all the uniforms
+ var uniforms = new List();
+ GL.GetProgram(pid, ProgramPropertyARB.ActiveUniforms, out var nUniforms);
+ var samplers = new List();
+
+ for (uint i = 0; i < nUniforms; i++)
+ {
+ GL.GetActiveUniform(pid, i, 1024, out _, out _, out UniformType type, out string name);
+ _ = GL.GetError();
+ var loc = GL.GetUniformLocation(pid, name);
+
+ var ui = new UniformInfo { Name = name, Opaque = loc };
+
+ if (type == UniformType.Sampler2D)
+ {
+ ui.IsSampler = true;
+ ui.SamplerIndex = samplers.Count;
+ ui.Opaque = loc | (samplers.Count << 24);
+ samplers.Add(loc);
+ }
+
+ uniforms.Add(ui);
+ }
+
+ // deactivate the program, so we don't accidentally use it
+ GL.UseProgram(0);
+
+ if (!vertexShader.Available) success = false;
+ if (!fragmentShader.Available) success = false;
+
+ var pw = new PipelineWrapper { pid = pid, VertexShader = vertexShader, FragmentShader = fragmentShader, SamplerLocs = samplers };
+
+ return new(this, pw, success, vertexLayout, uniforms, memo);
+ }
+
+ public void FreePipeline(Pipeline pipeline)
+ {
+ // unavailable pipelines will have no opaque
+ if (pipeline.Opaque is not PipelineWrapper pw)
+ {
+ return;
+ }
+
+ GL.DeleteProgram(pw.pid);
+
+ pw.FragmentShader.Release();
+ pw.VertexShader.Release();
+ }
+
+ public void Internal_FreeShader(BizShader shader)
+ {
+ var sw = (ShaderWrapper)shader.Opaque;
+ GL.DeleteShader(sw.sid);
+ }
+
+ /// . is
+ public void BindPipeline(Pipeline pipeline)
+ {
+ _currPipeline = pipeline;
+
+ if (pipeline == null)
+ {
+ sStatePendingVertexLayout = null;
+ GL.UseProgram(0);
+ return;
+ }
+
+ if (!pipeline.Available) throw new InvalidOperationException("Attempt to bind unavailable pipeline");
+ sStatePendingVertexLayout = pipeline.VertexLayout;
+
+ var pw = (PipelineWrapper)pipeline.Opaque;
+ GL.UseProgram(pw.pid);
+
+ // this is dumb and confusing, but we have to bind physical sampler numbers to sampler variables.
+ for (var i = 0; i < pw.SamplerLocs.Count; i++)
+ {
+ GL.Uniform1(pw.SamplerLocs[i], i);
+ }
+ }
+
+ public VertexLayout CreateVertexLayout() => new(this, null);
+
+ private void BindTexture2d(Texture2d tex)
+ {
+ GL.BindTexture(TextureTarget.Texture2D, (uint)tex.Opaque);
+ }
+
+ public void SetTextureWrapMode(Texture2d tex, bool clamp)
+ {
+ BindTexture2d(tex);
+
+ var mode = clamp ? TextureWrapMode.ClampToEdge : TextureWrapMode.Repeat;
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)mode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)mode);
+ }
+
+ public void BindArrayData(IntPtr pData) => MyBindArrayData(sStatePendingVertexLayout, pData);
+
+ public void DrawArrays(BizPrimitiveType mode, int first, int count)
+ {
+ GL.DrawArrays((GLPrimitiveType)mode, first, (uint)count); // these are the same enum
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, bool value)
+ {
+ GL.Uniform1((int)uniform.Sole.Opaque, value ? 1 : 0);
+ }
+
+ public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4 mat, bool transpose)
+ {
+ GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)&mat);
+ }
+
+ public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4 mat, bool transpose)
+ {
+ fixed (Matrix4* pMat = &mat)
+ {
+ GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)pMat);
+ }
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
+ {
+ GL.Uniform4((int)uniform.Sole.Opaque, value.X, value.Y, value.Z, value.W);
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
+ {
+ GL.Uniform2((int)uniform.Sole.Opaque, value.X, value.Y);
+ }
+
+ public void SetPipelineUniform(PipelineUniform uniform, float value)
+ {
+ if (uniform.Owner == null)
+ {
+ return; // uniform was optimized out
+ }
+
+ GL.Uniform1((int)uniform.Sole.Opaque, value);
+ }
+
+ public unsafe void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
+ {
+ fixed (Vector4* pValues = &values[0])
+ {
+ GL.Uniform4((int)uniform.Sole.Opaque, (uint)values.Length, (float*)pValues);
+ }
+ }
+
+ public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
+ {
+ var n = (int)uniform.Sole.Opaque >> 24;
+
+ // set the sampler index into the uniform first
+ if (sActiveTexture != n)
+ {
+ sActiveTexture = n;
+ var selectedUnit = TextureUnit.Texture0 + n;
+ GL.ActiveTexture(selectedUnit);
+ }
+
+ // now bind the texture
+ GL.BindTexture(TextureTarget.Texture2D, (uint)tex.Opaque);
+ }
+
+ public void SetMinFilter(Texture2d texture, BizTextureMinFilter minFilter)
+ {
+ BindTexture2d(texture);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)minFilter);
+ }
+
+ public void SetMagFilter(Texture2d texture, BizTextureMagFilter magFilter)
+ {
+ BindTexture2d(texture);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)magFilter);
+ }
+
+ public Texture2d LoadTexture(Bitmap bitmap)
+ {
+ using var bmp = new BitmapBuffer(bitmap, new());
+ return LoadTexture(bmp);
+ }
+
+ public Texture2d LoadTexture(Stream stream)
+ {
+ using var bmp = new BitmapBuffer(stream, new());
+ return LoadTexture(bmp);
+ }
+
+ public Texture2d CreateTexture(int width, int height)
+ {
+ var id = GenTexture();
+ return new(this, id, width, height);
+ }
+
+ public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
+ {
+ return new(this, glTexId.ToInt32(), width, height);
+ }
+
+ public unsafe void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
+ {
+ var bmpData = bmp.LockBits();
+ try
+ {
+ GL.BindTexture(TextureTarget.Texture2D, (uint)tex.Opaque);
+ GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, (uint)bmp.Width, (uint)bmp.Height, PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0.ToPointer());
+ }
+ finally
+ {
+ bmp.UnlockBits(bmpData);
+ }
+ }
+
+ public void FreeRenderTarget(RenderTarget rt)
+ {
+ rt.Texture2d.Dispose();
+ GL.DeleteFramebuffer((uint)rt.Opaque);
+ }
+
+ /// framebuffer creation unsuccessful
+ public unsafe RenderTarget CreateRenderTarget(int w, int h)
+ {
+ // create a texture for it
+ var texId = GenTexture();
+ var tex = new Texture2d(this, texId, w, h);
+
+ GL.BindTexture(TextureTarget.Texture2D, texId);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)w, (uint)h, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero.ToPointer());
+ tex.SetMagFilter(BizTextureMagFilter.Nearest);
+ tex.SetMinFilter(BizTextureMinFilter.Nearest);
+
+ // create the FBO
+ var fbId = GL.GenFramebuffer();
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, fbId);
+
+ // bind the tex to the FBO
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texId, 0);
+
+ // do something, I guess say which color buffers are used by the framebuffer
+ var buffers = stackalloc DrawBufferMode[1];
+ buffers[0] = DrawBufferMode.ColorAttachment0;
+ GL.DrawBuffers(1, buffers);
+
+ if ((FramebufferStatus)GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferStatus.Complete)
+ {
+ throw new InvalidOperationException($"Error creating framebuffer (at {nameof(GL.CheckFramebufferStatus)})");
+ }
+
+ // since we're done configuring unbind this framebuffer, to return to the default
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+
+ return new(this, fbId, tex);
+ }
+
+ public void BindRenderTarget(RenderTarget rt)
+ {
+ _currRenderTarget = rt;
+ if (rt == null)
+ {
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+ }
+ else
+ {
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, (uint)rt.Opaque);
+ }
+ }
+
+ public unsafe Texture2d LoadTexture(BitmapBuffer bmp)
+ {
+ Texture2d ret;
+ var id = GenTexture();
+ try
+ {
+ ret = new(this, id, bmp.Width, bmp.Height);
+ GL.BindTexture(TextureTarget.Texture2D, id);
+ // picking a color order that matches doesnt seem to help, any. maybe my driver is accelerating it, or maybe it isnt a big deal. but its something to study on another day
+ GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)bmp.Width, (uint)bmp.Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero.ToPointer());
+ LoadTextureData(ret, bmp);
+ }
+ catch
+ {
+ GL.DeleteTexture(id);
+ throw;
+ }
+
+ // set default filtering... its safest to do this always
+ ret.SetFilterNearest();
+
+ return ret;
+ }
+
+ public unsafe BitmapBuffer ResolveTexture2d(Texture2d tex)
+ {
+ // note - this is dangerous since it changes the bound texture. could we save it?
+ BindTexture2d(tex);
+ var bb = new BitmapBuffer(tex.IntWidth, tex.IntHeight);
+ var bmpdata = bb.LockBits();
+ GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgra, PixelType.UnsignedByte, bmpdata.Scan0.ToPointer());
+ _ = GL.GetError();
+ bb.UnlockBits(bmpdata);
+ return bb;
+ }
+
+ public Texture2d LoadTexture(string path)
+ {
+ using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return LoadTexture(fs);
+ }
+
+ public Matrix4 CreateGuiProjectionMatrix(int w, int h)
+ {
+ return CreateGuiProjectionMatrix(new(w, h));
+ }
+
+ public Matrix4 CreateGuiViewMatrix(int w, int h, bool autoflip)
+ {
+ return CreateGuiViewMatrix(new(w, h), autoflip);
+ }
+
+ public Matrix4 CreateGuiProjectionMatrix(Size dims)
+ {
+ var ret = Matrix4.Identity;
+ ret.Row0.X = 2.0f / dims.Width;
+ ret.Row1.Y = 2.0f / dims.Height;
+ return ret;
+ }
+
+ public Matrix4 CreateGuiViewMatrix(Size dims, bool autoflip)
+ {
+ var ret = Matrix4.Identity;
+ ret.Row1.Y = -1.0f;
+ ret.Row3.X = dims.Width * -0.5f;
+ ret.Row3.Y = dims.Height * 0.5f;
+ if (autoflip && _currRenderTarget is not null) // flip as long as we're not a final render target
+ {
+ ret.Row1.Y = 1.0f;
+ ret.Row3.Y *= -1;
+ }
+ return ret;
+ }
+
+ public void SetViewport(int x, int y, int width, int height)
+ {
+ GL.Viewport(x, y, (uint)width, (uint)height);
+ GL.Scissor(x, y, (uint)width, (uint)height); // hack for mupen[rice]+intel: at least the rice plugin leaves the scissor rectangle scrambled, and we're trying to run it in the main graphics context for intel
+ // BUT ALSO: new specifications.. viewport+scissor make sense together
+ }
+
+ public void SetViewport(int width, int height)
+ {
+ SetViewport(0, 0, width, height);
+ }
+
+ public void SetViewport(Size size)
+ {
+ SetViewport(size.Width, size.Height);
+ }
+
+ // my utility methods
+
+ private BizShader CreateShader(ShaderType type, string source, bool required)
+ {
+ var sw = new ShaderWrapper();
+ var info = string.Empty;
+
+ var sid = GL.CreateShader(type);
+ var ok = CompileShaderSimple(sid, source, required);
+ if (!ok)
+ {
+ GL.GetShaderInfoLog(sid, out info);
+ GL.DeleteShader(sid);
+ sid = 0;
+ }
+
+ var ret = new BizShader(this, sw, ok)
+ {
+ Errors = info
+ };
+
+ sw.sid = sid;
+
+ return ret;
+ }
+
+ private bool CompileShaderSimple(uint sid, string source, bool required)
+ {
+ var success = true;
+
+ var errcode = (ErrorCode)GL.GetError();
+ if (errcode != ErrorCode.NoError)
+ {
+ if (required)
+ {
+ throw new InvalidOperationException($"Error compiling shader (from previous operation) {errcode}");
+ }
+
+ success = false;
+ }
+
+ GL.ShaderSource(sid, source);
+
+ errcode = (ErrorCode)GL.GetError();
+ if (errcode != ErrorCode.NoError)
+ {
+ if (required)
+ {
+ throw new InvalidOperationException($"Error compiling shader ({nameof(GL.ShaderSource)}) {errcode}");
+ }
+
+ success = false;
+ }
+
+ GL.CompileShader(sid);
+
+ errcode = (ErrorCode)GL.GetError();
+ var resultLog = GL.GetShaderInfoLog(sid);
+ if (errcode != ErrorCode.NoError)
+ {
+ var message = $"Error compiling shader ({nameof(GL.CompileShader)}) {errcode}\r\n\r\n{resultLog}";
+ if (required)
+ {
+ throw new InvalidOperationException(message);
+ }
+
+ Console.WriteLine(message);
+ success = false;
+ }
+
+ GL.GetShader(sid, ShaderParameterName.CompileStatus, out var n);
+
+ if (n == 0)
+ {
+ if (required)
+ {
+ throw new InvalidOperationException($"Error compiling shader ({nameof(GL.GetShader)})\r\n\r\n{resultLog}");
+ }
+
+ success = false;
+ }
+
+ return success;
+ }
+
+ private void UnbindVertexAttributes()
+ {
+ // HAMNUTS:
+ // its not clear how many bindings we'll have to disable before we can enable the ones we need..
+ // so lets just disable the ones we remember we have bound
+ foreach (var index in sVertexAttribEnables)
+ {
+ GL.DisableVertexAttribArray(index);
+ }
+
+ sVertexAttribEnables.Clear();
+ }
+
+ private unsafe void MyBindArrayData(VertexLayout layout, IntPtr pData)
+ {
+ UnbindVertexAttributes();
+
+ // HAMNUTS (continued)
+
+ // DEPRECATED CRAP USED, I DON'T KNOW WHAT TO USE NOW
+ // ALSO THIS IS WHY LEGACY PACKAGE IS USED AS NON-LEGACY DOESN'T HAVE THESE
+#pragma warning disable CS0618
+#pragma warning disable CS0612
+
+ if (layout == null) return;
+
+ // disable all the client states.. a lot of overhead right now, to be sure
+
+ GL.DisableClientState(EnableCap.VertexArray);
+ GL.DisableClientState(EnableCap.ColorArray);
+
+ for (uint i = 0; i < 8; i++)
+ {
+ GL.DisableVertexAttribArray(i);
+ }
+
+ for (var i = 0; i < 8; i++)
+ {
+ GL.ClientActiveTexture(TextureUnit.Texture0 + i);
+ GL.DisableClientState(EnableCap.TextureCoordArray);
+ }
+
+ GL.ClientActiveTexture(TextureUnit.Texture0);
+
+ foreach (var (i, item) in layout.Items)
+ {
+ if (_currPipeline.Memo == "gui")
+ {
+ GL.VertexAttribPointer(
+ (uint)i,
+ item.Components,
+ (GLVertexAttribPointerType)item.AttribType, // these are the same enum
+ item.Normalized,
+ (uint)item.Stride,
+ (pData + item.Offset).ToPointer());
+ GL.EnableVertexAttribArray((uint)i);
+ sVertexAttribEnables.Add((uint)i);
+ }
+ else
+ {
+ // comment SNACKPANTS
+ switch (item.Usage)
+ {
+ case AttribUsage.Position:
+ GL.EnableClientState(EnableCap.VertexArray);
+ GL.VertexPointer(item.Components, VertexPointerType.Float, (uint)item.Stride, (pData + item.Offset).ToPointer());
+ break;
+ case AttribUsage.Texcoord0:
+ GL.ClientActiveTexture(TextureUnit.Texture0);
+ GL.EnableClientState(EnableCap.TextureCoordArray);
+ GL.TexCoordPointer(item.Components, TexCoordPointerType.Float, (uint)item.Stride, (pData + item.Offset).ToPointer());
+ break;
+ case AttribUsage.Texcoord1:
+ GL.ClientActiveTexture(TextureUnit.Texture1);
+ GL.EnableClientState(EnableCap.TextureCoordArray);
+ GL.TexCoordPointer(item.Components, TexCoordPointerType.Float, (uint)item.Stride, (pData + item.Offset).ToPointer());
+ GL.ClientActiveTexture(TextureUnit.Texture0);
+ break;
+ case AttribUsage.Color0:
+ break;
+ case AttribUsage.Unspecified:
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+ }
+#pragma warning restore CS0618
+#pragma warning restore CS0612
+ }
+
+ private void CreateRenderStates()
+ {
+ _rsBlendNoneVerbatim = new(
+ false,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
+
+ _rsBlendNoneOpaque = new(
+ false,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
+ BlendingFactorSrc.ConstantAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
+
+ _rsBlendNormal = new(
+ true,
+ BlendingFactorSrc.SrcAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.OneMinusSrcAlpha,
+ BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
+ }
+
+ private CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal;
+
+ // state caches
+ private int sActiveTexture = -1;
+ private VertexLayout sStatePendingVertexLayout;
+ private readonly HashSet sVertexAttribEnables = new();
+
+ private void ContextChangeCallback(SDL2OpenGLContext context)
+ {
+ // null means the current context is being cleared
+ // set it back to the offscreen context
+ if (context is null)
+ {
+ OffscreenContext.MakeContextCurrent();
+ context = OffscreenContext;
+ }
+
+ sActiveTexture = -1;
+ sStatePendingVertexLayout = null;
+ sVertexAttribEnables.Clear();
+ ActiveContext = context;
+ }
+
+ public void MakeOffscreenContextCurrent()
+ {
+ OffscreenContext.MakeContextCurrent();
+ ContextChangeCallback(OffscreenContext);
+ }
+ }
+}
diff --git a/src/BizHawk.Bizware.Graphics/OpenGL/OpenGLControl.cs b/src/BizHawk.Bizware.Graphics/OpenGL/OpenGLControl.cs
new file mode 100644
index 00000000000..f04cc6c3251
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/OpenGL/OpenGLControl.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Windows.Forms;
+
+using BizHawk.Bizware.BizwareGL;
+using BizHawk.Common;
+
+namespace BizHawk.Bizware.Graphics
+{
+ internal class OpenGLControl : Control, IGraphicsControl
+ {
+ private readonly int _majorVersion;
+ private readonly int _minorVersion;
+ private readonly bool _forwardCompatible;
+ private readonly Action _contextChangeCallback;
+
+ public SDL2OpenGLContext Context { get; private set; }
+
+ public RenderTargetWrapper RenderTargetWrapper
+ {
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
+ }
+
+ public OpenGLControl(int majorVersion, int minorVersion, bool forwardCompatible, Action contextChangeCallback)
+ {
+ _majorVersion = majorVersion;
+ _minorVersion = minorVersion;
+ _forwardCompatible = forwardCompatible;
+ _contextChangeCallback = contextChangeCallback;
+
+ // according to OpenTK, these are the styles we want to set
+ SetStyle(ControlStyles.Opaque, true);
+ SetStyle(ControlStyles.UserPaint, true);
+ SetStyle(ControlStyles.AllPaintingInWmPaint, true);
+ SetStyle(ControlStyles.UserMouse, true);
+ DoubleBuffered = false;
+ }
+
+ protected override CreateParams CreateParams
+ {
+ get
+ {
+ const int CS_VREDRAW = 0x1;
+ const int CS_HREDRAW = 0x2;
+ const int CS_OWNDC = 0x20;
+
+ var cp = base.CreateParams;
+ if (!OSTailoredCode.IsUnixHost)
+ {
+ // According to OpenTK, this is necessary for OpenGL on windows
+ cp.ClassStyle |= CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
+ }
+
+ return cp;
+ }
+ }
+
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+ Context = new(Handle, _majorVersion, _minorVersion, _forwardCompatible);
+
+ _contextChangeCallback(Context);
+ }
+
+ protected override void OnHandleDestroyed(EventArgs e)
+ {
+ base.OnHandleDestroyed(e);
+
+ if (Context.IsCurrent)
+ {
+ _contextChangeCallback(null);
+ }
+
+ Context.Dispose();
+ Context = null;
+ }
+
+ private void MakeContextCurrent()
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException(nameof(OpenGLControl));
+ }
+
+ if (Context is null)
+ {
+ CreateControl();
+ }
+ else
+ {
+ if (!Context.IsCurrent)
+ {
+ Context.MakeContextCurrent();
+ _contextChangeCallback(Context);
+ }
+ }
+ }
+
+ public void SetVsync(bool state)
+ {
+ MakeContextCurrent();
+ Context.SetVsync(state);
+ }
+
+ public void Begin()
+ {
+ MakeContextCurrent();
+ }
+
+ public void End()
+ {
+ _contextChangeCallback(null);
+ }
+
+ public void SwapBuffers()
+ {
+ MakeContextCurrent();
+ Context.SwapBuffers();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BizHawk.Bizware.Graphics/OpenGL/SDL2OpenGLContext.cs b/src/BizHawk.Bizware.Graphics/OpenGL/SDL2OpenGLContext.cs
new file mode 100644
index 00000000000..6b882c72dfd
--- /dev/null
+++ b/src/BizHawk.Bizware.Graphics/OpenGL/SDL2OpenGLContext.cs
@@ -0,0 +1,169 @@
+using System;
+
+using Silk.NET.OpenGL.Legacy;
+
+using static SDL2.SDL;
+
+namespace BizHawk.Bizware.Graphics
+{
+ ///
+ /// Wraps an SDL2 OpenGL context
+ ///
+ internal class SDL2OpenGLContext : IDisposable
+ {
+ static SDL2OpenGLContext()
+ {
+ // init SDL video
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ throw new($"Could not init SDL video! SDL Error: {SDL_GetError()}");
+ }
+
+ // load the default OpenGL library
+ if (SDL_GL_LoadLibrary(null) != 0)
+ {
+ throw new($"Could not load default OpenGL library! SDL Error: {SDL_GetError()}");
+ }
+
+ // set some sensible defaults
+ SDL_GL_ResetAttributes();
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8) != 0 ||
+ SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8) != 0 ||
+ SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8) != 0 ||
+ SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 0) != 0 ||
+ SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1) != 0)
+ {
+ throw new($"Could not set GL attributes! SDL Error: {SDL_GetError()}");
+ }
+
+ // we will be turning a foreign window into an SDL window
+ // we need this so it knows that it is capable of using OpenGL functions
+ SDL_SetHint(SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL, "1");
+ }
+
+ private IntPtr _sdlWindow;
+ private IntPtr _glContext;
+
+ public GL GL { get; private set; }
+
+ private void CreateContext(int majorVersion, int minorVersion, bool forwardCompatible)
+ {
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion) != 0)
+ {
+ throw new($"Could not set GL Major Version! SDL Error: {SDL_GetError()}");
+ }
+
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, minorVersion) != 0)
+ {
+ throw new($"Could not set GL Minor Version! SDL Error: {SDL_GetError()}");
+ }
+
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, forwardCompatible
+ ? (int)SDL_GLcontext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG : 0) != 0)
+ {
+ throw new($"Could not set GL Context Flags! SDL Error: {SDL_GetError()}");
+ }
+
+ // if we're requesting OpenGL 3.3+, get the core profile
+ // profiles don't exist otherwise
+ var profile = majorVersion * 10 + minorVersion >= 33
+ ? SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE
+ : 0;
+
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, profile) != 0)
+ {
+ throw new($"Could not set GL profile! SDL Error: {SDL_GetError()}");
+ }
+
+ _glContext = SDL_GL_CreateContext(_sdlWindow);
+ if (_glContext == IntPtr.Zero)
+ {
+ throw new($"Could not create GL Context! SDL Error: {SDL_GetError()}");
+ }
+
+ // get GL functions
+ // these are specific towards a context, and so these are owned by the context here
+ GL = GL.GetApi(SDL_GL_GetProcAddress);
+ }
+
+ public SDL2OpenGLContext(IntPtr nativeWindowhandle, int majorVersion, int minorVersion, bool forwardCompatible)
+ {
+ _sdlWindow = SDL_CreateWindowFrom(nativeWindowhandle);
+ if (_sdlWindow == IntPtr.Zero)
+ {
+ throw new($"Could not create SDL Window! SDL Error: {SDL_GetError()}");
+ }
+
+ // Controls are not shared, they are the sharees
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0) != 0)
+ {
+ throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}");
+ }
+
+ CreateContext(majorVersion, minorVersion, forwardCompatible);
+ }
+
+ public SDL2OpenGLContext(int majorVersion, int minorVersion, bool forwardCompatible)
+ {
+ _sdlWindow = SDL_CreateWindow(null, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1,
+ SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
+ if (_sdlWindow == IntPtr.Zero)
+ {
+ throw new($"Could not create SDL Window! SDL Error: {SDL_GetError()}");
+ }
+
+ // offscreen contexts are shared (as we want to send texture from it over to our control's context)
+ // make sure to set the current graphics' control context before creating this context
+ // (if no context is set, i.e. first IGL, then this won't do anything)
+ if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) != 0)
+ {
+ throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}");
+ }
+
+ CreateContext(majorVersion, minorVersion, forwardCompatible);
+ }
+
+ public void Dispose()
+ {
+ if (_glContext != IntPtr.Zero)
+ {
+ SDL_GL_DeleteContext(_glContext);
+ _glContext = IntPtr.Zero;
+ }
+
+ if (_sdlWindow != IntPtr.Zero)
+ {
+ SDL_DestroyWindow(_sdlWindow);
+ _sdlWindow = IntPtr.Zero;
+ }
+ }
+
+ public bool IsCurrent => SDL_GL_GetCurrentContext() == _glContext;
+
+ public void MakeContextCurrent()
+ {
+ // no-op if already current
+ _ = SDL_GL_MakeCurrent(_sdlWindow, _glContext);
+ }
+
+ public void SetVsync(bool state)
+ {
+ if (!IsCurrent)
+ {
+ throw new InvalidOperationException("Tried to set Vsync on non-active context");
+ }
+
+ _ = SDL_GL_SetSwapInterval(state ? 1 : 0);
+ }
+
+ public void SwapBuffers()
+ {
+ if (!IsCurrent)
+ {
+ throw new InvalidOperationException("Tried to swap buffers on non-active context");
+ }
+
+ SDL_GL_SwapWindow(_sdlWindow);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BizHawk.Bizware.OpenTK3/RenderStates.cs b/src/BizHawk.Bizware.Graphics/RenderStates.cs
similarity index 93%
rename from src/BizHawk.Bizware.OpenTK3/RenderStates.cs
rename to src/BizHawk.Bizware.Graphics/RenderStates.cs
index b6280889b0c..b3168263b7a 100644
--- a/src/BizHawk.Bizware.OpenTK3/RenderStates.cs
+++ b/src/BizHawk.Bizware.Graphics/RenderStates.cs
@@ -1,6 +1,6 @@
using BizHawk.Bizware.BizwareGL;
-namespace BizHawk.Bizware.OpenTK3
+namespace BizHawk.Bizware.Graphics
{
///
/// An IBlendState token that just caches all the args needed to create a blend state
diff --git a/src/BizHawk.Bizware.OpenTK3/BizHawk.Bizware.OpenTK3.csproj b/src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj
similarity index 54%
rename from src/BizHawk.Bizware.OpenTK3/BizHawk.Bizware.OpenTK3.csproj
rename to src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj
index 8f173da2d66..47dc86b1443 100644
--- a/src/BizHawk.Bizware.OpenTK3/BizHawk.Bizware.OpenTK3.csproj
+++ b/src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj
@@ -1,6 +1,6 @@
- net48
+ netstandard2.0
@@ -8,9 +8,9 @@
disable
-
-
-
+
+
+
diff --git a/src/BizHawk.Bizware.DirectX/GamePad.cs b/src/BizHawk.Bizware.Input/DirectX/DGamepad.cs
similarity index 64%
rename from src/BizHawk.Bizware.DirectX/GamePad.cs
rename to src/BizHawk.Bizware.Input/DirectX/DGamepad.cs
index 18824c7f44a..29d1d2d21a6 100644
--- a/src/BizHawk.Bizware.DirectX/GamePad.cs
+++ b/src/BizHawk.Bizware.Input/DirectX/DGamepad.cs
@@ -1,18 +1,20 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Reflection;
using BizHawk.Common;
-using SlimDX;
-using SlimDX.DirectInput;
+using Vortice.DirectInput;
-namespace BizHawk.Bizware.DirectX
+namespace BizHawk.Bizware.Input
{
- internal sealed class GamePad
+ internal sealed class DGamepad
{
- private static readonly object SyncObj = new object();
- private static readonly List Devices = new List();
- private static DirectInput _directInput;
+ private static readonly object SyncObj = new();
+ private static readonly List Devices = new();
+ private static IDirectInput8 _directInput;
public static void Initialize(IntPtr mainFormHandle)
{
@@ -20,33 +22,51 @@ public static void Initialize(IntPtr mainFormHandle)
{
Cleanup();
- _directInput = new DirectInput();
+ _directInput = DInput.DirectInput8Create();
- foreach (DeviceInstance device in _directInput.GetDevices(DeviceClass.GameController, DeviceEnumerationFlags.AttachedOnly))
+ foreach (var device in _directInput.GetDevices(DeviceClass.GameControl, DeviceEnumerationFlags.AttachedOnly))
{
Console.WriteLine("joy device: {0} `{1}`", device.InstanceGuid, device.ProductName);
if (device.ProductName.Contains("XBOX 360") || device.ProductName.Contains("Xbox One") || device.ProductName.Contains("XINPUT"))
continue; // Don't input XBOX 360 controllers into here; we'll process them via XInput (there are limitations in some trigger axes when xbox pads go over xinput)
- var joystick = new Joystick(_directInput, device.InstanceGuid);
- joystick.SetCooperativeLevel(mainFormHandle, CooperativeLevel.Background | CooperativeLevel.Nonexclusive);
- foreach (DeviceObjectInstance deviceObject in joystick.GetObjects())
+ var joystick = _directInput.CreateDevice(device.InstanceGuid);
+ joystick.SetCooperativeLevel(mainFormHandle, CooperativeLevel.Background | CooperativeLevel.NonExclusive);
+ joystick.SetDataFormat();
+#if false
+ // GetObjects returns localized names, so this doesn't actually work
+ foreach (var deviceObject in joystick.GetObjects(DeviceObjectTypeFlags.Axis))
{
- if ((deviceObject.ObjectType & ObjectDeviceType.Axis) != 0)
- {
- joystick.GetObjectPropertiesById((int)deviceObject.ObjectType).SetRange(-1000, 1000);
- }
+ joystick.GetObjectPropertiesByName(deviceObject.Name).Range = new(-1000, 1000);
}
+#elif false
+ // when https://github.com/amerkoleci/Vortice.Windows/issues/393 is fixed, this is what we should do
+ // cpp: this is fixed now, but updating to 3.x means bumping up to .net6+
+ foreach (var deviceObject in joystick.GetObjects(DeviceObjectTypeFlags.Axis))
+ {
+ joystick.GetObjectPropertiesById(deviceObject.ObjectId).Range = new(-1000, 1000);
+ }
+#else
+ // hack due to the above problems
+ var dict = (Dictionary)typeof(IDirectInputDevice8)
+ .GetField("_mapNameToObjectFormat", BindingFlags.Instance | BindingFlags.NonPublic)!
+ .GetValue(joystick);
+
+ foreach (var deviceObject in joystick.GetObjects(DeviceObjectTypeFlags.Axis))
+ {
+ joystick.GetObjectPropertiesByName(dict.Values.First(odf => odf.Guid == deviceObject.ObjectType).Name!).Range = new(-1000, 1000);
+ }
+#endif
joystick.Acquire();
- GamePad p = new GamePad(joystick, Devices.Count);
+ var p = new DGamepad(joystick, Devices.Count);
Devices.Add(p);
}
}
}
- public static IEnumerable EnumerateDevices()
+ public static IEnumerable EnumerateDevices()
{
lock (SyncObj)
{
@@ -89,10 +109,10 @@ public static void Cleanup()
// ********************************** Instance Members **********************************
- private readonly Joystick _joystick;
- private JoystickState _state = new JoystickState();
+ private readonly IDirectInputDevice8 _joystick;
+ private JoystickState _state = new();
- private GamePad(Joystick joystick, int index)
+ private DGamepad(IDirectInputDevice8 joystick, int index)
{
_joystick = joystick;
PlayerNumber = index + 1;
@@ -105,7 +125,7 @@ public void Update()
{
try
{
- if (_joystick.Acquire().IsFailure)
+ if (_joystick.Acquire().Failure)
return;
}
catch
@@ -114,24 +134,26 @@ public void Update()
}
if (_joystick.Poll()
- .IsFailure)
+ .Failure)
{
return;
}
- _state = _joystick.GetCurrentState();
- if (Result.Last.IsFailure)
+ try
+ {
+ _joystick.GetCurrentJoystickState(ref _state);
+ }
+ catch (SharpGen.Runtime.SharpGenException)
+ {
// do something?
- return;
+ }
}
+ private static readonly ImmutableArray _axesPropertyInfos = typeof(JoystickState).GetProperties().Where(pi => pi.PropertyType == typeof(int)).ToImmutableArray();
+
public IEnumerable<(string AxisID, float Value)> GetAxes()
{
- var pis = typeof(JoystickState).GetProperties();
- foreach (var pi in pis)
- {
- yield return (pi.Name, 10.0f * (float)(int)pi.GetValue(_state, null));
- }
+ return _axesPropertyInfos.Select(pi => (pi.Name, 10.0f * (int)pi.GetValue(_state)));
}
/// FOR DEBUGGING ONLY
@@ -156,8 +178,8 @@ public bool Pressed(int index)
public int NumButtons { get; private set; }
- private readonly List _names = new List();
- private readonly List> _actions = new List>();
+ private readonly List _names = new();
+ private readonly List> _actions = new();
private void AddItem(string name, Func callback)
{
@@ -226,22 +248,22 @@ private void InitializeCallbacks()
// i don't know what the "Slider"s do, so they're omitted for the moment
- for (int i = 0; i < _state.GetButtons().Length; i++)
+ for (var i = 0; i < _state.Buttons.Length; i++)
{
- int j = i;
- AddItem($"B{i + 1}", () => _state.IsPressed(j));
+ var j = i;
+ AddItem($"B{i + 1}", () => _state.Buttons[j]);
}
- for (int i = 0; i < _state.GetPointOfViewControllers().Length; i++)
+ for (var i = 0; i < _state.PointOfViewControllers.Length; i++)
{
- int j = i;
+ var j = i;
AddItem($"POV{i + 1}U", () => {
- var t = _state.GetPointOfViewControllers()[j];
+ var t = _state.PointOfViewControllers[j];
return 0.RangeTo(4500).Contains(t) || 31500.RangeToExclusive(36000).Contains(t);
});
- AddItem($"POV{i + 1}D", () => 13500.RangeTo(22500).Contains(_state.GetPointOfViewControllers()[j]));
- AddItem($"POV{i + 1}L", () => 22500.RangeTo(31500).Contains(_state.GetPointOfViewControllers()[j]));
- AddItem($"POV{i + 1}R", () => 4500.RangeTo(13500).Contains(_state.GetPointOfViewControllers()[j]));
+ AddItem($"POV{i + 1}D", () => 13500.RangeTo(22500).Contains(_state.PointOfViewControllers[j]));
+ AddItem($"POV{i + 1}L", () => 22500.RangeTo(31500).Contains(_state.PointOfViewControllers[j]));
+ AddItem($"POV{i + 1}R", () => 4500.RangeTo(13500).Contains(_state.PointOfViewControllers[j]));
}
}
@@ -252,18 +274,17 @@ public void SetVibration(int left, int right)
// I should just look for C++ examples instead of trying to look for SlimDX examples
var parameters = new EffectParameters
- {
- Duration = 0x2710,
- Gain = 0x2710,
- SamplePeriod = 0,
- TriggerButton = 0,
- TriggerRepeatInterval = 0x2710,
- Flags = EffectFlags.None
- };
+ {
+ Duration = 0x2710,
+ Gain = 0x2710,
+ SamplePeriod = 0,
+ TriggerButton = 0,
+ TriggerRepeatInterval = 0x2710,
+ Flags = EffectFlags.None
+ };
parameters.GetAxes(out var temp1, out var temp2);
parameters.SetAxes(temp1, temp2);
- var effect = new Effect(_joystick, EffectGuid.ConstantForce);
- effect.SetParameters(parameters);
+ var effect = _joystick.CreateEffect(EffectGuid.ConstantForce, parameters);
effect.Start(1);
}
}
diff --git a/src/BizHawk.Bizware.Input/DirectX/DKeyInput.cs b/src/BizHawk.Bizware.Input/DirectX/DKeyInput.cs
new file mode 100644
index 00000000000..c88a1e8a87c
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/DirectX/DKeyInput.cs
@@ -0,0 +1,352 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using BizHawk.Client.Common;
+using BizHawk.Common.CollectionExtensions;
+
+using Vortice.DirectInput;
+
+using static BizHawk.Common.Win32Imports;
+
+using DInputKey = Vortice.DirectInput.Key;
+
+namespace BizHawk.Bizware.Input
+{
+ internal static class DKeyInput
+ {
+ private static IDirectInput8? _directInput;
+
+ private static IDirectInputDevice8? _keyboard;
+
+ private static readonly object _lockObj = new();
+
+ public static void Initialize(IntPtr mainFormHandle)
+ {
+ lock (_lockObj)
+ {
+ Cleanup();
+
+ _directInput = DInput.DirectInput8Create();
+
+ _keyboard = _directInput.CreateDevice(PredefinedDevice.SysKeyboard);
+ _keyboard.SetCooperativeLevel(mainFormHandle, CooperativeLevel.Background | CooperativeLevel.NonExclusive);
+ _keyboard.SetDataFormat();
+ _keyboard.Properties.BufferSize = 8;
+ }
+ }
+
+ public static void Cleanup()
+ {
+ lock (_lockObj)
+ {
+ _keyboard?.Dispose();
+ _keyboard = null;
+ _directInput?.Dispose();
+ _directInput = null;
+ }
+ }
+
+ public static IEnumerable Update(Config config)
+ {
+ DistinctKey Mapped(DInputKey k) => KeyEnumMap[config.HandleAlternateKeyboardLayouts ? MapToRealKeyViaScanCode(k) : k];
+
+ lock (_lockObj)
+ {
+ if (_keyboard == null || _keyboard.Acquire().Failure || _keyboard.Poll().Failure) return Enumerable.Empty();
+
+ var eventList = new List();
+ while (true)
+ {
+ try
+ {
+ var events = _keyboard.GetBufferedKeyboardData();
+ if (events.Length == 0) return eventList;
+ eventList.AddRange(events.Select(e => new KeyEvent(Mapped(e.Key), e.IsPressed)));
+ }
+ catch (SharpGen.Runtime.SharpGenException)
+ {
+ return eventList;
+ }
+ }
+ }
+ }
+
+ private static DInputKey MapToRealKeyViaScanCode(DInputKey key)
+ {
+ const uint MAPVK_VSC_TO_VK_EX = 0x03;
+ // DInputKey is a scancode as is
+ var virtualKey = MapVirtualKey((uint) key, MAPVK_VSC_TO_VK_EX);
+ return VKeyToDKeyMap.GetValueOrDefault(virtualKey, DInputKey.Unknown);
+ }
+
+ private static readonly IReadOnlyDictionary KeyEnumMap = new Dictionary
+ {
+ [DInputKey.D0] = DistinctKey.D0,
+ [DInputKey.D1] = DistinctKey.D1,
+ [DInputKey.D2] = DistinctKey.D2,
+ [DInputKey.D3] = DistinctKey.D3,
+ [DInputKey.D4] = DistinctKey.D4,
+ [DInputKey.D5] = DistinctKey.D5,
+ [DInputKey.D6] = DistinctKey.D6,
+ [DInputKey.D7] = DistinctKey.D7,
+ [DInputKey.D8] = DistinctKey.D8,
+ [DInputKey.D9] = DistinctKey.D9,
+ [DInputKey.A] = DistinctKey.A,
+ [DInputKey.B] = DistinctKey.B,
+ [DInputKey.C] = DistinctKey.C,
+ [DInputKey.D] = DistinctKey.D,
+ [DInputKey.E] = DistinctKey.E,
+ [DInputKey.F] = DistinctKey.F,
+ [DInputKey.G] = DistinctKey.G,
+ [DInputKey.H] = DistinctKey.H,
+ [DInputKey.I] = DistinctKey.I,
+ [DInputKey.J] = DistinctKey.J,
+ [DInputKey.K] = DistinctKey.K,
+ [DInputKey.L] = DistinctKey.L,
+ [DInputKey.M] = DistinctKey.M,
+ [DInputKey.N] = DistinctKey.N,
+ [DInputKey.O] = DistinctKey.O,
+ [DInputKey.P] = DistinctKey.P,
+ [DInputKey.Q] = DistinctKey.Q,
+ [DInputKey.R] = DistinctKey.R,
+ [DInputKey.S] = DistinctKey.S,
+ [DInputKey.T] = DistinctKey.T,
+ [DInputKey.U] = DistinctKey.U,
+ [DInputKey.V] = DistinctKey.V,
+ [DInputKey.W] = DistinctKey.W,
+ [DInputKey.X] = DistinctKey.X,
+ [DInputKey.Y] = DistinctKey.Y,
+ [DInputKey.Z] = DistinctKey.Z,
+ [DInputKey.AbntC1] = DistinctKey.AbntC1,
+ [DInputKey.AbntC2] = DistinctKey.AbntC2,
+ [DInputKey.Apostrophe] = DistinctKey.OemQuotes,
+ [DInputKey.Applications] = DistinctKey.Apps,
+ [DInputKey.AT] = DistinctKey.Unknown,
+ [DInputKey.AX] = DistinctKey.Unknown,
+ [DInputKey.Back] = DistinctKey.Back,
+ [DInputKey.Backslash] = DistinctKey.OemPipe,
+ [DInputKey.Calculator] = DistinctKey.Unknown,
+ [DInputKey.CapsLock] = DistinctKey.CapsLock,
+ [DInputKey.Colon] = DistinctKey.Unknown,
+ [DInputKey.Comma] = DistinctKey.OemComma,
+ [DInputKey.Convert] = DistinctKey.ImeConvert,
+ [DInputKey.Delete] = DistinctKey.Delete,
+ [DInputKey.Down] = DistinctKey.Down,
+ [DInputKey.End] = DistinctKey.End,
+ [DInputKey.Equals] = DistinctKey.OemPlus,
+ [DInputKey.Escape] = DistinctKey.Escape,
+ [DInputKey.F1] = DistinctKey.F1,
+ [DInputKey.F2] = DistinctKey.F2,
+ [DInputKey.F3] = DistinctKey.F3,
+ [DInputKey.F4] = DistinctKey.F4,
+ [DInputKey.F5] = DistinctKey.F5,
+ [DInputKey.F6] = DistinctKey.F6,
+ [DInputKey.F7] = DistinctKey.F7,
+ [DInputKey.F8] = DistinctKey.F8,
+ [DInputKey.F9] = DistinctKey.F9,
+ [DInputKey.F10] = DistinctKey.F10,
+ [DInputKey.F11] = DistinctKey.F11,
+ [DInputKey.F12] = DistinctKey.F12,
+ [DInputKey.F13] = DistinctKey.F13,
+ [DInputKey.F14] = DistinctKey.F14,
+ [DInputKey.F15] = DistinctKey.F15,
+ [DInputKey.Grave] = DistinctKey.OemTilde,
+ [DInputKey.Home] = DistinctKey.Home,
+ [DInputKey.Insert] = DistinctKey.Insert,
+ [DInputKey.Kana] = DistinctKey.KanaMode,
+ [DInputKey.Kanji] = DistinctKey.KanjiMode,
+ [DInputKey.LeftBracket] = DistinctKey.OemOpenBrackets,
+ [DInputKey.LeftControl] = DistinctKey.LeftCtrl,
+ [DInputKey.Left] = DistinctKey.Left,
+ [DInputKey.LeftAlt] = DistinctKey.LeftAlt,
+ [DInputKey.LeftShift] = DistinctKey.LeftShift,
+ [DInputKey.LeftWindowsKey] = DistinctKey.LWin,
+ [DInputKey.Mail] = DistinctKey.LaunchMail,
+ [DInputKey.MediaSelect] = DistinctKey.SelectMedia,
+ [DInputKey.MediaStop] = DistinctKey.MediaStop,
+ [DInputKey.Minus] = DistinctKey.OemMinus,
+ [DInputKey.Mute] = DistinctKey.VolumeMute,
+ [DInputKey.MyComputer] = DistinctKey.Unknown,
+ [DInputKey.NextTrack] = DistinctKey.MediaNextTrack,
+ [DInputKey.NoConvert] = DistinctKey.ImeNonConvert,
+ [DInputKey.NumberLock] = DistinctKey.NumLock,
+ [DInputKey.NumberPad0] = DistinctKey.NumPad0,
+ [DInputKey.NumberPad1] = DistinctKey.NumPad1,
+ [DInputKey.NumberPad2] = DistinctKey.NumPad2,
+ [DInputKey.NumberPad3] = DistinctKey.NumPad3,
+ [DInputKey.NumberPad4] = DistinctKey.NumPad4,
+ [DInputKey.NumberPad5] = DistinctKey.NumPad5,
+ [DInputKey.NumberPad6] = DistinctKey.NumPad6,
+ [DInputKey.NumberPad7] = DistinctKey.NumPad7,
+ [DInputKey.NumberPad8] = DistinctKey.NumPad8,
+ [DInputKey.NumberPad9] = DistinctKey.NumPad9,
+ [DInputKey.NumberPadComma] = DistinctKey.Separator,
+ [DInputKey.NumberPadEnter] = DistinctKey.NumPadEnter,
+ [DInputKey.NumberPadEquals] = DistinctKey.OemPlus,
+ [DInputKey.Subtract] = DistinctKey.Subtract,
+ [DInputKey.Decimal] = DistinctKey.Decimal,
+ [DInputKey.Add] = DistinctKey.Add,
+ [DInputKey.Divide] = DistinctKey.Divide,
+ [DInputKey.Multiply] = DistinctKey.Multiply,
+ [DInputKey.Oem102] = DistinctKey.OemBackslash,
+ [DInputKey.PageDown] = DistinctKey.PageDown,
+ [DInputKey.PageUp] = DistinctKey.PageUp,
+ [DInputKey.Pause] = DistinctKey.Pause,
+ [DInputKey.Period] = DistinctKey.OemPeriod,
+ [DInputKey.PlayPause] = DistinctKey.MediaPlayPause,
+ [DInputKey.Power] = DistinctKey.Unknown,
+ [DInputKey.PreviousTrack] = DistinctKey.MediaPreviousTrack,
+ [DInputKey.RightBracket] = DistinctKey.OemCloseBrackets,
+ [DInputKey.RightControl] = DistinctKey.RightCtrl,
+ [DInputKey.Return] = DistinctKey.Return,
+ [DInputKey.Right] = DistinctKey.Right,
+ [DInputKey.RightAlt] = DistinctKey.RightAlt,
+ [DInputKey.RightShift] = DistinctKey.RightShift,
+ [DInputKey.RightWindowsKey] = DistinctKey.RWin,
+ [DInputKey.ScrollLock] = DistinctKey.Scroll,
+ [DInputKey.Semicolon] = DistinctKey.OemSemicolon,
+ [DInputKey.Slash] = DistinctKey.OemQuestion,
+ [DInputKey.Sleep] = DistinctKey.Sleep,
+ [DInputKey.Space] = DistinctKey.Space,
+ [DInputKey.Stop] = DistinctKey.MediaStop,
+ [DInputKey.PrintScreen] = DistinctKey.PrintScreen,
+ [DInputKey.Tab] = DistinctKey.Tab,
+ [DInputKey.Underline] = DistinctKey.Unknown,
+ [DInputKey.Unlabeled] = DistinctKey.Unknown,
+ [DInputKey.Up] = DistinctKey.Up,
+ [DInputKey.VolumeDown] = DistinctKey.VolumeDown,
+ [DInputKey.VolumeUp] = DistinctKey.VolumeUp,
+ [DInputKey.Wake] = DistinctKey.Sleep,
+ [DInputKey.WebBack] = DistinctKey.BrowserBack,
+ [DInputKey.WebFavorites] = DistinctKey.BrowserFavorites,
+ [DInputKey.WebForward] = DistinctKey.BrowserForward,
+ [DInputKey.WebHome] = DistinctKey.BrowserHome,
+ [DInputKey.WebRefresh] = DistinctKey.BrowserRefresh,
+ [DInputKey.WebSearch] = DistinctKey.BrowserSearch,
+ [DInputKey.WebStop] = DistinctKey.BrowserStop,
+ [DInputKey.Yen] = DistinctKey.Unknown,
+ [DInputKey.Unknown] = DistinctKey.Unknown
+ };
+
+ private static readonly IReadOnlyDictionary VKeyToDKeyMap = new Dictionary
+ {
+ [0x30] = DInputKey.D0,
+ [0x31] = DInputKey.D1,
+ [0x32] = DInputKey.D2,
+ [0x33] = DInputKey.D3,
+ [0x34] = DInputKey.D4,
+ [0x35] = DInputKey.D5,
+ [0x36] = DInputKey.D6,
+ [0x37] = DInputKey.D7,
+ [0x38] = DInputKey.D8,
+ [0x39] = DInputKey.D9,
+ [0x41] = DInputKey.A,
+ [0x42] = DInputKey.B,
+ [0x43] = DInputKey.C,
+ [0x44] = DInputKey.D,
+ [0x45] = DInputKey.E,
+ [0x46] = DInputKey.F,
+ [0x47] = DInputKey.G,
+ [0x48] = DInputKey.H,
+ [0x49] = DInputKey.I,
+ [0x4A] = DInputKey.J,
+ [0x4B] = DInputKey.K,
+ [0x4C] = DInputKey.L,
+ [0x4D] = DInputKey.M,
+ [0x4E] = DInputKey.N,
+ [0x4F] = DInputKey.O,
+ [0x50] = DInputKey.P,
+ [0x51] = DInputKey.Q,
+ [0x52] = DInputKey.R,
+ [0x53] = DInputKey.S,
+ [0x54] = DInputKey.T,
+ [0x55] = DInputKey.U,
+ [0x56] = DInputKey.V,
+ [0x57] = DInputKey.W,
+ [0x58] = DInputKey.X,
+ [0x59] = DInputKey.Y,
+ [0x5A] = DInputKey.Z,
+ [0xDE] = DInputKey.Apostrophe,
+ [0x5D] = DInputKey.Applications,
+ [0x08] = DInputKey.Back,
+ [0xDC] = DInputKey.Backslash,
+ [0x14] = DInputKey.CapsLock,
+ [0xBC] = DInputKey.Comma,
+ [0x1C] = DInputKey.Convert,
+ [0x2E] = DInputKey.Delete,
+ [0x28] = DInputKey.Down,
+ [0x23] = DInputKey.End,
+ [0xBB] = DInputKey.Equals,
+ [0x1B] = DInputKey.Escape,
+ [0x70] = DInputKey.F1,
+ [0x71] = DInputKey.F2,
+ [0x72] = DInputKey.F3,
+ [0x73] = DInputKey.F4,
+ [0x74] = DInputKey.F5,
+ [0x75] = DInputKey.F6,
+ [0x76] = DInputKey.F7,
+ [0x77] = DInputKey.F8,
+ [0x78] = DInputKey.F9,
+ [0x79] = DInputKey.F10,
+ [0x7A] = DInputKey.F11,
+ [0x7B] = DInputKey.F12,
+ [0x7C] = DInputKey.F13,
+ [0x7D] = DInputKey.F14,
+ [0x7E] = DInputKey.F15,
+ [0xC0] = DInputKey.Grave,
+ [0x24] = DInputKey.Home,
+ [0x2D] = DInputKey.Insert,
+ [0xDB] = DInputKey.LeftBracket,
+ [0xA2] = DInputKey.LeftControl,
+ [0x25] = DInputKey.Left,
+ [0xA4] = DInputKey.LeftAlt,
+ [0xA0] = DInputKey.LeftShift,
+ [0x5B] = DInputKey.LeftWindowsKey,
+ [0xB4] = DInputKey.Mail,
+ [0xB5] = DInputKey.MediaSelect,
+ [0xB2] = DInputKey.MediaStop,
+ [0xBD] = DInputKey.Minus,
+ [0xAD] = DInputKey.Mute,
+ [0xB0] = DInputKey.NextTrack,
+ [0x90] = DInputKey.NumberLock,
+ [0x6D] = DInputKey.Subtract,
+ [0x6B] = DInputKey.Add,
+ [0x6F] = DInputKey.Divide,
+ [0x6A] = DInputKey.Multiply,
+ [0xE2] = DInputKey.Oem102,
+ [0x22] = DInputKey.PageDown,
+ [0x21] = DInputKey.PageUp,
+ [0x13] = DInputKey.Pause,
+ [0xBE] = DInputKey.Period,
+ [0xB3] = DInputKey.PlayPause,
+ [0xB1] = DInputKey.PreviousTrack,
+ [0xDD] = DInputKey.RightBracket,
+ [0xA3] = DInputKey.RightControl,
+ [0x0D] = DInputKey.Return,
+ [0x27] = DInputKey.Right,
+ [0xA5] = DInputKey.RightAlt,
+ [0xA1] = DInputKey.RightShift,
+ [0x5C] = DInputKey.RightWindowsKey,
+ [0x91] = DInputKey.ScrollLock,
+ [0xBA] = DInputKey.Semicolon,
+ [0xBF] = DInputKey.Slash,
+ [0x5F] = DInputKey.Sleep,
+ [0x20] = DInputKey.Space,
+ [0x2C] = DInputKey.PrintScreen,
+ [0x09] = DInputKey.Tab,
+ [0x26] = DInputKey.Up,
+ [0xAE] = DInputKey.VolumeDown,
+ [0xAF] = DInputKey.VolumeUp,
+ [0xA6] = DInputKey.WebBack,
+ [0xAB] = DInputKey.WebFavorites,
+ [0xA7] = DInputKey.WebForward,
+ [0xAC] = DInputKey.WebHome,
+ [0xA8] = DInputKey.WebRefresh,
+ [0xAA] = DInputKey.WebSearch,
+ [0xA9] = DInputKey.WebStop,
+ };
+ }
+}
diff --git a/src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs b/src/BizHawk.Bizware.Input/DirectX/DirectInputAdapter.cs
similarity index 68%
rename from src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs
rename to src/BizHawk.Bizware.Input/DirectX/DirectInputAdapter.cs
index b99a02c6b82..3a836f34a68 100644
--- a/src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs
+++ b/src/BizHawk.Bizware.Input/DirectX/DirectInputAdapter.cs
@@ -5,8 +5,9 @@
using System.Linq;
using BizHawk.Client.Common;
+using BizHawk.Common.CollectionExtensions;
-namespace BizHawk.Bizware.DirectX
+namespace BizHawk.Bizware.Input
{
public sealed class DirectInputAdapter : IHostInputAdapter
{
@@ -16,56 +17,56 @@ public sealed class DirectInputAdapter : IHostInputAdapter
private Config? _config;
- public string Desc { get; } = "DirectInput+XInput";
+ public string Desc => "DirectInput+XInput";
public void DeInitAll()
{
- KeyInput.Cleanup();
- GamePad.Cleanup();
+ DKeyInput.Cleanup();
+ DGamepad.Cleanup();
}
public void FirstInitAll(IntPtr mainFormHandle)
{
- KeyInput.Initialize(mainFormHandle);
+ DKeyInput.Initialize(mainFormHandle);
IPCKeyInput.Initialize();
ReInitGamepads(mainFormHandle);
}
public IReadOnlyDictionary> GetHapticsChannels()
- => GamePad360.EnumerateDevices().ToDictionary(pad => pad.InputNamePrefix, _ => XINPUT_HAPTIC_CHANNEL_NAMES);
+ => XGamepad.EnumerateDevices().ToDictionary(pad => pad.InputNamePrefix, _ => XINPUT_HAPTIC_CHANNEL_NAMES);
public void ReInitGamepads(IntPtr mainFormHandle)
{
- GamePad.Initialize(mainFormHandle);
- GamePad360.Initialize();
+ DGamepad.Initialize(mainFormHandle);
+ XGamepad.Initialize();
}
public void PreprocessHostGamepads()
{
- GamePad.UpdateAll();
- GamePad360.UpdateAll();
+ DGamepad.UpdateAll();
+ XGamepad.UpdateAll();
}
public void ProcessHostGamepads(Action handleButton, Action handleAxis)
{
- foreach (var pad in GamePad360.EnumerateDevices())
+ foreach (var pad in XGamepad.EnumerateDevices())
{
if (!pad.IsConnected)
continue;
for (int b = 0, n = pad.NumButtons; b < n; b++) handleButton(pad.InputNamePrefix + pad.ButtonName(b), pad.Pressed(b), ClientInputFocus.Pad);
foreach (var (axisName, f) in pad.GetAxes()) handleAxis(pad.InputNamePrefix + axisName, (int) f);
- _ = _lastHapticsSnapshot.TryGetValue(pad.InputNamePrefix + "Left", out var leftStrength);
- _ = _lastHapticsSnapshot.TryGetValue(pad.InputNamePrefix + "Right", out var rightStrength);
+ var leftStrength = _lastHapticsSnapshot.GetValueOrDefault(pad.InputNamePrefix + "Left");
+ var rightStrength = _lastHapticsSnapshot.GetValueOrDefault(pad.InputNamePrefix + "Right");
pad.SetVibration(leftStrength, rightStrength); // values will be 0 if not found
}
- foreach (var pad in GamePad.EnumerateDevices())
+ foreach (var pad in DGamepad.EnumerateDevices())
{
for (int b = 0, n = pad.NumButtons; b < n; b++) handleButton(pad.InputNamePrefix + pad.ButtonName(b), pad.Pressed(b), ClientInputFocus.Pad);
foreach (var (axisName, f) in pad.GetAxes()) handleAxis(pad.InputNamePrefix + axisName, (int) f);
}
}
- public IEnumerable ProcessHostKeyboards() => KeyInput.Update(_config ?? throw new Exception(nameof(ProcessHostKeyboards) + " called before the global config was passed"))
+ public IEnumerable ProcessHostKeyboards() => DKeyInput.Update(_config ?? throw new(nameof(ProcessHostKeyboards) + " called before the global config was passed"))
.Concat(IPCKeyInput.Update());
public void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot)
diff --git a/src/BizHawk.Bizware.Input/DirectX/XGamepad.cs b/src/BizHawk.Bizware.Input/DirectX/XGamepad.cs
new file mode 100644
index 00000000000..c6910ed044d
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/DirectX/XGamepad.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+using BizHawk.Common;
+
+using Vortice.XInput;
+
+namespace BizHawk.Bizware.Input
+{
+ internal sealed class XGamepad
+ {
+ // ********************************** Static interface **********************************
+
+ private static readonly object SyncObj = new();
+ private static readonly List Devices = new();
+ private static readonly bool IsAvailable;
+
+ // Vortice has some support for the unofficial API, but it has some issues
+ // (e.g. the check for AllowUnofficialAPI is in static ctor (???), uses it regardless of it being available)
+ // We'll just get the proc ourselves and use it
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ private delegate uint XInputGetStateExProcDelegate(int dwUserIndex, out State state);
+
+ private static readonly XInputGetStateExProcDelegate XInputGetStateExProc;
+
+ static XGamepad()
+ {
+ try
+ {
+ // some users won't even have xinput installed. in order to avoid spurious exceptions and possible instability, check for the library first
+ IsAvailable = XInput.Version != XInputVersion.Invalid;
+ if (IsAvailable)
+ {
+ var llManager = OSTailoredCode.LinkedLibManager;
+ var libHandle = XInput.Version switch
+ {
+ XInputVersion.Version14 => llManager.LoadOrThrow("xinput1_4.dll"),
+ XInputVersion.Version13 => llManager.LoadOrThrow("xinput1_3.dll"),
+ _ => IntPtr.Zero // unofficial API isn't available for 9.1.0
+ };
+
+ if (libHandle != IntPtr.Zero)
+ {
+ var fptr = llManager.GetProcAddrOrZero(libHandle, "#100");
+ if (fptr != IntPtr.Zero)
+ {
+ XInputGetStateExProc =
+ Marshal.GetDelegateForFunctionPointer(fptr);
+ }
+
+ // nb: this doesn't actually free the library here, rather it will just decrement the reference count
+ llManager.FreeByPtr(libHandle);
+ }
+
+ // don't remove this code. it's important to catch errors on systems with broken xinput installs.
+
+ _ = XInputGetStateExProc?.Invoke(0, out _);
+ _ = XInput.GetState(0, out _);
+ }
+ }
+ catch
+ {
+ IsAvailable = false;
+ }
+ }
+
+ public static void Initialize()
+ {
+ lock (SyncObj)
+ {
+ Devices.Clear();
+
+ if (!IsAvailable)
+ return;
+
+ if (XInput.GetState(0, out _)) Devices.Add(new(0));
+ if (XInput.GetState(1, out _)) Devices.Add(new(1));
+ if (XInput.GetState(2, out _)) Devices.Add(new(2));
+ if (XInput.GetState(3, out _)) Devices.Add(new(3));
+ }
+ }
+
+ public static IEnumerable EnumerateDevices()
+ {
+ lock (SyncObj)
+ {
+ foreach (var device in Devices)
+ {
+ yield return device;
+ }
+ }
+ }
+
+ public static void UpdateAll()
+ {
+ lock (SyncObj)
+ {
+ foreach (var device in Devices)
+ {
+ device.Update();
+ }
+ }
+ }
+
+ // ********************************** Instance Members **********************************
+
+ private readonly int _index0;
+ private State _state;
+
+ public int PlayerNumber => _index0 + 1;
+ public bool IsConnected => XInput.GetState(_index0, out _);
+ public readonly string InputNamePrefix;
+
+ private XGamepad(int index0)
+ {
+ _index0 = index0;
+ InputNamePrefix = $"X{PlayerNumber} ";
+ InitializeButtons();
+ Update();
+ }
+
+ public void Update()
+ {
+ if (!IsConnected)
+ return;
+
+ _state = default;
+ if (XInputGetStateExProc is not null)
+ {
+ XInputGetStateExProc(_index0, out _state);
+ }
+ else
+ {
+ XInput.GetState(_index0, out _state);
+ }
+ }
+
+ public IEnumerable<(string AxisID, float Value)> GetAxes()
+ {
+ var g = _state.Gamepad;
+
+ //constant for adapting a +/- 32768 range to a +/-10000-based range
+ const float f = 32768 / 10000.0f;
+
+ //since our whole input framework really only understands whole axes, let's make the triggers look like an axis
+ var lTrig = g.LeftTrigger / 255.0f * 2 - 1;
+ var rTrig = g.RightTrigger / 255.0f * 2 - 1;
+ lTrig *= 10000;
+ rTrig *= 10000;
+
+ yield return ("LeftThumbX", g.LeftThumbX / f);
+ yield return ("LeftThumbY", g.LeftThumbY / f);
+ yield return ("RightThumbX", g.RightThumbX / f);
+ yield return ("RightThumbY", g.RightThumbY / f);
+ yield return ("LeftTrigger", lTrig);
+ yield return ("RightTrigger", rTrig);
+ }
+
+ public int NumButtons { get; private set; }
+
+ private readonly List _names = new();
+ private readonly List> _actions = new();
+
+ private void InitializeButtons()
+ {
+ const int dzp = 20000;
+ const int dzn = -20000;
+ const int dzt = 40;
+
+ AddItem("A", () => (_state.Gamepad.Buttons & GamepadButtons.A) != 0);
+ AddItem("B", () => (_state.Gamepad.Buttons & GamepadButtons.B) != 0);
+ AddItem("X", () => (_state.Gamepad.Buttons & GamepadButtons.X) != 0);
+ AddItem("Y", () => (_state.Gamepad.Buttons & GamepadButtons.Y) != 0);
+ AddItem("Guide", () => (_state.Gamepad.Buttons & GamepadButtons.Guide) != 0);
+
+ AddItem("Start", () => (_state.Gamepad.Buttons & GamepadButtons.Start) != 0);
+ AddItem("Back", () => (_state.Gamepad.Buttons & GamepadButtons.Back) != 0);
+ AddItem("LeftThumb", () => (_state.Gamepad.Buttons & GamepadButtons.LeftThumb) != 0);
+ AddItem("RightThumb", () => (_state.Gamepad.Buttons & GamepadButtons.RightThumb) != 0);
+ AddItem("LeftShoulder", () => (_state.Gamepad.Buttons & GamepadButtons.LeftShoulder) != 0);
+ AddItem("RightShoulder", () => (_state.Gamepad.Buttons & GamepadButtons.RightShoulder) != 0);
+
+ AddItem("DpadUp", () => (_state.Gamepad.Buttons & GamepadButtons.DPadUp) != 0);
+ AddItem("DpadDown", () => (_state.Gamepad.Buttons & GamepadButtons.DPadDown) != 0);
+ AddItem("DpadLeft", () => (_state.Gamepad.Buttons & GamepadButtons.DPadLeft) != 0);
+ AddItem("DpadRight", () => (_state.Gamepad.Buttons & GamepadButtons.DPadRight) != 0);
+
+ AddItem("LStickUp", () => _state.Gamepad.LeftThumbY >= dzp);
+ AddItem("LStickDown", () => _state.Gamepad.LeftThumbY <= dzn);
+ AddItem("LStickLeft", () => _state.Gamepad.LeftThumbX <= dzn);
+ AddItem("LStickRight", () => _state.Gamepad.LeftThumbX >= dzp);
+
+ AddItem("RStickUp", () => _state.Gamepad.RightThumbY >= dzp);
+ AddItem("RStickDown", () => _state.Gamepad.RightThumbY <= dzn);
+ AddItem("RStickLeft", () => _state.Gamepad.RightThumbX <= dzn);
+ AddItem("RStickRight", () => _state.Gamepad.RightThumbX >= dzp);
+
+ AddItem("LeftTrigger", () => _state.Gamepad.LeftTrigger > dzt);
+ AddItem("RightTrigger", () => _state.Gamepad.RightTrigger > dzt);
+ }
+
+ private void AddItem(string name, Func pressed)
+ {
+ _names.Add(name);
+ _actions.Add(pressed);
+ NumButtons++;
+ }
+
+ public string ButtonName(int index) => _names[index];
+
+ public bool Pressed(int index) => _actions[index]();
+
+ /// and are in 0..
+ public void SetVibration(int left, int right)
+ {
+ static ushort Conv(int i) => unchecked((ushort) ((i >> 15) & 0xFFFF));
+
+ if (!XInput.SetVibration(_index0, new(Conv(left), Conv(right))))
+ {
+ // Ignored, most likely the controller disconnected
+ }
+ }
+ }
+}
diff --git a/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs b/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs
new file mode 100644
index 00000000000..431ec920851
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.IO.Pipes;
+
+using BizHawk.Client.Common;
+
+// this is not a very safe or pretty protocol, I'm not proud of it
+namespace BizHawk.Bizware.Input
+{
+ internal static class IPCKeyInput
+ {
+ public static void Initialize()
+ {
+ if (!IPCActive)
+ {
+ var t = new Thread(IPCThread) { IsBackground = true };
+ t.Start();
+ IPCActive = true;
+ }
+ }
+
+ private static readonly List PendingEventList = new();
+ private static readonly List EventList = new();
+ private static bool IPCActive;
+
+ private static void IPCThread()
+ {
+ var pipeName = $"bizhawk-pid-{Process.GetCurrentProcess().Id}-IPCKeyInput";
+
+ while (true)
+ {
+ using var pipe = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024);
+ try
+ {
+ pipe.WaitForConnection();
+
+ var br = new BinaryReader(pipe);
+
+ while (true)
+ {
+ var e = br.ReadUInt32();
+ var pressed = (e & 0x80000000) != 0;
+ lock (PendingEventList)
+ {
+ PendingEventList.Add(new((DistinctKey)(e & 0x7FFFFFFF), pressed));
+ }
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ // ReSharper disable once FunctionNeverReturns
+ }
+
+ public static IEnumerable Update()
+ {
+ EventList.Clear();
+
+ lock (PendingEventList)
+ {
+ EventList.AddRange(PendingEventList);
+ PendingEventList.Clear();
+ }
+
+ return EventList;
+ }
+ }
+}
diff --git a/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs b/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs
new file mode 100644
index 00000000000..10b22fd1e88
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs
@@ -0,0 +1,95 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using BizHawk.Client.Common;
+using BizHawk.Common;
+
+namespace BizHawk.Bizware.Input
+{
+ ///
+ /// Abstract class which only handles keyboard input
+ /// Uses OS specific functionality, as there is no good cross platform way to do this
+ /// (mostly as all the available cross-platform options require a focused window, arg!)
+ /// TODO: Doesn't work for Wayland or macOS yet (maybe Linux should just use evdev here)
+ ///
+ public abstract class OSTailoredKeyInputAdapter : IHostInputAdapter
+ {
+ protected Config? _config;
+
+ public abstract string Desc { get; }
+
+ public virtual void DeInitAll()
+ {
+ switch (OSTailoredCode.CurrentOS)
+ {
+ case OSTailoredCode.DistinctOS.Linux:
+ X11KeyInput.Deinitialize();
+ break;
+ case OSTailoredCode.DistinctOS.macOS:
+ //QuartzKeyInput.Deinitialize();
+ //break;
+ throw new NotSupportedException("TODO QUARTZ");
+ case OSTailoredCode.DistinctOS.Windows:
+ DKeyInput.Cleanup();
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ public virtual void FirstInitAll(IntPtr mainFormHandle)
+ {
+ switch (OSTailoredCode.CurrentOS)
+ {
+ case OSTailoredCode.DistinctOS.Linux:
+ // TODO: probably need a libinput option for Wayland
+ // (unless we just want to ditch this and always use evdev here?)
+ X11KeyInput.Initialize();
+ break;
+ case OSTailoredCode.DistinctOS.macOS:
+ //QuartzKeyInput.Initialize();
+ //break;
+ throw new NotSupportedException("TODO QUARTZ");
+ case OSTailoredCode.DistinctOS.Windows:
+ // TODO: Consider if we want to use RAWINPUT API for keyboards instead
+ // Would remove DInput depenency on Windows (DInput gamepads could be considered optional in this sense)
+ // (also, this would be needed for keyboard support with UWP, which doesn't support DInput)
+ DKeyInput.Initialize(mainFormHandle);
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ IPCKeyInput.Initialize(); // why not? this isn't necessarily OS specific
+ }
+
+ public abstract IReadOnlyDictionary> GetHapticsChannels();
+
+ public abstract void ReInitGamepads(IntPtr mainFormHandle);
+
+ public abstract void PreprocessHostGamepads();
+
+ public abstract void ProcessHostGamepads(Action handleButton, Action handleAxis);
+
+ public virtual IEnumerable ProcessHostKeyboards()
+ {
+ var ret = OSTailoredCode.CurrentOS switch
+ {
+ OSTailoredCode.DistinctOS.Linux => X11KeyInput.Update(),
+ OSTailoredCode.DistinctOS.macOS => throw new NotSupportedException("TODO QUARTZ"),
+ OSTailoredCode.DistinctOS.Windows => DKeyInput.Update(_config ?? throw new(nameof(ProcessHostKeyboards) + " called before the global config was passed")),
+ _ => throw new InvalidOperationException()
+ };
+
+ return ret.Concat(IPCKeyInput.Update());
+ }
+
+ public abstract void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot);
+
+ public virtual void UpdateConfig(Config config)
+ => _config = config;
+ }
+}
diff --git a/src/BizHawk.Bizware.Input/SDL2/SDL2Gamepad.cs b/src/BizHawk.Bizware.Input/SDL2/SDL2Gamepad.cs
new file mode 100644
index 00000000000..c94eb182e4a
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/SDL2/SDL2Gamepad.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Collections.Generic;
+
+using static SDL2.SDL;
+
+namespace BizHawk.Bizware.Input
+{
+ ///
+ /// SDL2 Gamepad Handler
+ ///
+ internal class SDL2Gamepad : IDisposable
+ {
+ // indexed by instance id
+ private static readonly Dictionary Gamepads = new();
+
+ private readonly IntPtr Opaque;
+
+ /// Is an SDL_GameController rather than an SDL_Joystick
+ public readonly bool IsGameController;
+
+ /// Has rumble
+ public readonly bool HasRumble;
+
+ /// Contains name and delegate function for all buttons, hats and axis
+ public readonly IReadOnlyCollection<(string ButtonName, Func GetIsPressed)> ButtonGetters;
+
+ /// For use in keybind boxes
+ public string InputNamePrefix { get; private set; }
+
+ /// Device index in SDL
+ public int DeviceIndex { get; private set; }
+
+ /// Instance ID in SDL
+ public int InstanceID { get; }
+
+ /// Device name in SDL
+ public string DeviceName { get; }
+
+ public static void Deinitialize()
+ {
+ foreach (var gamepad in Gamepads.Values)
+ {
+ gamepad.Dispose();
+ }
+
+ Gamepads.Clear();
+ }
+
+ public void Dispose()
+ {
+ Console.WriteLine($"Disconnecting SDL gamepad, device index {DeviceIndex}, instance ID {InstanceID}, name {DeviceName}");
+
+ if (IsGameController)
+ {
+ SDL_GameControllerClose(Opaque);
+ }
+ else
+ {
+ SDL_JoystickClose(Opaque);
+ }
+ }
+
+ private static void RefreshIndexes()
+ {
+ var njoysticks = SDL_NumJoysticks();
+ for (var i = 0; i < njoysticks; i++)
+ {
+ var joystickId = SDL_JoystickGetDeviceInstanceID(i);
+ if (Gamepads.TryGetValue(joystickId, out var gamepad))
+ {
+ gamepad.UpdateIndex(i);
+ }
+ }
+ }
+
+ public static void AddDevice(int deviceIndex)
+ {
+ var instanceId = SDL_JoystickGetDeviceInstanceID(deviceIndex);
+ if (!Gamepads.ContainsKey(instanceId))
+ {
+ var gamepad = new SDL2Gamepad(deviceIndex);
+ Gamepads.Add(gamepad.InstanceID, gamepad);
+ }
+ else
+ {
+ Console.WriteLine($"Gamepads contained a joystick with instance ID {instanceId}, ignoring add device event");
+ }
+
+ RefreshIndexes();
+ }
+
+ public static void RemoveDevice(int deviceInstanceId)
+ {
+ if (Gamepads.TryGetValue(deviceInstanceId, out var gamepad))
+ {
+ gamepad.Dispose();
+ Gamepads.Remove(deviceInstanceId);
+ }
+ else
+ {
+ Console.WriteLine($"Gamepads did not contain a joystick with instance ID {deviceInstanceId}, ignoring remove device event");
+ }
+
+ RefreshIndexes();
+ }
+
+ public static IEnumerable EnumerateDevices()
+ => Gamepads.Values;
+
+ private List<(string ButtonName, Func GetIsPressed)> CreateGameControllerButtonGetters()
+ {
+ List<(string ButtonName, Func GetIsPressed)> buttonGetters = new();
+
+ const int dzp = 20000;
+ const int dzn = -20000;
+ const int dzt = 5000;
+
+ // buttons
+ buttonGetters.Add(("A", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A) == 1));
+ buttonGetters.Add(("B", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B) == 1));
+ buttonGetters.Add(("X", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X) == 1));
+ buttonGetters.Add(("Y", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y) == 1));
+ buttonGetters.Add(("Back", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK) == 1));
+ buttonGetters.Add(("Guide", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE) == 1));
+ buttonGetters.Add(("Start", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START) == 1));
+ buttonGetters.Add(("LeftThumb", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK) == 1));
+ buttonGetters.Add(("RightThumb", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK) == 1));
+ buttonGetters.Add(("LeftShoulder", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) == 1));
+ buttonGetters.Add(("RightShoulder", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) == 1));
+ buttonGetters.Add(("DpadUp", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP) == 1));
+ buttonGetters.Add(("DpadDown", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN) == 1));
+ buttonGetters.Add(("DpadLeft", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT) == 1));
+ buttonGetters.Add(("DpadRight", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) == 1));
+ buttonGetters.Add(("Misc", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1) == 1));
+ buttonGetters.Add(("Paddle1", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1) == 1));
+ buttonGetters.Add(("Paddle2", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2) == 1));
+ buttonGetters.Add(("Paddle3", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3) == 1));
+ buttonGetters.Add(("Paddle4", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4) == 1));
+ buttonGetters.Add(("Touchpad", () => SDL_GameControllerGetButton(Opaque, SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD) == 1));
+
+ // note: SDL has flipped meaning for the Y axis compared to DirectInput/XInput (-/+ for u/d instead of +/- for u/d)
+
+ // sticks
+ buttonGetters.Add(("LStickUp", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY) <= dzn));
+ buttonGetters.Add(("LStickDown", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY) >= dzp));
+ buttonGetters.Add(("LStickLeft", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX) <= dzn));
+ buttonGetters.Add(("LStickRight", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX) >= dzp));
+ buttonGetters.Add(("RStickUp", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY) <= dzn));
+ buttonGetters.Add(("RStickDown", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY) >= dzp));
+ buttonGetters.Add(("RStickLeft", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX) <= dzn));
+ buttonGetters.Add(("RStickRight", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX) >= dzp));
+
+ // triggers
+ buttonGetters.Add(("LeftTrigger", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT) > dzt));
+ buttonGetters.Add(("RightTrigger", () => SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > dzt));
+
+ return buttonGetters;
+ }
+
+ private List<(string ButtonName, Func GetIsPressed)> CreateJoystickButtonGetters()
+ {
+ List<(string ButtonName, Func GetIsPressed)> buttonGetters = new();
+
+ const float dzp = 20000;
+ const float dzn = -20000;
+
+ // axes
+ buttonGetters.Add(("X+", () => SDL_JoystickGetAxis(Opaque, 0) >= dzp));
+ buttonGetters.Add(("X-", () => SDL_JoystickGetAxis(Opaque, 0) <= dzn));
+ buttonGetters.Add(("Y+", () => SDL_JoystickGetAxis(Opaque, 1) >= dzp));
+ buttonGetters.Add(("Y-", () => SDL_JoystickGetAxis(Opaque, 1) <= dzn));
+ buttonGetters.Add(("Z+", () => SDL_JoystickGetAxis(Opaque, 2) >= dzp));
+ buttonGetters.Add(("Z-", () => SDL_JoystickGetAxis(Opaque, 2) <= dzn));
+ buttonGetters.Add(("W+", () => SDL_JoystickGetAxis(Opaque, 3) >= dzp));
+ buttonGetters.Add(("W-", () => SDL_JoystickGetAxis(Opaque, 3) <= dzn));
+ buttonGetters.Add(("V+", () => SDL_JoystickGetAxis(Opaque, 4) >= dzp));
+ buttonGetters.Add(("V-", () => SDL_JoystickGetAxis(Opaque, 4) <= dzn));
+ buttonGetters.Add(("S+", () => SDL_JoystickGetAxis(Opaque, 5) >= dzp));
+ buttonGetters.Add(("S-", () => SDL_JoystickGetAxis(Opaque, 5) <= dzn));
+ buttonGetters.Add(("Q+", () => SDL_JoystickGetAxis(Opaque, 6) >= dzp));
+ buttonGetters.Add(("Q-", () => SDL_JoystickGetAxis(Opaque, 6) <= dzn));
+ buttonGetters.Add(("P+", () => SDL_JoystickGetAxis(Opaque, 7) >= dzp));
+ buttonGetters.Add(("P-", () => SDL_JoystickGetAxis(Opaque, 7) <= dzn));
+ buttonGetters.Add(("N+", () => SDL_JoystickGetAxis(Opaque, 8) >= dzp));
+ buttonGetters.Add(("N-", () => SDL_JoystickGetAxis(Opaque, 8) <= dzn));
+ var naxes = SDL_JoystickNumAxes(Opaque);
+ for (var i = 9; i < naxes; i++)
+ {
+ var j = i;
+ buttonGetters.Add(($"Axis{j}+", () => SDL_JoystickGetAxis(Opaque, j) >= dzp));
+ buttonGetters.Add(($"Axis{j}-", () => SDL_JoystickGetAxis(Opaque, j) <= dzn));
+ }
+
+ // buttons
+ var nbuttons = SDL_JoystickNumButtons(Opaque);
+ for (var i = 0; i < nbuttons; i++)
+ {
+ var j = i;
+ buttonGetters.Add(($"B{i + 1}", () => SDL_JoystickGetButton(Opaque, j) == 1));
+ }
+
+ // hats
+ var nhats = SDL_JoystickNumHats(Opaque);
+ for (var i = 0; i < nhats; i++)
+ {
+ var j = i;
+ buttonGetters.Add(($"POV{j}U", () => (SDL_JoystickGetHat(Opaque, j) & SDL_HAT_UP) == SDL_HAT_UP));
+ buttonGetters.Add(($"POV{j}D", () => (SDL_JoystickGetHat(Opaque, j) & SDL_HAT_DOWN) == SDL_HAT_DOWN));
+ buttonGetters.Add(($"POV{j}L", () => (SDL_JoystickGetHat(Opaque, j) & SDL_HAT_LEFT) == SDL_HAT_LEFT));
+ buttonGetters.Add(($"POV{j}R", () => (SDL_JoystickGetHat(Opaque, j) & SDL_HAT_RIGHT) == SDL_HAT_RIGHT));
+ }
+
+ return buttonGetters;
+ }
+
+ public void UpdateIndex(int index)
+ {
+ InputNamePrefix = IsGameController
+ ? $"X{index + 1} "
+ : $"J{index + 1} ";
+ DeviceIndex = index;
+ }
+
+ private SDL2Gamepad(int index)
+ {
+ if (SDL_IsGameController(index) == SDL_bool.SDL_TRUE)
+ {
+ Opaque = SDL_GameControllerOpen(index);
+ HasRumble = SDL_GameControllerHasRumble(Opaque) == SDL_bool.SDL_TRUE;
+ ButtonGetters = CreateGameControllerButtonGetters();
+ IsGameController = true;
+ InputNamePrefix = $"X{index + 1} ";
+ DeviceName = SDL_GameControllerName(Opaque);
+ }
+ else
+ {
+ Opaque = SDL_JoystickOpen(index);
+ HasRumble = SDL_JoystickHasRumble(Opaque) == SDL_bool.SDL_TRUE;
+ ButtonGetters = CreateJoystickButtonGetters();
+ IsGameController = false;
+ InputNamePrefix = $"J{index + 1} ";
+ DeviceName = SDL_JoystickName(Opaque);
+ }
+
+ DeviceIndex = index;
+ InstanceID = SDL_JoystickGetDeviceInstanceID(index);
+
+ Console.WriteLine($"Connected SDL gamepad, device index {index}, instance ID {InstanceID}, name {DeviceName}");
+ }
+
+ public IEnumerable<(string AxisID, int Value)> GetAxes()
+ {
+ //constant for adapting a +/- 32768 range to a +/-10000-based range
+ const float f = 32768 / 10000.0f;
+ static int Conv(short num) => (int)(num / f);
+
+ // note: SDL has flipped meaning for the Y axis compared to DirectInput/XInput (-/+ for u/d instead of +/- for u/d)
+
+ if (IsGameController)
+ {
+ return new[]
+ {
+ ("LeftThumbX", Conv(SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX))),
+ ("LeftThumbY", -Conv(SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY))),
+ ("RightThumbX", Conv(SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX))),
+ ("RightThumbY", -Conv(SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY))),
+ ("LeftTrigger", Conv(SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT))),
+ ("RightTrigger", Conv(SDL_GameControllerGetAxis(Opaque, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT))),
+ };
+ }
+
+ List<(string AxisID, int Value)> values = new()
+ {
+ ("X", Conv(SDL_JoystickGetAxis(Opaque, 0))),
+ ("Y", Conv(SDL_JoystickGetAxis(Opaque, 1))),
+ ("Z", Conv(SDL_JoystickGetAxis(Opaque, 2))),
+ ("W", Conv(SDL_JoystickGetAxis(Opaque, 3))),
+ ("V", Conv(SDL_JoystickGetAxis(Opaque, 4))),
+ ("S", Conv(SDL_JoystickGetAxis(Opaque, 5))),
+ ("Q", Conv(SDL_JoystickGetAxis(Opaque, 6))),
+ ("P", Conv(SDL_JoystickGetAxis(Opaque, 7))),
+ ("N", Conv(SDL_JoystickGetAxis(Opaque, 8))),
+ };
+
+ var naxes = SDL_JoystickNumAxes(Opaque);
+ for (var i = 9; i < naxes; i++)
+ {
+ var j = i;
+ values.Add(($"Axis{j}", Conv(SDL_JoystickGetAxis(Opaque, j))));
+ }
+
+ return values;
+ }
+
+ /// and are in 0..
+ public void SetVibration(int left, int right)
+ {
+ static ushort Conv(int i) => unchecked((ushort) ((i >> 15) & 0xFFFF));
+ _ = IsGameController
+ ? SDL_GameControllerRumble(Opaque, Conv(left), Conv(right), uint.MaxValue)
+ : SDL_JoystickRumble(Opaque, Conv(left), Conv(right), uint.MaxValue);
+ }
+ }
+}
+
diff --git a/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs b/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs
new file mode 100644
index 00000000000..bad8f912eb0
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs
@@ -0,0 +1,187 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using BizHawk.Client.Common;
+using BizHawk.Common;
+using BizHawk.Common.CollectionExtensions;
+
+using static SDL2.SDL;
+
+namespace BizHawk.Bizware.Input
+{
+ public sealed class SDL2InputAdapter : OSTailoredKeyInputAdapter
+ {
+ private static readonly IReadOnlyCollection SDL2_HAPTIC_CHANNEL_NAMES = new[] { "Left", "Right" };
+
+ private IReadOnlyDictionary _lastHapticsSnapshot = new Dictionary();
+
+ private bool _sdlInitCalled; // must be deferred on the input thread (FirstInitAll is not on the input thread)
+ private bool _isInit;
+
+ public override string Desc => "SDL2";
+
+ // we only want joystick adding and remove events
+ private static readonly SDL_EventFilter _sdlEventFilter = SDLEventFilter;
+
+ private static unsafe int SDLEventFilter(IntPtr userdata, IntPtr e)
+ => ((SDL_Event*)e)->type is SDL_EventType.SDL_JOYDEVICEADDED or SDL_EventType.SDL_JOYDEVICEREMOVED ? 1 : 0;
+
+ static SDL2InputAdapter()
+ {
+ SDL_SetEventFilter(_sdlEventFilter, IntPtr.Zero);
+ // this is required as we create hidden (unfocused!) SDL windows in IGL backends
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+ }
+
+ private static void DoSDLEventLoop()
+ {
+ var e = new SDL_Event[1];
+ // this loop somewhat models SDL_PollEvent
+ while (true)
+ {
+ // need to pump events for this thread's queue (normally expected from SDL_PumpEvents, but not allowed since this is not the video thread)
+ // similar code shouldn't be needed on other platforms (which have global message queues and not thread local message queues)
+ if (!OSTailoredCode.IsUnixHost)
+ {
+ while (Win32Imports.PeekMessage(out var msg, IntPtr.Zero, 0, 0, Win32Imports.PM_REMOVE))
+ {
+ Win32Imports.TranslateMessage(ref msg);
+ Win32Imports.DispatchMessage(ref msg);
+ }
+ }
+
+ SDL_JoystickUpdate();
+
+ if (SDL_PeepEvents(e, 1, SDL_eventaction.SDL_GETEVENT, SDL_EventType.SDL_JOYDEVICEADDED, SDL_EventType.SDL_JOYDEVICEREMOVED) != 1)
+ {
+ break;
+ }
+
+ // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
+ switch (e[0].type)
+ {
+ case SDL_EventType.SDL_JOYDEVICEADDED:
+ SDL2Gamepad.AddDevice(e[0].jdevice.which);
+ break;
+ case SDL_EventType.SDL_JOYDEVICEREMOVED:
+ SDL2Gamepad.RemoveDevice(e[0].jdevice.which);
+ break;
+ }
+ }
+ }
+
+ public override void DeInitAll()
+ {
+ if (!_isInit)
+ {
+ return;
+ }
+
+ base.DeInitAll();
+ SDL2Gamepad.Deinitialize();
+ if (_sdlInitCalled)
+ {
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER);
+ _sdlInitCalled = false;
+ }
+
+ _isInit = false;
+ }
+
+ public override void FirstInitAll(IntPtr mainFormHandle)
+ {
+ if (_isInit) throw new InvalidOperationException($"Cannot reinit with {nameof(FirstInitAll)}");
+
+ // SDL2's keyboard support is not usable by us, as it requires a focused window
+ // even worse, the main form doesn't even work in this context
+ // as for some reason SDL2 just never receives input events
+ base.FirstInitAll(mainFormHandle);
+
+ // first event loop adds controllers
+ // but it must be deferred to the input thread (first PreprocessHostGamepads call)
+ // however, let's just test if SDL init works (if it does, 99.9% chance it will on the input thread)
+ try
+ {
+ if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) != 0)
+ {
+ throw new($"SDL failed to init, SDL error: {SDL_GetError()}");
+ }
+ }
+ finally
+ {
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER);
+ SDL_FlushEvents(SDL_EventType.SDL_FIRSTEVENT, SDL_EventType.SDL_LASTEVENT);
+ }
+
+ _isInit = true;
+ }
+
+ public override IReadOnlyDictionary> GetHapticsChannels()
+ {
+ return _isInit
+ ? SDL2Gamepad.EnumerateDevices()
+ .Where(pad => pad.HasRumble)
+ .Select(pad => pad.InputNamePrefix)
+ .ToDictionary(s => s, _ => SDL2_HAPTIC_CHANNEL_NAMES)
+ : new();
+ }
+
+ public override void ReInitGamepads(IntPtr mainFormHandle)
+ {
+ }
+
+ public override void PreprocessHostGamepads()
+ {
+ if (!_sdlInitCalled)
+ {
+ if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) != 0)
+ {
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER);
+ throw new($"SDL failed to init, SDL error: {SDL_GetError()}");
+ }
+
+ _sdlInitCalled = true;
+ }
+
+ DoSDLEventLoop();
+ }
+
+ public override void ProcessHostGamepads(Action handleButton, Action handleAxis)
+ {
+ if (!_isInit) return;
+
+ foreach (var pad in SDL2Gamepad.EnumerateDevices())
+ {
+ foreach (var but in pad.ButtonGetters)
+ {
+ handleButton(pad.InputNamePrefix + but.ButtonName, but.GetIsPressed(), ClientInputFocus.Pad);
+ }
+
+ foreach (var (axisID, f) in pad.GetAxes())
+ {
+ handleAxis($"{pad.InputNamePrefix}{axisID} Axis", f);
+ }
+
+ if (pad.HasRumble)
+ {
+ var leftStrength = _lastHapticsSnapshot.GetValueOrDefault(pad.InputNamePrefix + "Left");
+ var rightStrength = _lastHapticsSnapshot.GetValueOrDefault(pad.InputNamePrefix + "Right");
+ pad.SetVibration(leftStrength, rightStrength);
+ }
+ }
+ }
+
+ public override IEnumerable ProcessHostKeyboards()
+ {
+ return _isInit
+ ? base.ProcessHostKeyboards()
+ : Enumerable.Empty();
+ }
+
+ public override void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot)
+ => _lastHapticsSnapshot = hapticsSnapshot.ToDictionary(tuple => tuple.Name, tuple => tuple.Strength);
+ }
+}
diff --git a/src/BizHawk.Bizware.Input/X11/X11KeyInput.cs b/src/BizHawk.Bizware.Input/X11/X11KeyInput.cs
new file mode 100644
index 00000000000..7e8000d8503
--- /dev/null
+++ b/src/BizHawk.Bizware.Input/X11/X11KeyInput.cs
@@ -0,0 +1,396 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+
+using BizHawk.Client.Common;
+using BizHawk.Common.CollectionExtensions;
+
+using static BizHawk.Common.XlibImports;
+
+// a lot of this code is taken from OpenTK
+
+namespace BizHawk.Bizware.Input
+{
+ internal static class X11KeyInput
+ {
+ private static IntPtr Display;
+ private static readonly DistinctKey[] KeyEnumMap = new DistinctKey[256];
+ private static readonly bool[] LastKeyState = new bool[256];
+
+ private static readonly object _syncObject = new();
+
+ public static void Initialize()
+ {
+ lock (_syncObject)
+ {
+ Deinitialize();
+
+ Display = XOpenDisplay(null);
+
+ if (Display == IntPtr.Zero)
+ {
+ throw new("Could not open XDisplay");
+ }
+
+ using (new XLock(Display))
+ {
+ // check if we can use XKb
+ int major = 1, minor = 0;
+ var supportsXkb = XkbQueryExtension(Display, out _, out _, out _, ref major, ref minor);
+
+ if (supportsXkb)
+ {
+ // we generally want this behavior
+ XkbSetDetectableAutoRepeat(Display, true, out _);
+ }
+
+ CreateKeyMap(supportsXkb);
+ }
+ }
+ }
+
+ public static void Deinitialize()
+ {
+ lock (_syncObject)
+ {
+ if (Display != IntPtr.Zero)
+ {
+ _ = XCloseDisplay(Display);
+ Display = IntPtr.Zero;
+ }
+ }
+ }
+
+ public static unsafe IEnumerable Update()
+ {
+ lock (_syncObject)
+ {
+ var keys = stackalloc byte[32];
+
+ using (new XLock(Display))
+ {
+ // this apparently always returns 1 no matter what?
+ _ = XQueryKeymap(Display, keys);
+ }
+
+ var eventList = new List();
+ for (var keycode = 0; keycode < 256; keycode++)
+ {
+ var keystate = (keys[keycode >> 3] >> (keycode & 0x07) & 0x01) != 0;
+
+ if (LastKeyState[keycode] != keystate)
+ {
+ eventList.Add(new(KeyEnumMap[keycode], pressed: keystate));
+ LastKeyState[keycode] = keystate;
+ }
+ }
+
+ return eventList;
+ }
+ }
+
+ private static unsafe void CreateKeyMap(bool supportsXkb)
+ {
+ for (var i = 0; i < KeyEnumMap.Length; i++)
+ {
+ KeyEnumMap[i] = DistinctKey.Unknown;
+ }
+
+ if (supportsXkb)
+ {
+ var keyboard = XkbAllocKeyboard(Display);
+ if (keyboard != null)
+ {
+ _ = XkbGetNames(Display, 0x3FF, keyboard);
+
+ for (int i = keyboard->min_key_code; i <= keyboard->max_key_code; i++)
+ {
+ var name = new string(keyboard->names->keys[i].name, 0, 4);
+ var key = name switch
+ {
+ "TLDE" => DistinctKey.OemTilde,
+ "AE01" => DistinctKey.D1,
+ "AE02" => DistinctKey.D2,
+ "AE03" => DistinctKey.D3,
+ "AE04" => DistinctKey.D4,
+ "AE05" => DistinctKey.D5,
+ "AE06" => DistinctKey.D6,
+ "AE07" => DistinctKey.D7,
+ "AE08" => DistinctKey.D8,
+ "AE09" => DistinctKey.D9,
+ "AE10" => DistinctKey.D0,
+ "AE11" => DistinctKey.OemMinus,
+ "AE12" => DistinctKey.OemPlus,
+ "AD01" => DistinctKey.Q,
+ "AD02" => DistinctKey.W,
+ "AD03" => DistinctKey.E,
+ "AD04" => DistinctKey.R,
+ "AD05" => DistinctKey.T,
+ "AD06" => DistinctKey.Y,
+ "AD07" => DistinctKey.U,
+ "AD08" => DistinctKey.I,
+ "AD09" => DistinctKey.O,
+ "AD10" => DistinctKey.P,
+ "AD11" => DistinctKey.OemOpenBrackets,
+ "AD12" => DistinctKey.OemCloseBrackets,
+ "AC01" => DistinctKey.A,
+ "AC02" => DistinctKey.S,
+ "AC03" => DistinctKey.D,
+ "AC04" => DistinctKey.F,
+ "AC05" => DistinctKey.G,
+ "AC06" => DistinctKey.H,
+ "AC07" => DistinctKey.J,
+ "AC08" => DistinctKey.K,
+ "AC09" => DistinctKey.L,
+ "AC10" => DistinctKey.OemSemicolon,
+ "AC11" => DistinctKey.OemQuotes,
+ "AB01" => DistinctKey.Z,
+ "AB02" => DistinctKey.X,
+ "AB03" => DistinctKey.C,
+ "AB04" => DistinctKey.V,
+ "AB05" => DistinctKey.B,
+ "AB06" => DistinctKey.N,
+ "AB07" => DistinctKey.M,
+ "AB08" => DistinctKey.OemComma,
+ "AB09" => DistinctKey.OemPeriod,
+ "AB10" => DistinctKey.OemQuestion,
+ "BKSL" => DistinctKey.OemPipe,
+ _ => DistinctKey.Unknown,
+ };
+
+ KeyEnumMap[i] = key;
+ }
+
+ XkbFreeKeyboard(keyboard, 0, true);
+ }
+ }
+
+ for (var i = 0; i < KeyEnumMap.Length; i++)
+ {
+ if (KeyEnumMap[i] == DistinctKey.Unknown)
+ {
+ if (supportsXkb)
+ {
+ var keysym = XkbKeycodeToKeysym(Display, i, 0, 1);
+ var key = keysym switch
+ {
+ Keysym.KP_0 => DistinctKey.NumPad0,
+ Keysym.KP_1 => DistinctKey.NumPad1,
+ Keysym.KP_2 => DistinctKey.NumPad2,
+ Keysym.KP_3 => DistinctKey.NumPad3,
+ Keysym.KP_4 => DistinctKey.NumPad4,
+ Keysym.KP_5 => DistinctKey.NumPad5,
+ Keysym.KP_6 => DistinctKey.NumPad6,
+ Keysym.KP_7 => DistinctKey.NumPad7,
+ Keysym.KP_8 => DistinctKey.NumPad8,
+ Keysym.KP_9 => DistinctKey.NumPad9,
+ Keysym.KP_Separator => DistinctKey.Separator,
+ Keysym.KP_Decimal => DistinctKey.Decimal,
+ Keysym.KP_Enter => DistinctKey.NumPadEnter,
+ _ => DistinctKey.Unknown,
+ };
+
+ if (key == DistinctKey.Unknown)
+ {
+ keysym = XkbKeycodeToKeysym(Display, i, 0, 0);
+ key = KeysymEnumMap.GetValueOrDefault(keysym, DistinctKey.Unknown);
+ }
+
+ KeyEnumMap[i] = key;
+ }
+ else
+ {
+ var e = new XKeyEvent
+ {
+ display = Display,
+ keycode = i,
+ };
+
+ var keysym = XLookupKeysym(ref e, 0);
+ var key = KeysymEnumMap.GetValueOrDefault(keysym, DistinctKey.Unknown);
+
+ if (key == DistinctKey.Unknown)
+ {
+ keysym = XLookupKeysym(ref e, 1);
+ key = KeysymEnumMap.GetValueOrDefault(keysym, DistinctKey.Unknown);
+ }
+
+ KeyEnumMap[i] = key;
+ }
+ }
+ }
+ }
+
+ private static readonly IReadOnlyDictionary KeysymEnumMap = new Dictionary
+ {
+ [Keysym.Escape] = DistinctKey.Escape,
+ [Keysym.Return] = DistinctKey.Return,
+ [Keysym.space] = DistinctKey.Space,
+ [Keysym.BackSpace] = DistinctKey.Back,
+ [Keysym.Shift_L] = DistinctKey.LeftShift,
+ [Keysym.Shift_R] = DistinctKey.RightShift,
+ [Keysym.Alt_L] = DistinctKey.LeftAlt,
+ [Keysym.Alt_R] = DistinctKey.RightAlt,
+ [Keysym.Control_L] = DistinctKey.LeftCtrl,
+ [Keysym.Control_R] = DistinctKey.RightCtrl,
+ [Keysym.Super_L] = DistinctKey.LWin,
+ [Keysym.Super_R] = DistinctKey.RWin,
+ [Keysym.Meta_L] = DistinctKey.LWin,
+ [Keysym.Meta_R] = DistinctKey.RWin,
+ [Keysym.ISO_Level3_Shift] = DistinctKey.RightAlt,
+ [Keysym.Menu] = DistinctKey.Apps,
+ [Keysym.Tab] = DistinctKey.Tab,
+ [Keysym.minus] = DistinctKey.OemMinus,
+ [Keysym.plus] = DistinctKey.OemPlus,
+ [Keysym.equal] = DistinctKey.OemPlus,
+ [Keysym.Caps_Lock] = DistinctKey.CapsLock,
+ [Keysym.Num_Lock] = DistinctKey.NumLock,
+ [Keysym.F1] = DistinctKey.F1,
+ [Keysym.F2] = DistinctKey.F2,
+ [Keysym.F3] = DistinctKey.F3,
+ [Keysym.F4] = DistinctKey.F4,
+ [Keysym.F5] = DistinctKey.F5,
+ [Keysym.F6] = DistinctKey.F6,
+ [Keysym.F7] = DistinctKey.F7,
+ [Keysym.F8] = DistinctKey.F8,
+ [Keysym.F9] = DistinctKey.F9,
+ [Keysym.F10] = DistinctKey.F10,
+ [Keysym.F11] = DistinctKey.F11,
+ [Keysym.F12] = DistinctKey.F12,
+ [Keysym.F13] = DistinctKey.F13,
+ [Keysym.F14] = DistinctKey.F14,
+ [Keysym.F15] = DistinctKey.F15,
+ [Keysym.F16] = DistinctKey.F16,
+ [Keysym.F17] = DistinctKey.F17,
+ [Keysym.F18] = DistinctKey.F18,
+ [Keysym.F19] = DistinctKey.F19,
+ [Keysym.F20] = DistinctKey.F20,
+ [Keysym.F21] = DistinctKey.F21,
+ [Keysym.F22] = DistinctKey.F22,
+ [Keysym.F23] = DistinctKey.F23,
+ [Keysym.F24] = DistinctKey.F24,
+ [Keysym.A] = DistinctKey.A,
+ [Keysym.a] = DistinctKey.A,
+ [Keysym.B] = DistinctKey.B,
+ [Keysym.b] = DistinctKey.B,
+ [Keysym.C] = DistinctKey.C,
+ [Keysym.c] = DistinctKey.C,
+ [Keysym.D] = DistinctKey.D,
+ [Keysym.d] = DistinctKey.D,
+ [Keysym.E] = DistinctKey.E,
+ [Keysym.e] = DistinctKey.E,
+ [Keysym.F] = DistinctKey.F,
+ [Keysym.f] = DistinctKey.F,
+ [Keysym.G] = DistinctKey.G,
+ [Keysym.g] = DistinctKey.G,
+ [Keysym.H] = DistinctKey.H,
+ [Keysym.h] = DistinctKey.H,
+ [Keysym.I] = DistinctKey.I,
+ [Keysym.i] = DistinctKey.I,
+ [Keysym.J] = DistinctKey.J,
+ [Keysym.j] = DistinctKey.J,
+ [Keysym.K] = DistinctKey.K,
+ [Keysym.k] = DistinctKey.K,
+ [Keysym.L] = DistinctKey.L,
+ [Keysym.l] = DistinctKey.L,
+ [Keysym.M] = DistinctKey.M,
+ [Keysym.m] = DistinctKey.M,
+ [Keysym.N] = DistinctKey.N,
+ [Keysym.n] = DistinctKey.N,
+ [Keysym.O] = DistinctKey.O,
+ [Keysym.o] = DistinctKey.O,
+ [Keysym.P] = DistinctKey.P,
+ [Keysym.p] = DistinctKey.P,
+ [Keysym.Q] = DistinctKey.Q,
+ [Keysym.q] = DistinctKey.Q,
+ [Keysym.R] = DistinctKey.R,
+ [Keysym.r] = DistinctKey.R,
+ [Keysym.S] = DistinctKey.S,
+ [Keysym.s] = DistinctKey.S,
+ [Keysym.T] = DistinctKey.T,
+ [Keysym.t] = DistinctKey.T,
+ [Keysym.U] = DistinctKey.U,
+ [Keysym.u] = DistinctKey.U,
+ [Keysym.V] = DistinctKey.V,
+ [Keysym.v] = DistinctKey.V,
+ [Keysym.W] = DistinctKey.W,
+ [Keysym.w] = DistinctKey.W,
+ [Keysym.X] = DistinctKey.X,
+ [Keysym.x] = DistinctKey.X,
+ [Keysym.Y] = DistinctKey.Y,
+ [Keysym.y] = DistinctKey.Y,
+ [Keysym.Z] = DistinctKey.Z,
+ [Keysym.z] = DistinctKey.Z,
+ [Keysym.Number0] = DistinctKey.D0,
+ [Keysym.Number1] = DistinctKey.D1,
+ [Keysym.Number2] = DistinctKey.D2,
+ [Keysym.Number3] = DistinctKey.D3,
+ [Keysym.Number4] = DistinctKey.D4,
+ [Keysym.Number5] = DistinctKey.D5,
+ [Keysym.Number6] = DistinctKey.D6,
+ [Keysym.Number7] = DistinctKey.D7,
+ [Keysym.Number8] = DistinctKey.D8,
+ [Keysym.Number9] = DistinctKey.D9,
+ [Keysym.KP_0] = DistinctKey.NumPad0,
+ [Keysym.KP_1] = DistinctKey.NumPad1,
+ [Keysym.KP_2] = DistinctKey.NumPad2,
+ [Keysym.KP_3] = DistinctKey.NumPad3,
+ [Keysym.KP_4] = DistinctKey.NumPad4,
+ [Keysym.KP_5] = DistinctKey.NumPad5,
+ [Keysym.KP_6] = DistinctKey.NumPad6,
+ [Keysym.KP_7] = DistinctKey.NumPad7,
+ [Keysym.KP_8] = DistinctKey.NumPad8,
+ [Keysym.KP_9] = DistinctKey.NumPad9,
+ [Keysym.Pause] = DistinctKey.Pause,
+ [Keysym.Break] = DistinctKey.Pause,
+ [Keysym.Scroll_Lock] = DistinctKey.Scroll,
+ [Keysym.Insert] = DistinctKey.Insert,
+ [Keysym.Print] = DistinctKey.PrintScreen,
+ [Keysym.Sys_Req] = DistinctKey.PrintScreen,
+ [Keysym.backslash] = DistinctKey.OemBackslash,
+ [Keysym.bar] = DistinctKey.OemBackslash,
+ [Keysym.braceleft] = DistinctKey.OemOpenBrackets,
+ [Keysym.bracketleft] = DistinctKey.OemOpenBrackets,
+ [Keysym.braceright] = DistinctKey.OemCloseBrackets,
+ [Keysym.bracketright] = DistinctKey.OemCloseBrackets,
+ [Keysym.colon] = DistinctKey.OemSemicolon,
+ [Keysym.semicolon] = DistinctKey.OemSemicolon,
+ [Keysym.quoteright] = DistinctKey.OemQuotes,
+ [Keysym.quotedbl] = DistinctKey.OemQuotes,
+ [Keysym.quoteleft] = DistinctKey.OemTilde,
+ [Keysym.asciitilde] = DistinctKey.OemTilde,
+ [Keysym.comma] = DistinctKey.OemComma,
+ [Keysym.less] = DistinctKey.OemComma,
+ [Keysym.period] = DistinctKey.OemPeriod,
+ [Keysym.greater] = DistinctKey.OemPeriod,
+ [Keysym.slash] = DistinctKey.OemQuestion,
+ [Keysym.question] = DistinctKey.OemQuestion,
+ [Keysym.Left] = DistinctKey.Left,
+ [Keysym.Down] = DistinctKey.Down,
+ [Keysym.Right] = DistinctKey.Right,
+ [Keysym.Up] = DistinctKey.Up,
+ [Keysym.Delete] = DistinctKey.Delete,
+ [Keysym.Home] = DistinctKey.Home,
+ [Keysym.End] = DistinctKey.End,
+ [Keysym.Page_Up] = DistinctKey.PageUp,
+ [Keysym.Page_Down] = DistinctKey.PageDown,
+ [Keysym.KP_Add] = DistinctKey.Add,
+ [Keysym.KP_Subtract] = DistinctKey.Subtract,
+ [Keysym.KP_Multiply] = DistinctKey.Multiply,
+ [Keysym.KP_Divide] = DistinctKey.Divide,
+ [Keysym.KP_Decimal] = DistinctKey.Decimal,
+ [Keysym.KP_Insert] = DistinctKey.NumPad0,
+ [Keysym.KP_End] = DistinctKey.NumPad1,
+ [Keysym.KP_Down] = DistinctKey.NumPad2,
+ [Keysym.KP_Page_Down] = DistinctKey.NumPad3,
+ [Keysym.KP_Left] = DistinctKey.NumPad4,
+ [Keysym.KP_Right] = DistinctKey.NumPad6,
+ [Keysym.KP_Home] = DistinctKey.NumPad7,
+ [Keysym.KP_Up] = DistinctKey.NumPad8,
+ [Keysym.KP_Page_Up] = DistinctKey.NumPad9,
+ [Keysym.KP_Delete] = DistinctKey.Decimal,
+ [Keysym.KP_Enter] = DistinctKey.NumPadEnter,
+ };
+ }
+}
diff --git a/src/BizHawk.Bizware.OpenTK3/GraphicsControl_TK.cs b/src/BizHawk.Bizware.OpenTK3/GraphicsControl_TK.cs
deleted file mode 100644
index a77aae1f757..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/GraphicsControl_TK.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Windows.Forms;
-
-using BizHawk.Bizware.BizwareGL;
-
-using OpenTK;
-using OpenTK.Graphics;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- internal class GLControlWrapper : GLControl, IGraphicsControl
- {
- public RenderTargetWrapper RenderTargetWrapper
- {
- get => throw new NotImplementedException();
- set => throw new NotImplementedException();
- }
-
- // Note: In order to work around bugs in OpenTK which sometimes do things to a context without making that context active first...
- // we are going to push and pop the context before doing stuff
- public GLControlWrapper(IGL_TK owner)
- : base(GraphicsMode.Default, 2, 0, GraphicsContextFlags.Default)
- {
- _owner = owner;
- _glControl = this;
- }
-
- private readonly GLControl _glControl;
- private readonly IGL_TK _owner;
-
- public Control Control => this;
-
- public void SetVsync(bool state)
- {
- _glControl.MakeCurrent();
- _glControl.VSync = state;
- }
-
- public void Begin()
- {
- if (!_glControl.Context.IsCurrent)
- {
- _owner.MakeContextCurrent(_glControl.Context, _glControl.WindowInfo);
- }
- }
-
- public void End()
- {
- _owner.MakeDefaultCurrent();
- }
-
- public new void SwapBuffers()
- {
- if (!_glControl.Context.IsCurrent)
- {
- MakeCurrent();
- }
-
- base.SwapBuffers();
- }
- }
-}
\ No newline at end of file
diff --git a/src/BizHawk.Bizware.OpenTK3/IGL_TK.cs b/src/BizHawk.Bizware.OpenTK3/IGL_TK.cs
deleted file mode 100644
index cc309c072a3..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/IGL_TK.cs
+++ /dev/null
@@ -1,857 +0,0 @@
-// regarding binding and vertex arrays:
-// http://stackoverflow.com/questions/8704801/glvertexattribpointer-clarification
-// http://stackoverflow.com/questions/9536973/oes-vertex-array-object-and-client-state
-// http://www.opengl.org/wiki/Vertex_Specification
-
-// etc
-// glBindAttribLocation (programID, 0, "vertexPosition_modelspace");
-
-using System;
-using System.IO;
-using System.Collections.Generic;
-
-using BizHawk.Bizware.BizwareGL;
-using BizHawk.Common;
-
-using OpenTK;
-using OpenTK.Graphics;
-using OpenTK.Graphics.OpenGL;
-using OpenTK.Platform;
-
-using BizGL = BizHawk.Bizware.BizwareGL;
-using BlendEquationMode = OpenTK.Graphics.OpenGL.BlendEquationMode;
-using BlendingFactorDest = OpenTK.Graphics.OpenGL.BlendingFactorDest;
-using BlendingFactorSrc = OpenTK.Graphics.OpenGL.BlendingFactorSrc;
-using ClearBufferMask = OpenTK.Graphics.OpenGL.ClearBufferMask;
-using Matrix4 = BizHawk.Bizware.BizwareGL.Matrix4;
-using PrimitiveType = OpenTK.Graphics.OpenGL.PrimitiveType;
-using sd = System.Drawing;
-using sdi = System.Drawing.Imaging;
-using swf = System.Windows.Forms;
-using Vector2 = BizHawk.Bizware.BizwareGL.Vector2;
-using Vector4 = BizHawk.Bizware.BizwareGL.Vector4;
-using VertexAttribPointerType = OpenTK.Graphics.OpenGL.VertexAttribPointerType;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- ///
- /// OpenTK implementation of the BizwareGL.IGL interface.
- /// TODO - can we have more than one of these? could be dangerous. such dangerous things to be possibly reconsidered are marked with HAMNUTS
- /// TODO - if we have any way of making contexts, we also need a way of freeing it, and then we can cleanup our dictionaries
- ///
- public class IGL_TK : IGL
- {
- public EDispMethod DispMethodEnum => EDispMethod.OpenGL;
-
- //rendering state
- private Pipeline _currPipeline;
- private RenderTarget _currRenderTarget;
-
- public string API => "OPENGL";
-
- public int Version
- {
- get
- {
- //doesnt work on older than gl3 maybe
- //int major, minor;
- ////other overloads may not exist...
- //GL.GetInteger(GetPName.MajorVersion, out major);
- //GL.GetInteger(GetPName.MinorVersion, out minor);
-
- //supposedly the standard dictates that whatever junk is in the version string, some kind of version is at the beginning
- string version_string = GL.GetString(StringName.Version);
- var version_parts = version_string.Split('.');
- int major = int.Parse(version_parts[0]);
- //getting a minor version out is too hard and not needed now
- return major * 100;
- }
- }
-
- public IGL_TK(int majorVersion, int minorVersion, bool forwardCompatible)
- {
- OpenTKConfigurator.EnsureConfigurated();
-
- //make an 'offscreen context' so we can at least do things without having to create a window
- OffscreenNativeWindow = new NativeWindow { ClientSize = new sd.Size(8, 8) };
- GraphicsContext = new GraphicsContext(GraphicsMode.Default, OffscreenNativeWindow.WindowInfo, majorVersion, minorVersion, forwardCompatible ? GraphicsContextFlags.ForwardCompatible : GraphicsContextFlags.Default);
- MakeDefaultCurrent();
-
- //this is important for reasons unknown
- GraphicsContext.LoadAll();
-
- //misc initialization
- CreateRenderStates();
- PurgeStateCache();
- }
-
- public void BeginScene()
- {
- // seems not to be needed...
- }
-
- public void EndScene()
- {
- // seems not to be needed...
- }
-
- void IDisposable.Dispose()
- {
- //TODO - a lot of analysis here
- OffscreenNativeWindow.Dispose(); OffscreenNativeWindow = null;
- GraphicsContext.Dispose(); GraphicsContext = null;
- }
-
- public void Clear(BizGL.ClearBufferMask mask)
- {
- GL.Clear((ClearBufferMask) (int) mask); // these are the same enum
- }
- public void SetClearColor(sd.Color color)
- {
- GL.ClearColor(color);
- }
-
- public IGraphicsControl Internal_CreateGraphicsControl()
- {
- var glc = new GLControlWrapper(this);
- glc.CreateControl();
-
- // now the control's context will be current. annoying! fix it.
- MakeDefaultCurrent();
-
- return glc;
- }
-
- public int GenTexture() => GL.GenTexture();
-
- public void FreeTexture(Texture2d tex)
- {
- GL.DeleteTexture((int)tex.Opaque);
- }
-
- public Shader CreateFragmentShader(string source, string entry, bool required)
- {
- return CreateShader(ShaderType.FragmentShader, source, entry, required);
- }
-
- public Shader CreateVertexShader(string source, string entry, bool required)
- {
- return CreateShader(ShaderType.VertexShader, source, entry, required);
- }
-
- public IBlendState CreateBlendState(
- BizGL.BlendingFactorSrc colorSource,
- BizGL.BlendEquationMode colorEquation,
- BizGL.BlendingFactorDest colorDest,
- BizGL.BlendingFactorSrc alphaSource,
- BizGL.BlendEquationMode alphaEquation,
- BizGL.BlendingFactorDest alphaDest)
- {
- return new CacheBlendState(true, colorSource, colorEquation, colorDest, alphaSource, alphaEquation, alphaDest);
- }
-
- public void SetBlendState(IBlendState rsBlend)
- {
- var mybs = rsBlend as CacheBlendState;
- if (mybs.Enabled)
- {
- GL.Enable(EnableCap.Blend);
- // these are all casts to copies of the same enum
- GL.BlendEquationSeparate(
- (BlendEquationMode) (int) mybs.colorEquation,
- (BlendEquationMode) (int) mybs.alphaEquation);
- GL.BlendFuncSeparate(
- (BlendingFactorSrc) (int) mybs.colorSource,
- (BlendingFactorDest) (int) mybs.colorDest,
- (BlendingFactorSrc) (int) mybs.alphaSource,
- (BlendingFactorDest) (int) mybs.alphaDest);
- }
- else GL.Disable(EnableCap.Blend);
- if (rsBlend == _rsBlendNoneOpaque)
- {
- //make sure constant color is set correctly
- GL.BlendColor(new Color4(255, 255, 255, 255));
- }
- }
-
- public IBlendState BlendNoneCopy => _rsBlendNoneVerbatim;
- public IBlendState BlendNoneOpaque => _rsBlendNoneOpaque;
- public IBlendState BlendNormal => _rsBlendNormal;
-
- private class ShaderWrapper
- {
- public int sid;
-// public Dictionary MapCodeToNative;
-// public Dictionary MapNativeToCode;
- }
-
- private class PipelineWrapper
- {
- public int pid;
- public Shader FragmentShader, VertexShader;
- public List SamplerLocs;
- }
-
- ///
- /// is and either or is unavailable (their property is ), or
- /// glLinkProgram call did not produce expected result
- ///
- public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
- {
- // if the shaders aren't available, the pipeline isn't either
- if (!vertexShader.Available || !fragmentShader.Available)
- {
- string errors = $"Vertex Shader:\r\n {vertexShader.Errors} \r\n-------\r\nFragment Shader:\r\n{fragmentShader.Errors}";
- if (required)
- throw new InvalidOperationException($"Couldn't build required GL pipeline:\r\n{errors}");
- var pipeline = new Pipeline(this, null, false, null, null, null) { Errors = errors };
- return pipeline;
- }
-
- bool success = true;
-
- var vsw = vertexShader.Opaque as ShaderWrapper;
- var fsw = fragmentShader.Opaque as ShaderWrapper;
- var sws = new[] { vsw,fsw };
-
- ErrorCode errcode;
- int pid = GL.CreateProgram();
- GL.AttachShader(pid, vsw.sid);
- errcode = GL.GetError();
- GL.AttachShader(pid, fsw.sid);
- errcode = GL.GetError();
-
- GL.LinkProgram(pid);
- errcode = GL.GetError();
-
- string resultLog = GL.GetProgramInfoLog(pid);
-
- if (errcode != ErrorCode.NoError)
- {
- if (required)
- throw new InvalidOperationException($"Error creating pipeline (error returned from glLinkProgram): {errcode}\r\n\r\n{resultLog}");
- else success = false;
- }
-
- GL.GetProgram(pid, GetProgramParameterName.LinkStatus, out var linkStatus);
- if (linkStatus == 0)
- {
- if (required)
- throw new InvalidOperationException($"Error creating pipeline (link status false returned from glLinkProgram): \r\n\r\n{resultLog}");
- else success = false;
- resultLog = GL.GetProgramInfoLog(pid);
- Util.DebugWriteLine(resultLog);
- }
-
- //need to work on validation. apparently there are some weird caveats to glValidate which make it complicated and possibly excuses (barely) the intel drivers' dysfunctional operation
- //"A sampler points to a texture unit used by fixed function with an incompatible target"
- //
- //info:
- //http://www.opengl.org/sdk/docs/man/xhtml/glValidateProgram.xml
- //This function mimics the validation operation that OpenGL implementations must perform when rendering commands are issued while programmable shaders are part of current state.
- //glValidateProgram checks to see whether the executables contained in program can execute given the current OpenGL state
- //This function is typically useful only during application development.
- //
- //So, this is no big deal. we shouldn't be calling validate right now anyway.
- //conclusion: glValidate is very complicated and is of virtually no use unless your draw calls are returning errors and you want to know why
- //GL.ValidateProgram(pid);
- //errcode = GL.GetError();
- //resultLog = GL.GetProgramInfoLog(pid);
- //if (errcode != ErrorCode.NoError)
- // throw new InvalidOperationException($"Error creating pipeline (error returned from glValidateProgram): {errcode}\r\n\r\n{resultLog}");
- //int validateStatus;
- //GL.GetProgram(pid, GetProgramParameterName.ValidateStatus, out validateStatus);
- //if (validateStatus == 0)
- // throw new InvalidOperationException($"Error creating pipeline (validateStatus status false returned from glValidateProgram): \r\n\r\n{resultLog}");
-
- //set the program to active, in case we need to set sampler uniforms on it
- GL.UseProgram(pid);
-
- //get all the attributes (not needed)
- List attributes = new List();
- GL.GetProgram(pid, GetProgramParameterName.ActiveAttributes, out var nAttributes);
- for (int i = 0; i < nAttributes; i++)
- {
- int size, length;
- string name = new System.Text.StringBuilder(1024).ToString();
- ActiveAttribType type;
- GL.GetActiveAttrib(pid, i, 1024, out length, out size, out type, out name);
- attributes.Add(new AttributeInfo() { Handle = new IntPtr(i), Name = name });
- }
-
- //get all the uniforms
- List uniforms = new List();
- GL.GetProgram(pid,GetProgramParameterName.ActiveUniforms,out var nUniforms);
- List samplers = new List();
-
- for (int i = 0; i < nUniforms; i++)
- {
- int size, length;
- string name = new System.Text.StringBuilder(1024).ToString();
- GL.GetActiveUniform(pid, i, 1024, out length, out size, out var type, out name);
- errcode = GL.GetError();
- int loc = GL.GetUniformLocation(pid, name);
-
- var ui = new UniformInfo { Name = name, Opaque = loc };
-
- if (type == ActiveUniformType.Sampler2D)
- {
- ui.IsSampler = true;
- ui.SamplerIndex = samplers.Count;
- ui.Opaque = loc | (samplers.Count << 24);
- samplers.Add(loc);
- }
-
- uniforms.Add(ui);
- }
-
- // deactivate the program, so we don't accidentally use it
- GL.UseProgram(0);
-
- if (!vertexShader.Available) success = false;
- if (!fragmentShader.Available) success = false;
-
- var pw = new PipelineWrapper { pid = pid, VertexShader = vertexShader, FragmentShader = fragmentShader, SamplerLocs = samplers };
-
- return new Pipeline(this, pw, success, vertexLayout, uniforms, memo);
- }
-
- public void FreePipeline(Pipeline pipeline)
- {
- var pw = pipeline.Opaque as PipelineWrapper;
-
- // unavailable pipelines will have no opaque
- if (pw == null)
- {
- return;
- }
-
- GL.DeleteProgram(pw.pid);
-
- pw.FragmentShader.Release();
- pw.VertexShader.Release();
- }
-
- public void Internal_FreeShader(Shader shader)
- {
- var sw = shader.Opaque as ShaderWrapper;
- GL.DeleteShader(sw.sid);
- }
-
- /// . is
- public void BindPipeline(Pipeline pipeline)
- {
- _currPipeline = pipeline;
-
- if (pipeline == null)
- {
- sStatePendingVertexLayout = null;
- GL.UseProgram(0);
- return;
- }
-
- if (!pipeline.Available) throw new InvalidOperationException("Attempt to bind unavailable pipeline");
- sStatePendingVertexLayout = pipeline.VertexLayout;
-
- var pw = pipeline.Opaque as PipelineWrapper;
- GL.UseProgram(pw.pid);
-
- //this is dumb and confusing, but we have to bind physical sampler numbers to sampler variables.
- for (int i = 0; i < pw.SamplerLocs.Count; i++)
- {
- GL.Uniform1(pw.SamplerLocs[i], i);
- }
- }
-
-
- public VertexLayout CreateVertexLayout() => new VertexLayout(this, null);
-
- private void BindTexture2d(Texture2d tex)
- {
- GL.BindTexture(TextureTarget.Texture2D, (int)tex.Opaque);
- }
-
- public void SetTextureWrapMode(Texture2d tex, bool clamp)
- {
- BindTexture2d(tex);
- int mode;
- if (clamp)
- {
- mode = (int)TextureWrapMode.ClampToEdge;
- }
- else
- {
- mode = (int)TextureWrapMode.Repeat;
- }
-
- GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, mode);
- GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, mode);
- }
-
- public void BindArrayData(IntPtr pData) => MyBindArrayData(sStatePendingVertexLayout, pData);
-
- public void DrawArrays(BizGL.PrimitiveType mode, int first, int count)
- {
- GL.DrawArrays((PrimitiveType) (int) mode, first, count); // these are the same enum
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, bool value)
- {
- GL.Uniform1((int) uniform.Sole.Opaque, value ? 1 : 0);
- }
-
- public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4 mat, bool transpose)
- {
- GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)&mat);
- }
-
- public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4 mat, bool transpose)
- {
- fixed(Matrix4* pMat = &mat)
- GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)pMat);
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
- {
- GL.Uniform4((int)uniform.Sole.Opaque, value.X, value.Y, value.Z, value.W);
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
- {
- GL.Uniform2((int)uniform.Sole.Opaque, value.X, value.Y);
- }
-
- public void SetPipelineUniform(PipelineUniform uniform, float value)
- {
- if (uniform.Owner == null) return; //uniform was optimized out
- GL.Uniform1((int)uniform.Sole.Opaque, value);
- }
-
- public unsafe void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
- {
- fixed (Vector4* pValues = &values[0])
- GL.Uniform4((int)uniform.Sole.Opaque, values.Length, (float*)pValues);
- }
-
- public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
- {
- int n = ((int)uniform.Sole.Opaque)>>24;
-
- //set the sampler index into the uniform first
- if (sActiveTexture != n)
- {
- sActiveTexture = n;
- var selectedUnit = (TextureUnit)((int)TextureUnit.Texture0 + n);
- GL.ActiveTexture(selectedUnit);
- }
-
- // now bind the texture
- GL.BindTexture(TextureTarget.Texture2D, (int)tex.Opaque);
- }
-
- public void SetMinFilter(Texture2d texture, BizGL.TextureMinFilter minFilter)
- {
- BindTexture2d(texture);
- GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) minFilter);
- }
-
- public void SetMagFilter(Texture2d texture, BizGL.TextureMagFilter magFilter)
- {
- BindTexture2d(texture);
- GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) magFilter);
- }
-
- public Texture2d LoadTexture(sd.Bitmap bitmap)
- {
- using var bmp = new BitmapBuffer(bitmap, new BitmapLoadOptions());
- return (this as IGL).LoadTexture(bmp);
- }
-
- public Texture2d LoadTexture(Stream stream)
- {
- using var bmp = new BitmapBuffer(stream,new BitmapLoadOptions());
- return (this as IGL).LoadTexture(bmp);
- }
-
- public Texture2d CreateTexture(int width, int height)
- {
- int id = GenTexture();
- return new Texture2d(this, id, width, height);
- }
-
- public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
- {
- return new Texture2d(this as IGL, glTexId.ToInt32(), width, height);
- }
-
- public void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
- {
- sdi.BitmapData bmpData = bmp.LockBits();
- try
- {
- GL.BindTexture(TextureTarget.Texture2D, (int)tex.Opaque);
- GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, bmp.Width, bmp.Height, PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0);
- }
- finally
- {
- bmp.UnlockBits(bmpData);
- }
- }
-
- public void FreeRenderTarget(RenderTarget rt)
- {
- rt.Texture2d.Dispose();
- GL.Ext.DeleteFramebuffer((int)rt.Opaque);
- }
-
- /// framebuffer creation unsuccessful
- public unsafe RenderTarget CreateRenderTarget(int w, int h)
- {
- //create a texture for it
- int texId = GenTexture();
- Texture2d tex = new Texture2d(this, texId, w, h);
- GL.BindTexture(TextureTarget.Texture2D, texId);
- GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, w, h, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
- tex.SetMagFilter(BizGL.TextureMagFilter.Nearest);
- tex.SetMinFilter(BizGL.TextureMinFilter.Nearest);
-
- // create the FBO
- int fbId = GL.Ext.GenFramebuffer();
- GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, fbId);
-
- //bind the tex to the FBO
- GL.Ext.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texId, 0);
-
- // do something, I guess say which color buffers are used by the framebuffer
- DrawBuffersEnum* buffers = stackalloc DrawBuffersEnum[1];
- buffers[0] = DrawBuffersEnum.ColorAttachment0;
- GL.DrawBuffers(1, buffers);
-
- if (GL.Ext.CheckFramebufferStatus(FramebufferTarget.Framebuffer) !=
- FramebufferErrorCode.FramebufferComplete)
- {
- throw new InvalidOperationException($"Error creating framebuffer (at {nameof(GL.Ext.CheckFramebufferStatus)})");
- }
-
- // since we're done configuring unbind this framebuffer, to return to the default
- GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
-
- return new RenderTarget(this, fbId, tex);
- }
-
- public void BindRenderTarget(RenderTarget rt)
- {
- _currRenderTarget = rt;
- if (rt == null)
- {
- GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
- }
- else
- {
- GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, (int)rt.Opaque);
- }
- }
-
- public Texture2d LoadTexture(BitmapBuffer bmp)
- {
- Texture2d ret = null;
- int id = GenTexture();
- try
- {
- ret = new Texture2d(this, id, bmp.Width, bmp.Height);
- GL.BindTexture(TextureTarget.Texture2D, id);
- //picking a color order that matches doesnt seem to help, any. maybe my driver is accelerating it, or maybe it isnt a big deal. but its something to study on another day
- GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmp.Width, bmp.Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
- (this as IGL).LoadTextureData(ret, bmp);
- }
- catch
- {
- GL.DeleteTexture(id);
- throw;
- }
-
- //set default filtering.. its safest to do this always
- ret.SetFilterNearest();
-
- return ret;
- }
-
- public BitmapBuffer ResolveTexture2d(Texture2d tex)
- {
- //note - this is dangerous since it changes the bound texture. could we save it?
- BindTexture2d(tex);
- var bb = new BitmapBuffer(tex.IntWidth, tex.IntHeight);
- var bmpdata = bb.LockBits();
- GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgra, PixelType.UnsignedByte, bmpdata.Scan0);
- var err = GL.GetError();
- bb.UnlockBits(bmpdata);
- return bb;
- }
-
- public Texture2d LoadTexture(string path)
- {
- using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
- return (this as IGL).LoadTexture(fs);
- }
-
- public Matrix4 CreateGuiProjectionMatrix(int w, int h)
- {
- return CreateGuiProjectionMatrix(new sd.Size(w, h));
- }
-
- public Matrix4 CreateGuiViewMatrix(int w, int h, bool autoflip)
- {
- return CreateGuiViewMatrix(new sd.Size(w, h), autoflip);
- }
-
- public Matrix4 CreateGuiProjectionMatrix(sd.Size dims)
- {
- Matrix4 ret = Matrix4.Identity;
- ret.Row0.X = 2.0f / (float)dims.Width;
- ret.Row1.Y = 2.0f / (float)dims.Height;
- return ret;
- }
-
- public Matrix4 CreateGuiViewMatrix(sd.Size dims, bool autoflip)
- {
- Matrix4 ret = Matrix4.Identity;
- ret.Row1.Y = -1.0f;
- ret.Row3.X = -(float)dims.Width * 0.5f;
- ret.Row3.Y = (float)dims.Height * 0.5f;
- if (autoflip && _currRenderTarget is not null) // flip as long as we're not a final render target
- {
- ret.Row1.Y = 1.0f;
- ret.Row3.Y *= -1;
- }
- return ret;
- }
-
- public void SetViewport(int x, int y, int width, int height)
- {
- GL.Viewport(x, y, width, height);
- GL.Scissor(x, y, width, height); //hack for mupen[rice]+intel: at least the rice plugin leaves the scissor rectangle scrambled, and we're trying to run it in the main graphics context for intel
- //BUT ALSO: new specifications.. viewport+scissor make sense together
- }
-
- public void SetViewport(int width, int height)
- {
- SetViewport(0, 0, width, height);
- }
-
- public void SetViewport(sd.Size size)
- {
- SetViewport(size.Width, size.Height);
- }
-
- public void SetViewport(swf.Control control)
- {
- var r = control.ClientRectangle;
- SetViewport(r.Left, r.Top, r.Width, r.Height);
- }
-
- //------------------
-
- private INativeWindow OffscreenNativeWindow;
- private IGraphicsContext GraphicsContext;
-
- //---------------
- //my utility methods
-
- private GLControl CastControl(swf.Control swfControl)
- {
- if (swfControl is not GLControl glc) throw new ArgumentException(message: "Argument isn't a control created by the IGL interface", paramName: nameof(swfControl));
- return glc;
- }
-
- private Shader CreateShader(ShaderType type, string source, string entry, bool required)
- {
- var sw = new ShaderWrapper();
- string info = "";
-
- int sid = GL.CreateShader(type);
- bool ok = CompileShaderSimple(sid, source, required);
- if(!ok)
- {
- GL.GetShaderInfoLog(sid, out info);
- GL.DeleteShader(sid);
- sid = 0;
- }
-
- Shader ret = new Shader(this, sw, ok);
- ret.Errors = info;
- sw.sid = sid;
-
- return ret;
- }
-
- private bool CompileShaderSimple(int sid, string source, bool required)
- {
- bool success = true;
- ErrorCode errcode;
-
- errcode = GL.GetError();
- if (errcode != ErrorCode.NoError)
- if (required)
- throw new InvalidOperationException($"Error compiling shader (from previous operation) {errcode}");
- else success = false;
-
- GL.ShaderSource(sid, source);
-
- errcode = GL.GetError();
- if (errcode != ErrorCode.NoError)
- if (required)
- throw new InvalidOperationException($"Error compiling shader ({nameof(GL.ShaderSource)}) {errcode}");
- else success = false;
-
- GL.CompileShader(sid);
- errcode = GL.GetError();
-
- string resultLog = GL.GetShaderInfoLog(sid);
-
- if (errcode != ErrorCode.NoError)
- {
- string message = $"Error compiling shader ({nameof(GL.CompileShader)}) {errcode}\r\n\r\n{resultLog}";
- if (required)
- throw new InvalidOperationException(message);
- else
- {
- Console.WriteLine(message);
- success = false;
- }
- }
-
- GL.GetShader(sid, ShaderParameter.CompileStatus, out var n);
-
- if (n == 0)
- if (required)
- throw new InvalidOperationException($"Error compiling shader ({nameof(GL.GetShader)})\r\n\r\n{resultLog}");
- else success = false;
-
- return success;
- }
-
- private void UnbindVertexAttributes()
- {
- //HAMNUTS:
- //its not clear how many bindings we'll have to disable before we can enable the ones we need..
- //so lets just disable the ones we remember we have bound
- var currBindings = _sVertexAttribEnables;
- foreach (var index in currBindings)
- GL.DisableVertexAttribArray(index);
- currBindings.Clear();
- }
-
- private void MyBindArrayData(VertexLayout layout, IntPtr pData)
- {
- UnbindVertexAttributes();
-
- //HAMNUTS (continued)
- var currBindings = _sVertexAttribEnables;
- sStateCurrentVertexLayout = sStatePendingVertexLayout;
-
- if (layout == null) return;
-
- //disable all the client states.. a lot of overhead right now, to be sure
- GL.DisableClientState(ArrayCap.VertexArray);
- GL.DisableClientState(ArrayCap.ColorArray);
- for(int i=0;i<8;i++)
- GL.DisableVertexAttribArray(i);
- for (int i = 0; i < 8; i++)
- {
- GL.ClientActiveTexture(TextureUnit.Texture0 + i);
- GL.DisableClientState(ArrayCap.TextureCoordArray);
- }
- GL.ClientActiveTexture(TextureUnit.Texture0);
-
- foreach (var (i, item) in layout.Items)
- {
- if(_currPipeline.Memo == "gui")
- {
- GL.VertexAttribPointer(
- i,
- item.Components,
- (VertexAttribPointerType) (int) item.AttribType, // these are the same enum
- item.Normalized,
- item.Stride,
- pData + item.Offset);
- GL.EnableVertexAttribArray(i);
- currBindings.Add(i);
- }
- else
- {
-
- var pw = _currPipeline.Opaque as PipelineWrapper;
-
- //comment SNACKPANTS
- switch (item.Usage)
- {
- case AttribUsage.Position:
- GL.EnableClientState(ArrayCap.VertexArray);
- GL.VertexPointer(item.Components, VertexPointerType.Float, item.Stride, pData + item.Offset);
- break;
- case AttribUsage.Texcoord0:
- GL.ClientActiveTexture(TextureUnit.Texture0);
- GL.EnableClientState(ArrayCap.TextureCoordArray);
- GL.TexCoordPointer(item.Components, TexCoordPointerType.Float, item.Stride, pData + item.Offset);
- break;
- case AttribUsage.Texcoord1:
- GL.ClientActiveTexture(TextureUnit.Texture1);
- GL.EnableClientState(ArrayCap.TextureCoordArray);
- GL.TexCoordPointer(item.Components, TexCoordPointerType.Float, item.Stride, pData + item.Offset);
- GL.ClientActiveTexture(TextureUnit.Texture0);
- break;
- case AttribUsage.Color0:
- break;
- }
- }
- }
- }
-
- public void MakeDefaultCurrent()
- {
- MakeContextCurrent(this.GraphicsContext,OffscreenNativeWindow.WindowInfo);
- }
-
- internal void MakeContextCurrent(IGraphicsContext context, IWindowInfo windowInfo)
- {
- context.MakeCurrent(windowInfo);
- PurgeStateCache();
- }
-
- private void CreateRenderStates()
- {
- _rsBlendNoneVerbatim = new CacheBlendState(
- false,
- BizGL.BlendingFactorSrc.One, BizGL.BlendEquationMode.FuncAdd, BizGL.BlendingFactorDest.Zero,
- BizGL.BlendingFactorSrc.One, BizGL.BlendEquationMode.FuncAdd, BizGL.BlendingFactorDest.Zero);
-
- _rsBlendNoneOpaque = new CacheBlendState(
- false,
- BizGL.BlendingFactorSrc.One, BizGL.BlendEquationMode.FuncAdd, BizGL.BlendingFactorDest.Zero,
- BizGL.BlendingFactorSrc.ConstantAlpha, BizGL.BlendEquationMode.FuncAdd, BizGL.BlendingFactorDest.Zero);
-
- _rsBlendNormal = new CacheBlendState(
- true,
- BizGL.BlendingFactorSrc.SrcAlpha, BizGL.BlendEquationMode.FuncAdd, BizGL.BlendingFactorDest.OneMinusSrcAlpha,
- BizGL.BlendingFactorSrc.One, BizGL.BlendEquationMode.FuncAdd, BizGL.BlendingFactorDest.Zero);
- }
-
- private CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal;
-
- //state caches
- private int sActiveTexture;
- private VertexLayout sStateCurrentVertexLayout;
- private VertexLayout sStatePendingVertexLayout;
- private readonly HashSet _sVertexAttribEnables = new HashSet();
-
- private void PurgeStateCache()
- {
- sStateCurrentVertexLayout = null;
- sStatePendingVertexLayout = null;
- _sVertexAttribEnables.Clear();
- sActiveTexture = -1;
- }
-
- } //class IGL_TK
-
-}
diff --git a/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs b/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs
deleted file mode 100644
index f85aa6df6ec..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs
+++ /dev/null
@@ -1,341 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-
-using BizHawk.Common;
-
-using OpenTK.Input;
-
-using OpenTKGamePad = OpenTK.Input.GamePad;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- ///
- /// Modified OpenTK Gamepad Handler
- /// The jump from OpenTK 1.x to 3.x broke the original OpenTK.Input.Joystick implementation, but we gain OpenTK.Input.GamePad support on Unix. However, the gamepad auto-mapping is a little suspect, so we use both methods.
- /// As a side-effect, it should make it easier to implement virtual→host haptics in the future.
- ///
- public class OTK_GamePad
- {
- /// They don't have a way to query this for some reason. 4 is the minimum promised.
- private const int MAX_GAMEPADS = 4;
-
- private static readonly object _syncObj = new object();
-
- private static readonly OTK_GamePad[] Devices = new OTK_GamePad[MAX_GAMEPADS];
-
- private static volatile bool initialized = false;
-
- public static void Initialize()
- {
- for (var i = 0; i < MAX_GAMEPADS; i++) OpenTKGamePad.GetState(i); // not sure if this is important to do at this time, but the processing which used to be done here included calls to OpenTK, so I left this no-op just in case --yoshi
- initialized = true;
- }
-
- public static IEnumerable EnumerateDevices()
- {
- if (!initialized) return Enumerable.Empty();
- lock (_syncObj) return Devices.Where(pad => pad is not null).ToList();
- }
-
- public static void UpdateAll()
- {
- static void DropAt(int index, IList devices)
- {
- var known = devices[index];
- devices[index] = null;
- Console.WriteLine(known is null ? $"Dropped gamepad X{index + 1}/J{index + 1}" : $"Dropped gamepad {known.InputNamePrefixShort}: was {known.MappingsDatabaseName}");
- }
- if (!initialized) return;
- lock (_syncObj)
- {
- for (var tryIndex = 0; tryIndex < MAX_GAMEPADS; tryIndex++)
- {
- var known = Devices[tryIndex];
- try
- {
- var isConnectedAtIndex = OpenTKGamePad.GetState(tryIndex).IsConnected || Joystick.GetState(tryIndex).IsConnected;
- if (known is not null)
- {
- if (isConnectedAtIndex) known.Update();
- else DropAt(tryIndex, Devices);
- }
- else
- {
- if (isConnectedAtIndex)
- {
- var newConn = Devices[tryIndex] = new(tryIndex);
- Console.WriteLine($"Connected new gamepad {newConn.InputNamePrefixShort}: {newConn.MappingsDatabaseName}");
- }
- // else was and remains disconnected, move along
- }
- }
- catch (Exception e)
- {
- Util.DebugWriteLine($"caught {e.GetType().FullName} while enumerating OpenTK gamepads");
- DropAt(tryIndex, Devices);
- }
- }
- }
- }
-
- /// The OpenTK device index
- private readonly int _deviceIndex;
-
- /// The object returned by
- private readonly GamePadCapabilities? _gamePadCapabilities;
-
- /// The object returned by
- private readonly JoystickCapabilities? _joystickCapabilities;
-
- public readonly IReadOnlyCollection HapticsChannels;
-
- /// For use in keybind boxes
- public readonly string InputNamePrefix;
-
- /// as but without the trailing space
- private readonly string InputNamePrefixShort;
-
- /// Public check on whether mapped gamepad config is being used
- public bool MappedGamePad => _gamePadCapabilities?.IsMapped == true;
-
- /// GUID from (also used for DB) and name from via DB
- private readonly string MappingsDatabaseName;
-
- /// Gamepad Device state information - updated constantly
- private GamePadState state;
-
- /// Joystick Device state information - updated constantly
- private JoystickState jState;
-
- private OTK_GamePad(int deviceIndex)
- {
- _deviceIndex = deviceIndex;
-
- Guid? guid = null;
- if (Joystick.GetState(_deviceIndex).IsConnected)
- {
- guid = Joystick.GetGuid(_deviceIndex);
- _joystickCapabilities = Joystick.GetCapabilities(_deviceIndex);
- }
-
- string name;
- if (OpenTKGamePad.GetState(_deviceIndex).IsConnected)
- {
- name = OpenTKGamePad.GetName(_deviceIndex);
- _gamePadCapabilities = OpenTKGamePad.GetCapabilities(_deviceIndex);
- }
- else
- {
- name = "OTK GamePad Undetermined Name";
- }
- HapticsChannels = _gamePadCapabilities != null && _gamePadCapabilities.Value.HasLeftVibrationMotor && _gamePadCapabilities.Value.HasRightVibrationMotor
- ? new[] { "Left", "Right" } // two haptic motors
- : new[] { "Mono" }; // one or zero haptic motors -- in the latter case, pretend it's mono anyway as that doesn't seem to cause problems
- InputNamePrefixShort = $"{(MappedGamePad ? "X" : "J")}{_deviceIndex + 1}";
- InputNamePrefix = $"{InputNamePrefixShort} ";
- MappingsDatabaseName = $"{guid ?? Guid.Empty} {name}";
- Update();
-
- // Setup mappings prior to button initialization.
- List<(string ButtonName, Func GetIsPressed)> buttonGetters = new();
-
- if (guid is not null)
- {
- // placeholder for if/when we figure out how to supply OpenTK with custom GamePadConfigurationDatabase entries
- }
-
- // currently OpenTK has an internal database of mappings for the GamePad class: https://github.com/opentk/opentk/blob/master/src/OpenTK/Input/GamePadConfigurationDatabase.cs
- if (MappedGamePad)
- {
- // internal map detected - use OpenTKGamePad
-
- // OpenTK's ThumbSticks contain float values (as opposed to the shorts of SlimDX)
- const float ConversionFactor = 1.0f / short.MaxValue;
- const float dzp = 20000 * ConversionFactor;
- const float dzn = -20000 * ConversionFactor;
- const float dzt = 0.6f;
-
- // buttons
- buttonGetters.Add(("A", () => state.Buttons.A == ButtonState.Pressed));
- buttonGetters.Add(("B", () => state.Buttons.B == ButtonState.Pressed));
- buttonGetters.Add(("X", () => state.Buttons.X == ButtonState.Pressed));
- buttonGetters.Add(("Y", () => state.Buttons.Y == ButtonState.Pressed));
- buttonGetters.Add(("Guide", () => state.Buttons.BigButton == ButtonState.Pressed));
- buttonGetters.Add(("Start", () => state.Buttons.Start == ButtonState.Pressed));
- buttonGetters.Add(("Back", () => state.Buttons.Back == ButtonState.Pressed));
- buttonGetters.Add(("LeftThumb", () => state.Buttons.LeftStick == ButtonState.Pressed));
- buttonGetters.Add(("RightThumb", () => state.Buttons.RightStick == ButtonState.Pressed));
- buttonGetters.Add(("LeftShoulder", () => state.Buttons.LeftShoulder == ButtonState.Pressed));
- buttonGetters.Add(("RightShoulder", () => state.Buttons.RightShoulder == ButtonState.Pressed));
-
- // dpad
- buttonGetters.Add(("DpadUp", () => state.DPad.Up == ButtonState.Pressed));
- buttonGetters.Add(("DpadDown", () => state.DPad.Down == ButtonState.Pressed));
- buttonGetters.Add(("DpadLeft", () => state.DPad.Left == ButtonState.Pressed));
- buttonGetters.Add(("DpadRight", () => state.DPad.Right == ButtonState.Pressed));
-
- // sticks
- buttonGetters.Add(("LStickUp", () => state.ThumbSticks.Left.Y >= dzp));
- buttonGetters.Add(("LStickDown", () => state.ThumbSticks.Left.Y <= dzn));
- buttonGetters.Add(("LStickLeft", () => state.ThumbSticks.Left.X <= dzn));
- buttonGetters.Add(("LStickRight", () => state.ThumbSticks.Left.X >= dzp));
- buttonGetters.Add(("RStickUp", () => state.ThumbSticks.Right.Y >= dzp));
- buttonGetters.Add(("RStickDown", () => state.ThumbSticks.Right.Y <= dzn));
- buttonGetters.Add(("RStickLeft", () => state.ThumbSticks.Right.X <= dzn));
- buttonGetters.Add(("RStickRight", () => state.ThumbSticks.Right.X >= dzp));
-
- // triggers
- buttonGetters.Add(("LeftTrigger", () => state.Triggers.Left > dzt));
- buttonGetters.Add(("RightTrigger", () => state.Triggers.Right > dzt));
- }
- else
- {
- // no internal map detected - use Joystick
-
- // OpenTK's GetAxis returns float values (as opposed to the shorts of SlimDX)
- const float ConversionFactor = 1.0f / short.MaxValue;
- const float dzp = 20000 * ConversionFactor;
- const float dzn = -20000 * ConversionFactor;
-// const float dzt = 0.6f;
-
- // axis
- buttonGetters.Add(("X+", () => jState.GetAxis(0) >= dzp));
- buttonGetters.Add(("X-", () => jState.GetAxis(0) <= dzn));
- buttonGetters.Add(("Y+", () => jState.GetAxis(1) >= dzp));
- buttonGetters.Add(("Y-", () => jState.GetAxis(1) <= dzn));
- buttonGetters.Add(("Z+", () => jState.GetAxis(2) >= dzp));
- buttonGetters.Add(("Z-", () => jState.GetAxis(2) <= dzn));
- buttonGetters.Add(("W+", () => jState.GetAxis(3) >= dzp));
- buttonGetters.Add(("W-", () => jState.GetAxis(3) <= dzn));
- buttonGetters.Add(("V+", () => jState.GetAxis(4) >= dzp));
- buttonGetters.Add(("V-", () => jState.GetAxis(4) <= dzn));
- buttonGetters.Add(("S+", () => jState.GetAxis(5) >= dzp));
- buttonGetters.Add(("S-", () => jState.GetAxis(5) <= dzn));
- buttonGetters.Add(("Q+", () => jState.GetAxis(6) >= dzp));
- buttonGetters.Add(("Q-", () => jState.GetAxis(6) <= dzn));
- buttonGetters.Add(("P+", () => jState.GetAxis(7) >= dzp));
- buttonGetters.Add(("P-", () => jState.GetAxis(7) <= dzn));
- buttonGetters.Add(("N+", () => jState.GetAxis(8) >= dzp));
- buttonGetters.Add(("N-", () => jState.GetAxis(8) <= dzn));
- // should be enough axes, but just in case:
- for (var i = 9; i < 64; i++)
- {
- var j = i;
- buttonGetters.Add(($"Axis{j.ToString()}+", () => jState.GetAxis(j) >= dzp));
- buttonGetters.Add(($"Axis{j.ToString()}-", () => jState.GetAxis(j) <= dzn));
- }
-
- // buttons
- for (int i = 0, l = _joystickCapabilities?.ButtonCount ?? 0; i < l; i++)
- {
- var j = i;
- buttonGetters.Add(($"B{i + 1}", () => jState.GetButton(j) == ButtonState.Pressed));
- }
-
- // hats
- buttonGetters.Add(("POV1U", () => jState.GetHat(JoystickHat.Hat0).IsUp));
- buttonGetters.Add(("POV1D", () => jState.GetHat(JoystickHat.Hat0).IsDown));
- buttonGetters.Add(("POV1L", () => jState.GetHat(JoystickHat.Hat0).IsLeft));
- buttonGetters.Add(("POV1R", () => jState.GetHat(JoystickHat.Hat0).IsRight));
- buttonGetters.Add(("POV2U", () => jState.GetHat(JoystickHat.Hat1).IsUp));
- buttonGetters.Add(("POV2D", () => jState.GetHat(JoystickHat.Hat1).IsDown));
- buttonGetters.Add(("POV2L", () => jState.GetHat(JoystickHat.Hat1).IsLeft));
- buttonGetters.Add(("POV2R", () => jState.GetHat(JoystickHat.Hat1).IsRight));
- buttonGetters.Add(("POV3U", () => jState.GetHat(JoystickHat.Hat2).IsUp));
- buttonGetters.Add(("POV3D", () => jState.GetHat(JoystickHat.Hat2).IsDown));
- buttonGetters.Add(("POV3L", () => jState.GetHat(JoystickHat.Hat2).IsLeft));
- buttonGetters.Add(("POV3R", () => jState.GetHat(JoystickHat.Hat2).IsRight));
- buttonGetters.Add(("POV4U", () => jState.GetHat(JoystickHat.Hat3).IsUp));
- buttonGetters.Add(("POV4D", () => jState.GetHat(JoystickHat.Hat3).IsDown));
- buttonGetters.Add(("POV4L", () => jState.GetHat(JoystickHat.Hat3).IsLeft));
- buttonGetters.Add(("POV4R", () => jState.GetHat(JoystickHat.Hat3).IsRight));
- }
-
- ButtonGetters = buttonGetters;
- }
-
- public void Update()
- {
- // update both here just in case
- var tmpState = OpenTKGamePad.GetState(_deviceIndex);
- DebugState(tmpState);
- state = tmpState;
- var tmpJstate = Joystick.GetState(_deviceIndex);
- DebugState(tmpJstate);
- jState = tmpJstate;
- }
-
- [Conditional("DEBUG")]
- private void DebugState(GamePadState tmpState)
- {
- if (!tmpState.Equals(state)) Console.WriteLine($"GamePad State:\t{tmpState}");
- }
-
- [Conditional("DEBUG")]
- private void DebugState(JoystickState tmpJstate)
- {
- if (!tmpJstate.Equals(jState)) Console.WriteLine($"Joystick State:\t{tmpJstate}");
- }
-
- public IReadOnlyCollection<(string AxisID, int Value)> GetAxes()
- {
- // The host input stack appears to require values -10000.0..10000.0 rather than the -1.0..1.0 that OpenTK returns (although even then the results may be slightly outside of these bounds)
- static int ConstrainFloatInput(float num) => num switch
- {
- > 1 => 10000,
- < -1 => -10000,
- _ => (int) (num * 10000.0f)
- };
-
- if (MappedGamePad)
- {
- // automapping identified - use OpenTKGamePad
- return new[]
- {
- ("LeftThumbX", ConstrainFloatInput(state.ThumbSticks.Left.X)),
- ("LeftThumbY", ConstrainFloatInput(state.ThumbSticks.Left.Y)),
- ("RightThumbX", ConstrainFloatInput(state.ThumbSticks.Right.X)),
- ("RightThumbY", ConstrainFloatInput(state.ThumbSticks.Right.Y)),
- ("LeftTrigger", ConstrainFloatInput(state.Triggers.Left)),
- ("RightTrigger", ConstrainFloatInput(state.Triggers.Right)),
- };
- }
-
- // else use Joystick
- List<(string AxisID, int Value)> values = new()
- {
- ("X", ConstrainFloatInput(jState.GetAxis(0))),
- ("Y", ConstrainFloatInput(jState.GetAxis(1))),
- ("Z", ConstrainFloatInput(jState.GetAxis(2))),
- ("W", ConstrainFloatInput(jState.GetAxis(3))),
- ("V", ConstrainFloatInput(jState.GetAxis(4))),
- ("S", ConstrainFloatInput(jState.GetAxis(5))),
- ("Q", ConstrainFloatInput(jState.GetAxis(6))),
- ("P", ConstrainFloatInput(jState.GetAxis(7))),
- ("N", ConstrainFloatInput(jState.GetAxis(8))),
- };
-
- for (var i = 9; i < 64; i++)
- {
- var j = i;
- values.Add(($"Axis{j.ToString()}", ConstrainFloatInput(jState.GetAxis(j))));
- }
-
- return values;
- }
-
- /// Contains name and delegate function for all buttons, hats and axis
- public readonly IReadOnlyCollection<(string ButtonName, Func GetIsPressed)> ButtonGetters;
-
- /// and are in 0..
- public void SetVibration(int left, int right)
- {
- const double SCALE = 1.0 / int.MaxValue;
- static float Conv(int i) => (float) (i * SCALE);
- OpenTKGamePad.SetVibration(_deviceIndex, Conv(left), Conv(right));
- }
- }
-}
-
diff --git a/src/BizHawk.Bizware.OpenTK3/OTK_Keyboard.cs b/src/BizHawk.Bizware.OpenTK3/OTK_Keyboard.cs
deleted file mode 100644
index a50947f2e94..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/OTK_Keyboard.cs
+++ /dev/null
@@ -1,193 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-
-using BizHawk.Client.Common;
-
-using OpenTK.Input;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- public static class OTK_Keyboard
- {
- private static readonly IReadOnlyList KeyEnumMap = new List
- {
- DistinctKey.Unknown, // Unknown
- DistinctKey.LeftShift,
- DistinctKey.RightShift,
- DistinctKey.LeftCtrl,
- DistinctKey.RightCtrl,
- DistinctKey.LeftAlt,
- DistinctKey.RightAlt,
- DistinctKey.LWin,
- DistinctKey.RWin,
- DistinctKey.Apps,
- DistinctKey.F1,
- DistinctKey.F2,
- DistinctKey.F3,
- DistinctKey.F4,
- DistinctKey.F5,
- DistinctKey.F6,
- DistinctKey.F7,
- DistinctKey.F8,
- DistinctKey.F9,
- DistinctKey.F10,
- DistinctKey.F11,
- DistinctKey.F12,
- DistinctKey.F13,
- DistinctKey.F14,
- DistinctKey.F15,
- DistinctKey.F16,
- DistinctKey.F17,
- DistinctKey.F18,
- DistinctKey.F19,
- DistinctKey.F20,
- DistinctKey.F21,
- DistinctKey.F22,
- DistinctKey.F23,
- DistinctKey.F24,
- DistinctKey.Unknown, // F25
- DistinctKey.Unknown, // F26
- DistinctKey.Unknown, // F27
- DistinctKey.Unknown, // F28
- DistinctKey.Unknown, // F29
- DistinctKey.Unknown, // F30
- DistinctKey.Unknown, // F31
- DistinctKey.Unknown, // F32
- DistinctKey.Unknown, // F33
- DistinctKey.Unknown, // F34
- DistinctKey.Unknown, // F35
- DistinctKey.Up,
- DistinctKey.Down,
- DistinctKey.Left,
- DistinctKey.Right,
- DistinctKey.Return,
- DistinctKey.Escape,
- DistinctKey.Space,
- DistinctKey.Tab,
- DistinctKey.Back,
- DistinctKey.Insert,
- DistinctKey.Delete,
- DistinctKey.PageUp,
- DistinctKey.PageDown,
- DistinctKey.Home,
- DistinctKey.End,
- DistinctKey.CapsLock,
- DistinctKey.Scroll, // ScrollLock; my Scroll Lock key is only recognised by OpenTK as Pause tho --yoshi
- DistinctKey.PrintScreen,
- DistinctKey.Pause,
- DistinctKey.NumLock,
- DistinctKey.Clear,
- DistinctKey.Sleep,
- DistinctKey.NumPad0,
- DistinctKey.NumPad1,
- DistinctKey.NumPad2,
- DistinctKey.NumPad3,
- DistinctKey.NumPad4,
- DistinctKey.NumPad5,
- DistinctKey.NumPad6,
- DistinctKey.NumPad7,
- DistinctKey.NumPad8,
- DistinctKey.NumPad9,
- DistinctKey.Divide,
- DistinctKey.Multiply,
- DistinctKey.Subtract,
- DistinctKey.Add,
- DistinctKey.Decimal,
- DistinctKey.NumPadEnter,
- DistinctKey.A,
- DistinctKey.B,
- DistinctKey.C,
- DistinctKey.D,
- DistinctKey.E,
- DistinctKey.F,
- DistinctKey.G,
- DistinctKey.H,
- DistinctKey.I,
- DistinctKey.J,
- DistinctKey.K,
- DistinctKey.L,
- DistinctKey.M,
- DistinctKey.N,
- DistinctKey.O,
- DistinctKey.P,
- DistinctKey.Q,
- DistinctKey.R,
- DistinctKey.S,
- DistinctKey.T,
- DistinctKey.U,
- DistinctKey.V,
- DistinctKey.W,
- DistinctKey.X,
- DistinctKey.Y,
- DistinctKey.Z,
- DistinctKey.D0,
- DistinctKey.D1,
- DistinctKey.D2,
- DistinctKey.D3,
- DistinctKey.D4,
- DistinctKey.D5,
- DistinctKey.D6,
- DistinctKey.D7,
- DistinctKey.D8,
- DistinctKey.D9,
- DistinctKey.OemTilde,
- DistinctKey.OemMinus,
- DistinctKey.OemPlus,
- DistinctKey.OemOpenBrackets,
- DistinctKey.OemCloseBrackets,
- DistinctKey.OemSemicolon,
- DistinctKey.OemQuotes,
- DistinctKey.OemComma,
- DistinctKey.OemPeriod,
- DistinctKey.OemQuestion, // Slash
- DistinctKey.OemPipe, // BackSlash
- DistinctKey.OemBackslash, // NonUSBackSlash
- };
-
- private static KeyboardState _kbState;
-
- public static void Initialize ()
- {
- _kbState = Keyboard.GetState();
- }
-
- public static IEnumerable Update()
- {
- KeyboardState newState;
- try
- {
- newState = Keyboard.GetState();
- }
- catch
- {
- // OpenTK's keyboard class isn't thread safe.
- // In rare cases (sometimes it takes up to 10 minutes to occur) it will
- // be updating the keyboard state when we call GetState() and choke.
- // Until I fix OpenTK, it's fine to just swallow it because input continues working.
- if (Debugger.IsAttached) Console.WriteLine("OpenTK Keyboard thread is angry.");
- return Enumerable.Empty();
- }
-
- var lastState = _kbState;
- _kbState = newState;
- if (lastState == _kbState) return Enumerable.Empty();
-
- var eventList = new List();
- for (var i = 1; i < 131; i++)
- {
- var key = (Key) i;
- if (lastState.IsKeyUp(key) && _kbState.IsKeyDown(key))
- {
- eventList.Add(new KeyEvent(KeyEnumMap[i], pressed: true));
- }
- else if (lastState.IsKeyDown(key) && _kbState.IsKeyUp(key))
- {
- eventList.Add(new KeyEvent(KeyEnumMap[i], pressed: false));
- }
- }
- return eventList;
- }
- }
-}
diff --git a/src/BizHawk.Bizware.OpenTK3/OpenALSoundOutput.cs b/src/BizHawk.Bizware.OpenTK3/OpenALSoundOutput.cs
deleted file mode 100644
index ee93abb514a..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/OpenALSoundOutput.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-using BizHawk.Client.Common;
-
-using OpenTK.Audio;
-using OpenTK.Audio.OpenAL;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- public class OpenALSoundOutput : ISoundOutput
- {
- private bool _disposed;
- private readonly IHostAudioManager _sound;
- private AudioContext _context;
- private int _sourceID;
- private BufferPool _bufferPool;
- private int _currentSamplesQueued;
- private short[] _tempSampleBuffer;
-
- public OpenALSoundOutput(IHostAudioManager sound, string chosenDeviceName)
- {
- _sound = sound;
- _context = new AudioContext(
- GetDeviceNames().Contains(chosenDeviceName) ? chosenDeviceName : null,
- _sound.SampleRate
- );
- }
-
- public void Dispose()
- {
- if (_disposed) return;
-
- _context.Dispose();
- _context = null;
-
- _disposed = true;
- }
-
- public static IEnumerable GetDeviceNames()
- {
- return !Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT")
- ? Enumerable.Empty()
- : Alc.GetString(IntPtr.Zero, AlcGetStringList.AllDevicesSpecifier);
- }
-
- private int BufferSizeSamples { get; set; }
-
- public int MaxSamplesDeficit { get; private set; }
-
- public void ApplyVolumeSettings(double volume)
- {
- AL.Source(_sourceID, ALSourcef.Gain, (float)volume);
- }
-
- public void StartSound()
- {
- BufferSizeSamples = _sound.MillisecondsToSamples(_sound.ConfigBufferSizeMs);
- MaxSamplesDeficit = BufferSizeSamples;
-
- _sourceID = AL.GenSource();
-
- _bufferPool = new BufferPool();
- _currentSamplesQueued = 0;
- }
-
- public void StopSound()
- {
- AL.SourceStop(_sourceID);
-
- AL.DeleteSource(_sourceID);
-
- _bufferPool.Dispose();
- _bufferPool = null;
-
- BufferSizeSamples = 0;
- }
-
- public int CalculateSamplesNeeded()
- {
- int currentSamplesPlayed = GetSource(ALGetSourcei.SampleOffset);
- ALSourceState sourceState = AL.GetSourceState(_sourceID);
- bool isInitializing = sourceState == ALSourceState.Initial;
- bool detectedUnderrun = sourceState == ALSourceState.Stopped;
- if (detectedUnderrun)
- {
- // SampleOffset should reset to 0 when stopped; update the queued sample count to match
- UnqueueProcessedBuffers();
- currentSamplesPlayed = 0;
- }
- int samplesAwaitingPlayback = _currentSamplesQueued - currentSamplesPlayed;
- int samplesNeeded = Math.Max(BufferSizeSamples - samplesAwaitingPlayback, 0);
- if (isInitializing || detectedUnderrun)
- {
- _sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded);
- }
- return samplesNeeded;
- }
-
- public void WriteSamples(short[] samples, int sampleOffset, int sampleCount)
- {
- if (sampleCount == 0) return;
- UnqueueProcessedBuffers();
- int byteCount = sampleCount * _sound.BlockAlign;
- if (sampleOffset != 0)
- {
- AllocateTempSampleBuffer(sampleCount);
- Buffer.BlockCopy(samples, sampleOffset * _sound.BlockAlign, _tempSampleBuffer, 0, byteCount);
- samples = _tempSampleBuffer;
- }
- var buffer = _bufferPool.Obtain(byteCount);
- AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, _sound.SampleRate);
- AL.SourceQueueBuffer(_sourceID, buffer.BufferID);
- _currentSamplesQueued += sampleCount;
- if (AL.GetSourceState(_sourceID) != ALSourceState.Playing)
- {
- AL.SourcePlay(_sourceID);
- }
- }
-
- private void UnqueueProcessedBuffers()
- {
- int releaseCount = GetSource(ALGetSourcei.BuffersProcessed);
- for (int i = 0; i < releaseCount; i++)
- {
- AL.SourceUnqueueBuffer(_sourceID);
- var releasedBuffer = _bufferPool.ReleaseOne();
- _currentSamplesQueued -= releasedBuffer.Length / _sound.BlockAlign;
- }
- }
-
- private int GetSource(ALGetSourcei param)
- {
- AL.GetSource(_sourceID, param, out var value);
- return value;
- }
-
- private void AllocateTempSampleBuffer(int sampleCount)
- {
- int length = sampleCount * _sound.ChannelCount;
- if (_tempSampleBuffer == null || _tempSampleBuffer.Length < length)
- {
- _tempSampleBuffer = new short[length];
- }
- }
-
- private class BufferPool : IDisposable
- {
- private readonly Stack _availableItems = new Stack();
- private readonly Queue _obtainedItems = new Queue();
-
- public void Dispose()
- {
- foreach (BufferPoolItem item in _availableItems.Concat(_obtainedItems))
- {
- AL.DeleteBuffer(item.BufferID);
- }
- _availableItems.Clear();
- _obtainedItems.Clear();
- }
-
- public BufferPoolItem Obtain(int length)
- {
- BufferPoolItem item = _availableItems.Count != 0 ? _availableItems.Pop() : new BufferPoolItem();
- item.Length = length;
- _obtainedItems.Enqueue(item);
- return item;
- }
-
- public BufferPoolItem ReleaseOne()
- {
- BufferPoolItem item = _obtainedItems.Dequeue();
- _availableItems.Push(item);
- return item;
- }
-
- public class BufferPoolItem
- {
- public int BufferID { get; private set; }
- public int Length { get; set; }
-
- public BufferPoolItem()
- {
- BufferID = AL.GenBuffer();
- }
- }
- }
- }
-}
diff --git a/src/BizHawk.Bizware.OpenTK3/OpenTKConfigurator.cs b/src/BizHawk.Bizware.OpenTK3/OpenTKConfigurator.cs
deleted file mode 100644
index a1d9738a8b4..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/OpenTKConfigurator.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using OpenTK;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- public static class OpenTKConfigurator
- {
- static OpenTKConfigurator()
- {
- // make sure OpenTK initializes without getting wrecked on the SDL check and throwing an exception to annoy our MDA's
- var toolkitOptions = ToolkitOptions.Default;
- toolkitOptions.Backend = PlatformBackend.PreferNative;
- Toolkit.Init(toolkitOptions);
- // NOTE: this throws EGL exceptions anyway. I'm going to ignore it and whine about it later
- // still seeing the exception in VS as of 2.5.3 dev... --yoshi
- }
-
- /// no-op; this class' static ctor is guaranteed to be called exactly once if this is called at least once
- public static void EnsureConfigurated() {}
- }
-}
diff --git a/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs b/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs
deleted file mode 100644
index 9692e354ba4..00000000000
--- a/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-#nullable enable
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-using BizHawk.Client.Common;
-
-namespace BizHawk.Bizware.OpenTK3
-{
- public sealed class OpenTKInputAdapter : IHostInputAdapter
- {
- private IReadOnlyDictionary _lastHapticsSnapshot = new Dictionary();
-
- public string Desc { get; } = "OpenTK 3";
-
- public void DeInitAll() {}
-
- public void FirstInitAll(IntPtr mainFormHandle)
- {
- OTK_Keyboard.Initialize();
- OTK_GamePad.Initialize();
- }
-
- public IReadOnlyDictionary> GetHapticsChannels()
- => OTK_GamePad.EnumerateDevices().ToDictionary(pad => pad.InputNamePrefix, pad => pad.HapticsChannels);
-
- public void ReInitGamepads(IntPtr mainFormHandle) {}
-
- public void PreprocessHostGamepads() => OTK_GamePad.UpdateAll();
-
- public void ProcessHostGamepads(Action handleButton, Action handleAxis)
- {
- foreach (var pad in OTK_GamePad.EnumerateDevices())
- {
- foreach (var but in pad.ButtonGetters) handleButton(pad.InputNamePrefix + but.ButtonName, but.GetIsPressed(), ClientInputFocus.Pad);
- foreach (var (axisID, f) in pad.GetAxes()) handleAxis($"{pad.InputNamePrefix}{axisID} Axis", f);
-#if DEBUG // effectively no-op as OpenTK 3 doesn't seem to actually support haptic feedback
- foreach (var channel in pad.HapticsChannels)
- {
- if (!_lastHapticsSnapshot.TryGetValue(pad.InputNamePrefix + channel, out var strength))
- {
- pad.SetVibration(0, 0);
- continue;
- }
- switch (channel)
- {
- case "Mono":
- pad.SetVibration(strength, strength);
- break;
- case "Left": // presence of left channel implies presence of right channel, so we'll use it here...
- pad.SetVibration(strength, _lastHapticsSnapshot[pad.InputNamePrefix + "Right"]);
- break;
- case "Right": // ...and ignore it here
- break;
- default:
- Console.WriteLine(nameof(OTK_GamePad) + " has a new kind of haptic channel? (Dev forgot to update this file too?)");
- break;
- }
- }
-#endif
- }
- }
-
- public IEnumerable ProcessHostKeyboards() => OTK_Keyboard.Update();
-
- public void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot)
-#if DEBUG // effectively no-op as OpenTK 3 doesn't seem to actually support haptic feedback
- => _lastHapticsSnapshot = hapticsSnapshot.ToDictionary(tuple => tuple.Name, tuple => tuple.Strength);
-#else
- {}
-#endif
-
- public void UpdateConfig(Config config) {}
- }
-}
diff --git a/src/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj b/src/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj
index defe150a91c..4af484e8fa5 100644
--- a/src/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj
+++ b/src/BizHawk.Bizware.Test/BizHawk.Bizware.Test.csproj
@@ -9,7 +9,6 @@
-
diff --git a/src/BizHawk.Bizware.Test/Program.cs b/src/BizHawk.Bizware.Test/Program.cs
index 24453a4a618..d71f56a8cfd 100644
--- a/src/BizHawk.Bizware.Test/Program.cs
+++ b/src/BizHawk.Bizware.Test/Program.cs
@@ -7,7 +7,7 @@
using System.Windows.Forms;
using BizHawk.Bizware.BizwareGL;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Graphics;
using BizHawk.Client.EmuHawk;
namespace BizHawk.Bizware.Test
@@ -46,7 +46,7 @@ public TestForm()
private static void RunTest()
{
- IGL igl = new IGL_TK(2, 0, false);
+ IGL igl = new IGL_OpenGL(2, 0, false);
ArtManager am = new(igl);
var testArts = typeof(Program).Assembly.GetManifestResourceNames().Where(s => s.Contains("flame"))
.Select(s => am.LoadArt(ReflectionCache.EmbeddedResourceStream(s.Substring(21)))) // ReflectionCache adds back the prefix
diff --git a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs
index 00596b99477..21a69d9f4a9 100644
--- a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs
+++ b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs
@@ -87,7 +87,7 @@ public DisplayManagerBase(
LoadCustomFont(fceux);
}
- if (dispMethod == EDispMethod.OpenGL || dispMethod == EDispMethod.SlimDX9)
+ if (dispMethod == EDispMethod.OpenGL || dispMethod == EDispMethod.D3D9)
{
var fiHq2x = new FileInfo(Path.Combine(PathUtils.ExeDirectoryPath, "Shaders/BizHawk/hq2x.cgp"));
if (fiHq2x.Exists)
@@ -101,7 +101,7 @@ public DisplayManagerBase(
using var stream = fiScanlines.OpenRead();
_shaderChainScanlines = new RetroShaderChain(_gl, new RetroShaderPreset(stream), Path.Combine(PathUtils.ExeDirectoryPath, "Shaders/BizHawk"));
}
- var bicubicPath = dispMethod == EDispMethod.SlimDX9 ? "Shaders/BizHawk/bicubic-normal.cgp" : "Shaders/BizHawk/bicubic-fast.cgp";
+ var bicubicPath = dispMethod == EDispMethod.D3D9 ? "Shaders/BizHawk/bicubic-normal.cgp" : "Shaders/BizHawk/bicubic-fast.cgp";
var fiBicubic = new FileInfo(Path.Combine(PathUtils.ExeDirectoryPath, bicubicPath));
if (fiBicubic.Exists)
{
@@ -772,8 +772,10 @@ protected class JobInfo
private FilterProgram UpdateSourceInternal(JobInfo job)
{
//no drawing actually happens. it's important not to begin drawing on a control
- if (!job.Simulate && !job.Offscreen)
+ if (!job.Simulate/* && !job.Offscreen*/)
{
+ // i don't want to do this for offscreen jobs
+ // but it seems that would be required in case this context isn't active
ActivateGLContext();
if (job.ChainOutsize.Width == 0 || job.ChainOutsize.Height == 0)
@@ -788,21 +790,21 @@ private FilterProgram UpdateSourceInternal(JobInfo job)
}
}
- IVideoProvider videoProvider = job.VideoProvider;
- bool simulate = job.Simulate;
- Size chainOutsize = job.ChainOutsize;
+ var videoProvider = job.VideoProvider;
+ var simulate = job.Simulate;
+ var chainOutsize = job.ChainOutsize;
//simulate = true;
- int[] videoBuffer = videoProvider.GetVideoBuffer();
- int bufferWidth = videoProvider.BufferWidth;
- int bufferHeight = videoProvider.BufferHeight;
- int presenterTextureWidth = bufferWidth;
- int presenterTextureHeight = bufferHeight;
- bool isGlTextureId = videoBuffer.Length == 1;
+ var videoBuffer = videoProvider.GetVideoBuffer();
+ var bufferWidth = videoProvider.BufferWidth;
+ var bufferHeight = videoProvider.BufferHeight;
+ var presenterTextureWidth = bufferWidth;
+ var presenterTextureHeight = bufferHeight;
+ var isGlTextureId = videoBuffer.Length == 1;
- int vw = videoProvider.VirtualWidth;
- int vh = videoProvider.VirtualHeight;
+ var vw = videoProvider.VirtualWidth;
+ var vh = videoProvider.VirtualHeight;
//TODO: it is bad that this is happening outside the filter chain
//the filter chain has the ability to add padding...
@@ -811,7 +813,7 @@ private FilterProgram UpdateSourceInternal(JobInfo job)
var fCoreScreenControl = CreateCoreScreenControl();
if(fCoreScreenControl != null)
{
- var sz = fCoreScreenControl.PresizeInput("default", new Size(bufferWidth, bufferHeight));
+ var sz = fCoreScreenControl.PresizeInput("default", new(bufferWidth, bufferHeight));
presenterTextureWidth = vw = sz.Width;
presenterTextureHeight = vh = sz.Height;
}
@@ -854,21 +856,21 @@ private FilterProgram UpdateSourceInternal(JobInfo job)
{
if (isGlTextureId)
{
- //FYI: this is a million years from happening on n64, since it's all geriatric non-FBO code
- //is it workable for saturn?
- videoTexture = _gl.WrapGLTexture2d(new IntPtr(videoBuffer[0]), bufferWidth, bufferHeight);
+ // FYI: this is a million years from happening on n64, since it's all geriatric non-FBO code
+ // is it workable for saturn?
+ videoTexture = _gl.WrapGLTexture2d(new(videoBuffer[0]), bufferWidth, bufferHeight);
}
else
{
- //wrap the VideoProvider data in a BitmapBuffer (no point to refactoring that many IVideoProviders)
- bb = new BitmapBuffer(bufferWidth, bufferHeight, videoBuffer);
+ // wrap the VideoProvider data in a BitmapBuffer (no point to refactoring that many IVideoProviders)
+ bb = new(bufferWidth, bufferHeight, videoBuffer);
bb.DiscardAlpha();
//now, acquire the data sent from the videoProvider into a texture
videoTexture = _videoTextureFrugalizer.Get(bb);
// lets not use this. lets define BizwareGL to make clamp by default (TBD: check opengl)
- //GL.SetTextureWrapMode(videoTexture, true);
+ // GL.SetTextureWrapMode(videoTexture, true);
}
}
@@ -877,22 +879,22 @@ private FilterProgram UpdateSourceInternal(JobInfo job)
_currEmuHeight = bufferHeight;
//build the default filter chain and set it up with services filters will need
- Size chainInsize = new Size(bufferWidth, bufferHeight);
+ var chainInsize = new Size(bufferWidth, bufferHeight);
var filterProgram = BuildDefaultChain(chainInsize, chainOutsize, job.IncludeOSD, job.IncludeUserFilters);
filterProgram.GuiRenderer = _renderer;
filterProgram.GL = _gl;
//setup the source image filter
- SourceImage fInput = filterProgram["input"] as SourceImage;
+ var fInput = (SourceImage)filterProgram["input"];
fInput.Texture = videoTexture;
//setup the final presentation filter
- FinalPresentation fPresent = filterProgram["presentation"] as FinalPresentation;
+ var fPresent = (FinalPresentation)filterProgram["presentation"];
if (fPresent != null)
{
- fPresent.VirtualTextureSize = new Size(vw, vh);
- fPresent.TextureSize = new Size(presenterTextureWidth, presenterTextureHeight);
+ fPresent.VirtualTextureSize = new(vw, vh);
+ fPresent.TextureSize = new(presenterTextureWidth, presenterTextureHeight);
fPresent.BackgroundColor = videoProvider.BackgroundColor;
fPresent.GuiRenderer = _renderer;
fPresent.Flip = isGlTextureId;
diff --git a/src/BizHawk.Client.Common/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs
index cec408a7d6c..f47db66e65c 100644
--- a/src/BizHawk.Client.Common/config/Config.cs
+++ b/src/BizHawk.Client.Common/config/Config.cs
@@ -214,7 +214,7 @@ public void ResolveDefaults()
public int DispPrescale { get; set; } = 1;
- public EDispMethod DispMethod { get; set; } = HostCapabilityDetector.HasDirectX ? EDispMethod.SlimDX9 : EDispMethod.OpenGL;
+ public EDispMethod DispMethod { get; set; } = HostCapabilityDetector.HasDirectX ? EDispMethod.D3D9 : EDispMethod.OpenGL;
public int DispChromeFrameWindowed { get; set; } = 2;
public bool DispChromeStatusBarWindowed { get; set; } = true;
@@ -342,7 +342,7 @@ public void ResolveDefaults()
// ReSharper disable once UnusedMember.Global
public string LastWrittenFromDetailed { get; set; } = VersionInfo.GetEmuVersion();
- public EHostInputMethod HostInputMethod { get; set; } = HostCapabilityDetector.HasDirectX ? EHostInputMethod.DirectInput : EHostInputMethod.OpenTK;
+ public EHostInputMethod HostInputMethod { get; set; } = HostCapabilityDetector.HasDirectX ? EHostInputMethod.DirectInput : EHostInputMethod.SDL2;
public bool UseStaticWindowTitles { get; set; }
diff --git a/src/BizHawk.Client.Common/config/ConfigEnums.cs b/src/BizHawk.Client.Common/config/ConfigEnums.cs
index 0f477a0be87..d46bbfe1d56 100644
--- a/src/BizHawk.Client.Common/config/ConfigEnums.cs
+++ b/src/BizHawk.Client.Common/config/ConfigEnums.cs
@@ -29,7 +29,7 @@ public enum ClientProfile
public enum EHostInputMethod
{
- OpenTK = 0,
+ SDL2 = 0,
DirectInput = 1
}
diff --git a/src/BizHawk.Client.Common/input/HostInputAdapter.cs b/src/BizHawk.Client.Common/input/HostInputAdapter.cs
index f58e21b2886..a9c884d0805 100644
--- a/src/BizHawk.Client.Common/input/HostInputAdapter.cs
+++ b/src/BizHawk.Client.Common/input/HostInputAdapter.cs
@@ -6,6 +6,8 @@
namespace BizHawk.Client.Common
{
/// this was easier than trying to make static classes instantiable...
+ /// TODO: Reconsider if we want to hand over the main form handle
+ /// This is only used in DirectInput, and it would work just as fine if a hidden window was created internally in its place
public interface IHostInputAdapter
{
string Desc { get; }
diff --git a/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs b/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs
index aede5485b6d..3eed6c02135 100644
--- a/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs
+++ b/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
diff --git a/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj
index ecb0fe64647..e44c6ae5c2b 100755
--- a/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj
+++ b/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj
@@ -20,7 +20,9 @@
-
+
+
+
diff --git a/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs b/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs
index 3c395aeeb0f..d2516549bdb 100644
--- a/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs
+++ b/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs
@@ -40,7 +40,7 @@ public DisplayManager(
// setup the GL context manager, needed for coping with multiple opengl cores vs opengl display method
// but is it tho? --yoshi
// turns out it was, calling Instance getter here initialises it, and the encapsulated Activate call is necessary too --yoshi
- _crGraphicsControl = GLManager.Instance.GetContextForGraphicsControl(_graphicsControl);
+ _crGraphicsControl = GLManager.GetContextForGraphicsControl(_graphicsControl);
}
protected override void ActivateGLContext() => GLManager.Instance.Activate(_crGraphicsControl);
@@ -71,7 +71,7 @@ protected override void UpdateSourceDrawingWork(JobInfo job)
vsync = false;
//for now, it's assumed that the presentation panel is the main window, but that may not always be true
- if (vsync && GlobalConfig.DispAlternateVsync && GlobalConfig.VSyncThrottle && _gl.DispMethodEnum is EDispMethod.SlimDX9)
+ if (vsync && GlobalConfig.DispAlternateVsync && GlobalConfig.VSyncThrottle && _gl.DispMethodEnum is EDispMethod.D3D9)
{
alternateVsync = true;
//unset normal vsync if we've chosen the alternate vsync
diff --git a/src/BizHawk.Client.EmuHawk/GLManager.cs b/src/BizHawk.Client.EmuHawk/GLManager.cs
index bcbe5470021..712b65b36df 100644
--- a/src/BizHawk.Client.EmuHawk/GLManager.cs
+++ b/src/BizHawk.Client.EmuHawk/GLManager.cs
@@ -1,7 +1,6 @@
using System;
using BizHawk.Bizware.BizwareGL;
-using BizHawk.Bizware.DirectX;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Graphics;
namespace BizHawk.Client.EmuHawk
{
@@ -18,7 +17,7 @@ public void Dispose()
{
}
- private static readonly Lazy _lazyInstance = new Lazy(() => new GLManager());
+ private static readonly Lazy _lazyInstance = new(() => new());
public static GLManager Instance => _lazyInstance.Value;
@@ -30,14 +29,13 @@ public void ReleaseGLContext(object o)
public ContextRef CreateGLContext(int majorVersion, int minorVersion, bool forwardCompatible)
{
- var gl = new IGL_TK(majorVersion, minorVersion, forwardCompatible);
- var ret = new ContextRef { GL = gl };
- return ret;
+ var gl = new IGL_OpenGL(majorVersion, minorVersion, forwardCompatible);
+ return new() { GL = gl };
}
- public ContextRef GetContextForGraphicsControl(GraphicsControl gc)
+ public static ContextRef GetContextForGraphicsControl(GraphicsControl gc)
{
- return new ContextRef
+ return new()
{
Gc = gc,
GL = gc.IGL
@@ -53,34 +51,28 @@ public void Invalidate()
public void Activate(ContextRef cr)
{
- bool begun = false;
-
- //this needs a begin signal to set the swap chain to the next backbuffer
- if (cr.GL.DispMethodEnum is EDispMethod.SlimDX9)
- {
- cr.Gc.Begin();
- begun = true;
- }
-
if (cr == _activeContext)
{
+ // D3D9 needs a begin signal to set the swap chain to the next backbuffer
+ if (cr.GL.DispMethodEnum is EDispMethod.D3D9)
+ {
+ cr.Gc.Begin();
+ }
+
return;
}
_activeContext = cr;
+
if (cr.Gc != null)
{
- //TODO - this is checking the current context inside to avoid an extra NOP context change. make this optional or remove it, since we're tracking it here
- if (!begun)
- {
- cr.Gc.Begin();
- }
+ cr.Gc.Begin();
}
else
{
- if (cr.GL is IGL_TK tk)
+ if (cr.GL is IGL_OpenGL gl)
{
- tk.MakeDefaultCurrent();
+ gl.MakeOffscreenContextCurrent();
}
}
}
diff --git a/src/BizHawk.Client.EmuHawk/GraphicsImplementations/GraphicsControl.cs b/src/BizHawk.Client.EmuHawk/GraphicsImplementations/GraphicsControl.cs
index effc3a96c85..482894ca2f1 100644
--- a/src/BizHawk.Client.EmuHawk/GraphicsImplementations/GraphicsControl.cs
+++ b/src/BizHawk.Client.EmuHawk/GraphicsImplementations/GraphicsControl.cs
@@ -19,21 +19,18 @@ public GraphicsControl(IGL owner)
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserMouse, true);
- //in case we need it
- //GLControl.GetType().GetMethod("SetStyle", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(GLControl, new object[] { System.Windows.Forms.ControlStyles.UserMouse, true });
-
_igc = owner.Internal_CreateGraphicsControl();
- _managed = _igc as Control;
+ _managed = (Control)_igc;
_managed.Dock = DockStyle.Fill;
Controls.Add(_managed);
// pass through these events to the form. I tried really hard to find a better way, but there is none.
// (don't use HTTRANSPARENT, it isn't portable, I would assume)
- _managed.MouseDoubleClick += (sender, e) => OnMouseDoubleClick(e);
- _managed.MouseClick += (sender, e) => OnMouseClick(e);
- _managed.MouseEnter += (sender, e) => OnMouseEnter(e);
- _managed.MouseLeave += (sender, e) => OnMouseLeave(e);
- _managed.MouseMove += (sender, e) => OnMouseMove(e);
+ _managed.MouseDoubleClick += (_, e) => OnMouseDoubleClick(e);
+ _managed.MouseClick += (_, e) => OnMouseClick(e);
+ _managed.MouseEnter += (_, e) => OnMouseEnter(e);
+ _managed.MouseLeave += (_, e) => OnMouseLeave(e);
+ _managed.MouseMove += (_, e) => OnMouseMove(e);
//the GraphicsControl is occupying all of our area. So we pretty much never get paint events ourselves.
//So lets capture its paint event and use it for ourselves (it doesn't know how to do anything, anyway)
diff --git a/src/BizHawk.Client.EmuHawk/GraphicsImplementations/IGLExtensions.cs b/src/BizHawk.Client.EmuHawk/GraphicsImplementations/IGLExtensions.cs
index 44b8f008329..f3885501b45 100644
--- a/src/BizHawk.Client.EmuHawk/GraphicsImplementations/IGLExtensions.cs
+++ b/src/BizHawk.Client.EmuHawk/GraphicsImplementations/IGLExtensions.cs
@@ -1,5 +1,5 @@
using BizHawk.Bizware.BizwareGL;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Graphics;
namespace BizHawk.Client.EmuHawk
{
diff --git a/src/BizHawk.Client.EmuHawk/GraphicsImplementations/RetainedGraphicsControl.cs b/src/BizHawk.Client.EmuHawk/GraphicsImplementations/RetainedGraphicsControl.cs
index 646c56e5daa..cf3256510aa 100644
--- a/src/BizHawk.Client.EmuHawk/GraphicsImplementations/RetainedGraphicsControl.cs
+++ b/src/BizHawk.Client.EmuHawk/GraphicsImplementations/RetainedGraphicsControl.cs
@@ -1,6 +1,6 @@
using System.Windows.Forms;
using BizHawk.Bizware.BizwareGL;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Graphics;
namespace BizHawk.Client.EmuHawk
{
diff --git a/src/BizHawk.Client.EmuHawk/Input/Input.cs b/src/BizHawk.Client.EmuHawk/Input/Input.cs
index 51092cc7663..4574725ab91 100644
--- a/src/BizHawk.Client.EmuHawk/Input/Input.cs
+++ b/src/BizHawk.Client.EmuHawk/Input/Input.cs
@@ -4,8 +4,7 @@
using System.Threading;
using System.Windows.Forms;
-using BizHawk.Bizware.DirectX;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Input;
using BizHawk.Common;
using BizHawk.Client.Common;
using BizHawk.Common.CollectionExtensions;
@@ -48,8 +47,8 @@ internal Input(IntPtr mainFormHandle, Func getConfigCallback, Func new OpenTKInputAdapter(),
- _ when OSTailoredCode.IsUnixHost => new OpenTKInputAdapter(),
+ EHostInputMethod.SDL2 => new SDL2InputAdapter(),
+ _ when OSTailoredCode.IsUnixHost => new SDL2InputAdapter(),
EHostInputMethod.DirectInput => new DirectInputAdapter(),
_ => throw new InvalidOperationException()
};
diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs
index 60701733911..9138bb79671 100644
--- a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs
+++ b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs
@@ -6,8 +6,7 @@
using System.Linq;
using System.Windows.Forms;
-using BizHawk.Bizware.DirectX;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Audio;
using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk.CustomControls;
using BizHawk.Client.EmuHawk.ToolExtensions;
@@ -914,8 +913,8 @@ private void SoundMenuItem_Click(object sender, EventArgs e)
{
static IEnumerable GetDeviceNamesCallback(ESoundOutputMethod outputMethod) => outputMethod switch
{
- ESoundOutputMethod.DirectSound => IndirectX.GetDSSinkNames(),
- ESoundOutputMethod.XAudio2 => IndirectX.GetXAudio2SinkNames(),
+ ESoundOutputMethod.DirectSound => DirectSoundSoundOutput.GetDeviceNames(),
+ ESoundOutputMethod.XAudio2 => XAudio2SoundOutput.GetDeviceNames(),
ESoundOutputMethod.OpenAL => OpenALSoundOutput.GetDeviceNames(),
_ => Enumerable.Empty()
};
diff --git a/src/BizHawk.Client.EmuHawk/Program.cs b/src/BizHawk.Client.EmuHawk/Program.cs
index aa01e56fc3f..9ac125af41f 100644
--- a/src/BizHawk.Client.EmuHawk/Program.cs
+++ b/src/BizHawk.Client.EmuHawk/Program.cs
@@ -8,8 +8,7 @@
using System.Windows.Forms;
using BizHawk.Bizware.BizwareGL;
-using BizHawk.Bizware.DirectX;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Graphics;
using BizHawk.Common;
using BizHawk.Common.PathExtensions;
using BizHawk.Client.Common;
@@ -115,8 +114,6 @@ private static int SubMain(string[] args)
{
BizInvoke.ReflectionCache.AsmVersion,
Bizware.BizwareGL.ReflectionCache.AsmVersion,
- Bizware.DirectX.ReflectionCache.AsmVersion,
- Bizware.OpenTK3.ReflectionCache.AsmVersion,
Client.Common.ReflectionCache.AsmVersion,
Common.ReflectionCache.AsmVersion,
Emulation.Common.ReflectionCache.AsmVersion,
@@ -179,8 +176,24 @@ private static int SubMain(string[] args)
StringLogUtil.DefaultToDisk = initialConfig.Movies.MoviesOnDisk;
+ var glInitCount = 0;
+
IGL TryInitIGL(EDispMethod dispMethod)
{
+ glInitCount++;
+
+ (EDispMethod Method, string Name) ChooseFallback()
+ => glInitCount switch
+ {
+ // try to fallback on the faster option on Windows
+ // if we're on a Unix platform, there's only 1 fallback here...
+ 1 when OSTC.IsUnixHost => (EDispMethod.GdiPlus, "GDI+"),
+ 1 or 2 when !OSTC.IsUnixHost => dispMethod == EDispMethod.D3D9
+ ? (EDispMethod.OpenGL, "OpenGL")
+ : (EDispMethod.D3D9, "Direct3D9"),
+ _ => (EDispMethod.GdiPlus, "GDI+")
+ };
+
IGL CheckRenderer(IGL gl)
{
try
@@ -189,39 +202,45 @@ IGL CheckRenderer(IGL gl)
}
catch (Exception ex)
{
- new ExceptionBox(new Exception("Initialization of Display Method failed; falling back to GDI+", ex)).ShowDialog();
- return TryInitIGL(initialConfig.DispMethod = EDispMethod.GdiPlus);
+ var fallback = ChooseFallback();
+ new ExceptionBox(new Exception($"Initialization of Display Method failed; falling back to {fallback.Name}", ex)).ShowDialog();
+ return TryInitIGL(initialConfig.DispMethod = fallback.Method);
}
}
+
switch (dispMethod)
{
- case EDispMethod.SlimDX9:
- if (OSTC.CurrentOS != OSTC.DistinctOS.Windows)
+ case EDispMethod.D3D9:
+ if (OSTC.IsUnixHost)
{
// possibly sharing config w/ Windows, assume the user wants the not-slow method (but don't change the config)
return TryInitIGL(EDispMethod.OpenGL);
}
try
{
- return CheckRenderer(IndirectX.CreateD3DGLImpl());
+ return CheckRenderer(new IGL_D3D9());
}
catch (Exception ex)
{
- new ExceptionBox(new Exception("Initialization of Direct3d 9 Display Method failed; falling back to GDI+", ex)).ShowDialog();
- return TryInitIGL(initialConfig.DispMethod = EDispMethod.GdiPlus);
+ var fallback = ChooseFallback();
+ new ExceptionBox(new Exception($"Initialization of Direct3D9 Display Method failed; falling back to {fallback.Name}", ex)).ShowDialog();
+ return TryInitIGL(initialConfig.DispMethod = fallback.Method);
}
case EDispMethod.OpenGL:
- var glOpenTK = new IGL_TK(2, 0, false);
- if (glOpenTK.Version < 200)
+ var glOpenGL = new IGL_OpenGL(2, 0, false);
+ if (glOpenGL.Version < 200)
{
// too old to use, GDI+ will be better
- ((IDisposable) glOpenTK).Dispose();
- return TryInitIGL(initialConfig.DispMethod = EDispMethod.GdiPlus);
+ glOpenGL.Dispose();
+ var fallback = ChooseFallback();
+ new ExceptionBox(new Exception($"Initialization of OpenGL Display Method failed; falling back to {fallback.Name}")).ShowDialog();
+ return TryInitIGL(initialConfig.DispMethod = fallback.Method);
}
- return CheckRenderer(glOpenTK);
+ return CheckRenderer(glOpenGL);
default:
case EDispMethod.GdiPlus:
static GLControlWrapper_GdiPlus CreateGLControlWrapper(IGL_GdiPlus self) => new(self); // inlining as lambda causes crash, don't wanna know why --yoshi
+ // if this fails, we're screwed
return new IGL_GdiPlus(CreateGLControlWrapper);
}
}
diff --git a/src/BizHawk.Client.EmuHawk/Sound/Sound.cs b/src/BizHawk.Client.EmuHawk/Sound/Sound.cs
index 483f540a49c..20a773d41c1 100644
--- a/src/BizHawk.Client.EmuHawk/Sound/Sound.cs
+++ b/src/BizHawk.Client.EmuHawk/Sound/Sound.cs
@@ -1,8 +1,7 @@
using System;
using System.Threading;
-using BizHawk.Bizware.DirectX;
-using BizHawk.Bizware.OpenTK3;
+using BizHawk.Bizware.Audio;
using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
using BizHawk.Common;
@@ -51,8 +50,8 @@ public Sound(IntPtr mainWindowHandle, Config config, Func getCoreVsyncRa
{
_outputDevice = config.SoundOutputMethod switch
{
- ESoundOutputMethod.DirectSound => IndirectX.CreateDSSoundOutput(this, mainWindowHandle, config.SoundDevice),
- ESoundOutputMethod.XAudio2 => IndirectX.CreateXAudio2SoundOutput(this, config.SoundDevice),
+ ESoundOutputMethod.DirectSound => new DirectSoundSoundOutput(this, mainWindowHandle, config.SoundDevice),
+ ESoundOutputMethod.XAudio2 => new XAudio2SoundOutput(this, config.SoundDevice),
ESoundOutputMethod.OpenAL => new OpenALSoundOutput(this, config.SoundDevice),
_ => new DummySoundOutput(this)
};
diff --git a/src/BizHawk.Client.EmuHawk/config/ControllerConfig/FeedbacksBindPanel.cs b/src/BizHawk.Client.EmuHawk/config/ControllerConfig/FeedbacksBindPanel.cs
index ed7c52f44ed..bcf7b28ff83 100644
--- a/src/BizHawk.Client.EmuHawk/config/ControllerConfig/FeedbacksBindPanel.cs
+++ b/src/BizHawk.Client.EmuHawk/config/ControllerConfig/FeedbacksBindPanel.cs
@@ -18,7 +18,7 @@ public class FeedbacksBindPanel : UserControl
public FeedbacksBindPanel(IDictionary realConfigObject, ICollection? realConfigButtons = null)
{
_realConfigObject = realConfigObject;
- _flpMain.Controls.Add(new LabelEx { Text = "To bind, click \"Bind!\", move an axis (e.g. analog stick) on the desired gamepad, and choose from the dropdown.\nNote: haptic feedback won't work if your gamepad is shown as \"J#\" or if your input method is OpenTK." });
+ _flpMain.Controls.Add(new LabelEx { Text = "To bind, click \"Bind!\", move an axis (e.g. analog stick) on the desired gamepad, and choose from the dropdown.\nNote: haptic feedback won't work if your input method is DirectInput+XInput and your gamepad is shown as \"J#\"." });
var adapter = Input.Instance.Adapter;
foreach (var buttonName in realConfigButtons ?? realConfigObject.Keys)
{
diff --git a/src/BizHawk.Client.EmuHawk/config/DisplayConfig.cs b/src/BizHawk.Client.EmuHawk/config/DisplayConfig.cs
index 9e3a80d4082..f10dbdbce2f 100755
--- a/src/BizHawk.Client.EmuHawk/config/DisplayConfig.cs
+++ b/src/BizHawk.Client.EmuHawk/config/DisplayConfig.cs
@@ -62,7 +62,7 @@ public DisplayConfig(Config config, IDialogController dialogController, IGL gl)
rbOpenGL.Checked = _config.DispMethod == EDispMethod.OpenGL;
rbGDIPlus.Checked = _config.DispMethod == EDispMethod.GdiPlus;
- rbD3D9.Checked = _config.DispMethod == EDispMethod.SlimDX9;
+ rbD3D9.Checked = _config.DispMethod == EDispMethod.D3D9;
cbStatusBarWindowed.Checked = _config.DispChromeStatusBarWindowed;
cbCaptionWindowed.Checked = _config.DispChromeCaptionWindowed;
@@ -228,7 +228,7 @@ private void BtnOk_Click(object sender, EventArgs e)
if(rbGDIPlus.Checked)
_config.DispMethod = EDispMethod.GdiPlus;
if(rbD3D9.Checked)
- _config.DispMethod = EDispMethod.SlimDX9;
+ _config.DispMethod = EDispMethod.D3D9;
if (int.TryParse(txtCropLeft.Text, out int dispCropLeft))
{
diff --git a/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs b/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs
index aeb51fadc41..a2b8a2f8554 100755
--- a/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs
+++ b/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs
@@ -43,7 +43,7 @@ private void InitializeComponent()
this.EnableContextMenuCheckbox = new System.Windows.Forms.CheckBox();
this.PauseWhenMenuActivatedCheckbox = new System.Windows.Forms.CheckBox();
this.groupBox3 = new System.Windows.Forms.GroupBox();
- this.rbInputMethodOpenTK = new System.Windows.Forms.RadioButton();
+ this.rbInputMethodSDL2 = new System.Windows.Forms.RadioButton();
this.rbInputMethodDirectInput = new System.Windows.Forms.RadioButton();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.StartPausedCheckbox = new System.Windows.Forms.CheckBox();
@@ -227,7 +227,7 @@ private void InitializeComponent()
//
// groupBox3
//
- this.groupBox3.Controls.Add(this.rbInputMethodOpenTK);
+ this.groupBox3.Controls.Add(this.rbInputMethodSDL2);
this.groupBox3.Controls.Add(this.rbInputMethodDirectInput);
this.groupBox3.Location = new System.Drawing.Point(6, 151);
this.groupBox3.Name = "groupBox3";
@@ -236,16 +236,16 @@ private void InitializeComponent()
this.groupBox3.TabStop = false;
this.groupBox3.Text = "Input Method (requires restart)";
//
- // rbInputMethodOpenTK
+ // rbInputMethodSDL2
//
- this.rbInputMethodOpenTK.AutoSize = true;
- this.rbInputMethodOpenTK.Location = new System.Drawing.Point(136, 19);
- this.rbInputMethodOpenTK.Name = "rbInputMethodOpenTK";
- this.rbInputMethodOpenTK.Size = new System.Drawing.Size(65, 17);
- this.rbInputMethodOpenTK.TabIndex = 1;
- this.rbInputMethodOpenTK.TabStop = true;
- this.rbInputMethodOpenTK.Text = "OpenTK";
- this.rbInputMethodOpenTK.UseVisualStyleBackColor = true;
+ this.rbInputMethodSDL2.AutoSize = true;
+ this.rbInputMethodSDL2.Location = new System.Drawing.Point(136, 19);
+ this.rbInputMethodSDL2.Name = "rbInputMethodSDL2";
+ this.rbInputMethodSDL2.Size = new System.Drawing.Size(65, 17);
+ this.rbInputMethodSDL2.TabIndex = 1;
+ this.rbInputMethodSDL2.TabStop = true;
+ this.rbInputMethodSDL2.Text = "SDL2";
+ this.rbInputMethodSDL2.UseVisualStyleBackColor = true;
//
// rbInputMethodDirectInput
//
@@ -595,7 +595,7 @@ private void InitializeComponent()
private System.Windows.Forms.CheckBox EnableContextMenuCheckbox;
private System.Windows.Forms.CheckBox PauseWhenMenuActivatedCheckbox;
private System.Windows.Forms.GroupBox groupBox3;
- private System.Windows.Forms.RadioButton rbInputMethodOpenTK;
+ private System.Windows.Forms.RadioButton rbInputMethodSDL2;
private System.Windows.Forms.RadioButton rbInputMethodDirectInput;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.CheckBox StartPausedCheckbox;
diff --git a/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.cs b/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.cs
index 4278f241a3a..6a8a67db3ea 100755
--- a/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.cs
+++ b/src/BizHawk.Client.EmuHawk/config/EmuHawkOptions.cs
@@ -85,8 +85,8 @@ private void GuiOptions_Load(object sender, EventArgs e)
switch (_config.HostInputMethod)
{
- case EHostInputMethod.OpenTK:
- rbInputMethodOpenTK.Checked = true;
+ case EHostInputMethod.SDL2:
+ rbInputMethodSDL2.Checked = true;
break;
case EHostInputMethod.DirectInput:
rbInputMethodDirectInput.Checked = true;
@@ -123,7 +123,7 @@ private void OkBtn_Click(object sender, EventArgs e)
_config.MergeLAndRModifierKeys = cbMergeLAndRModifierKeys.Checked;
_config.SingleInstanceMode = SingleInstanceModeCheckbox.Checked;
if(rbInputMethodDirectInput.Checked) _config.HostInputMethod = EHostInputMethod.DirectInput;
- if(rbInputMethodOpenTK.Checked) _config.HostInputMethod = EHostInputMethod.OpenTK;
+ if(rbInputMethodSDL2.Checked) _config.HostInputMethod = EHostInputMethod.SDL2;
_config.BackupSaveram = BackupSRamCheckbox.Checked;
_config.AutosaveSaveRAM = AutosaveSRAMCheckbox.Checked;
@@ -159,6 +159,6 @@ private void AutosaveSRAMCheckbox_CheckedChanged(object sender, EventArgs e)
private void AutosaveSRAMRadioButton3_CheckedChanged(object sender, EventArgs e)
{
AutosaveSRAMtextBox.Enabled = AutosaveSRAMradioButton3.Checked;
- }
+ }
}
}
diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs
index 668a8cec046..4230754495e 100644
--- a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs
+++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs
@@ -1,5 +1,4 @@
-using System;
-using BizHawk.Emulation.Cores.Nintendo.SNES;
+using BizHawk.Emulation.Cores.Nintendo.SNES;
namespace BizHawk.Client.EmuHawk
{
diff --git a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs
index 04ead2e8c0c..e8c9bea0bd9 100644
--- a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs
+++ b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs
@@ -2229,7 +2229,7 @@ private void viewN64MatrixToolStripMenuItem_Click(object sender, EventArgs e)
}
#if false // if needed
- DialogController.ShowMessageBox(new SlimDX.Matrix {
+ DialogController.ShowMessageBox(new System.Numerics.Matrix4x4() {
M11 = matVals[0, 0], M12 = matVals[0, 1], M13 = matVals[0, 2], M14 = matVals[0, 3],
M21 = matVals[1, 0], M22 = matVals[1, 1], M23 = matVals[1, 2], M24 = matVals[1, 3],
M31 = matVals[2, 0], M32 = matVals[2, 1], M33 = matVals[2, 2], M34 = matVals[2, 3],
diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/HeaderEditor.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/HeaderEditor.Designer.cs
index 76f80435949..4788c1e6974 100644
--- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/HeaderEditor.Designer.cs
+++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/HeaderEditor.Designer.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
diff --git a/src/BizHawk.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
index 71506350a2d..24103f5a648 100644
--- a/src/BizHawk.Common/Extensions/CollectionExtensions.cs
+++ b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
@@ -160,6 +160,22 @@ public static bool CountIsExactly(this IEnumerable collection, int n)
? countable.Count == n
: collection.Take(n + 1).Count() == n;
+ ///
+ /// Returns the value at .
+ /// If the key is not present, returns default(TValue).
+ /// backported from .NET Core 2.0
+ ///
+ public static TValue? GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key)
+ => dictionary.TryGetValue(key, out var found) ? found : default;
+
+ ///
+ /// Returns the value at .
+ /// If the key is not present, returns .
+ /// backported from .NET Core 2.0
+ ///
+ public static TValue? GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue)
+ => dictionary.TryGetValue(key, out var found) ? found : defaultValue;
+
///
/// Returns the value at .
/// If the key is not present, stores the result of defaultValue(key) in the dict, and then returns that.
diff --git a/src/BizHawk.Common/LSB/XlibImports.cs b/src/BizHawk.Common/LSB/XlibImports.cs
new file mode 100644
index 00000000000..63af36cae94
--- /dev/null
+++ b/src/BizHawk.Common/LSB/XlibImports.cs
@@ -0,0 +1,547 @@
+using System;
+using System.Runtime.InteropServices;
+
+// ReSharper disable FieldCanBeMadeReadOnly.Global
+
+namespace BizHawk.Common
+{
+ public static class XlibImports
+ {
+ private const string LIB = "libX11.so.6";
+
+ [DllImport(LIB)]
+ public static extern IntPtr XOpenDisplay(string? display_name);
+
+ [DllImport(LIB)]
+ public static extern int XCloseDisplay(IntPtr display);
+
+ [DllImport(LIB)]
+ public static extern void XLockDisplay(IntPtr display);
+
+ [DllImport(LIB)]
+ public static extern void XUnlockDisplay(IntPtr display);
+
+ // helper struct for XLockDisplay/XUnlockDisplay
+ // largely taken from OpenTK
+ public ref struct XLock
+ {
+ private IntPtr _display;
+
+ public XLock(IntPtr display)
+ {
+ if (display == IntPtr.Zero)
+ {
+ throw new InvalidOperationException("null display");
+ }
+
+ _display = display;
+ XLockDisplay(display);
+ }
+
+ public void Dispose()
+ {
+ if (_display != IntPtr.Zero)
+ {
+ XUnlockDisplay(_display);
+ _display = IntPtr.Zero;
+ }
+ }
+ }
+
+ [DllImport(LIB)]
+ public static extern unsafe int XQueryKeymap(IntPtr display, byte* keys_return);
+
+ // copied from OpenTK
+ public enum Keysym
+ {
+ /*
+ * TTY function keys, cleverly chosen to map to ASCII, for convenience of
+ * programming, but could have been arbitrary (at the cost of lookup
+ * tables in client code).
+ */
+
+ BackSpace = 0xff08, /* Back space, back char */
+ Tab = 0xff09,
+ Linefeed = 0xff0a, /* Linefeed, LF */
+ Clear = 0xff0b,
+ Return = 0xff0d, /* Return, enter */
+ Pause = 0xff13, /* Pause, hold */
+ Scroll_Lock = 0xff14,
+ Sys_Req = 0xff15,
+ Escape = 0xff1b,
+ Delete = 0xffff, /* Delete, rubout */
+
+
+
+ /* International & multi-key character composition */
+
+ Multi_key = 0xff20, /* Multi-key character compose */
+ Codeinput = 0xff37,
+ SingleCandidate = 0xff3c,
+ MultipleCandidate = 0xff3d,
+ PreviousCandidate = 0xff3e,
+
+ /* Japanese keyboard support */
+
+ Kanji = 0xff21, /* Kanji, Kanji convert */
+ Muhenkan = 0xff22, /* Cancel Conversion */
+ Henkan_Mode = 0xff23, /* Start/Stop Conversion */
+ Henkan = 0xff23, /* Alias for Henkan_Mode */
+ Romaji = 0xff24, /* to Romaji */
+ Hiragana = 0xff25, /* to Hiragana */
+ Katakana = 0xff26, /* to Katakana */
+ Hiragana_Katakana = 0xff27, /* Hiragana/Katakana toggle */
+ Zenkaku = 0xff28, /* to Zenkaku */
+ Hankaku = 0xff29, /* to Hankaku */
+ Zenkaku_Hankaku = 0xff2a, /* Zenkaku/Hankaku toggle */
+ Touroku = 0xff2b, /* Add to Dictionary */
+ Massyo = 0xff2c, /* Delete from Dictionary */
+ Kana_Lock = 0xff2d, /* Kana Lock */
+ Kana_Shift = 0xff2e, /* Kana Shift */
+ Eisu_Shift = 0xff2f, /* Alphanumeric Shift */
+ Eisu_toggle = 0xff30, /* Alphanumeric toggle */
+ Kanji_Bangou = 0xff37, /* Codeinput */
+ Zen_Koho = 0xff3d, /* Multiple/All Candidate(s) */
+ Mae_Koho = 0xff3e, /* Previous Candidate */
+
+ /* 0xff31 thru 0xff3f are under XK_KOREAN */
+
+ /* Cursor control & motion */
+
+ Home = 0xff50,
+ Left = 0xff51, /* Move left, left arrow */
+ Up = 0xff52, /* Move up, up arrow */
+ Right = 0xff53, /* Move right, right arrow */
+ Down = 0xff54, /* Move down, down arrow */
+ Prior = 0xff55, /* Prior, previous */
+ Page_Up = 0xff55,
+ Next = 0xff56, /* Next */
+ Page_Down = 0xff56,
+ End = 0xff57, /* EOL */
+ Begin = 0xff58, /* BOL */
+
+
+ /* Misc functions */
+
+ Select = 0xff60, /* Select, mark */
+ Print = 0xff61,
+ Execute = 0xff62, /* Execute, run, do */
+ Insert = 0xff63, /* Insert, insert here */
+ Undo = 0xff65,
+ Redo = 0xff66, /* Redo, again */
+ Menu = 0xff67,
+ Find = 0xff68, /* Find, search */
+ Cancel = 0xff69, /* Cancel, stop, abort, exit */
+ Help = 0xff6a, /* Help */
+ Break = 0xff6b,
+ Mode_switch = 0xff7e, /* Character set switch */
+ script_switch = 0xff7e, /* Alias for mode_switch */
+ Num_Lock = 0xff7f,
+
+ /* Keypad functions, keypad numbers cleverly chosen to map to ASCII */
+
+ KP_Space = 0xff80, /* Space */
+ KP_Tab = 0xff89,
+ KP_Enter = 0xff8d, /* Enter */
+ KP_F1 = 0xff91, /* PF1, KP_A, ... */
+ KP_F2 = 0xff92,
+ KP_F3 = 0xff93,
+ KP_F4 = 0xff94,
+ KP_Home = 0xff95,
+ KP_Left = 0xff96,
+ KP_Up = 0xff97,
+ KP_Right = 0xff98,
+ KP_Down = 0xff99,
+ KP_Prior = 0xff9a,
+ KP_Page_Up = 0xff9a,
+ KP_Next = 0xff9b,
+ KP_Page_Down = 0xff9b,
+ KP_End = 0xff9c,
+ KP_Begin = 0xff9d,
+ KP_Insert = 0xff9e,
+ KP_Delete = 0xff9f,
+ KP_Equal = 0xffbd, /* Equals */
+ KP_Multiply = 0xffaa,
+ KP_Add = 0xffab,
+ KP_Separator = 0xffac, /* Separator, often comma */
+ KP_Subtract = 0xffad,
+ KP_Decimal = 0xffae,
+ KP_Divide = 0xffaf,
+
+ KP_0 = 0xffb0,
+ KP_1 = 0xffb1,
+ KP_2 = 0xffb2,
+ KP_3 = 0xffb3,
+ KP_4 = 0xffb4,
+ KP_5 = 0xffb5,
+ KP_6 = 0xffb6,
+ KP_7 = 0xffb7,
+ KP_8 = 0xffb8,
+ KP_9 = 0xffb9,
+
+ /*
+ * Auxiliary functions; note the duplicate definitions for left and right
+ * function keys; Sun keyboards and a few other manufacturers have such
+ * function key groups on the left and/or right sides of the keyboard.
+ * We've not found a keyboard with more than 35 function keys total.
+ */
+
+ F1 = 0xffbe,
+ F2 = 0xffbf,
+ F3 = 0xffc0,
+ F4 = 0xffc1,
+ F5 = 0xffc2,
+ F6 = 0xffc3,
+ F7 = 0xffc4,
+ F8 = 0xffc5,
+ F9 = 0xffc6,
+ F10 = 0xffc7,
+ F11 = 0xffc8,
+ L1 = 0xffc8,
+ F12 = 0xffc9,
+ L2 = 0xffc9,
+ F13 = 0xffca,
+ L3 = 0xffca,
+ F14 = 0xffcb,
+ L4 = 0xffcb,
+ F15 = 0xffcc,
+ L5 = 0xffcc,
+ F16 = 0xffcd,
+ L6 = 0xffcd,
+ F17 = 0xffce,
+ L7 = 0xffce,
+ F18 = 0xffcf,
+ L8 = 0xffcf,
+ F19 = 0xffd0,
+ L9 = 0xffd0,
+ F20 = 0xffd1,
+ L10 = 0xffd1,
+ F21 = 0xffd2,
+ R1 = 0xffd2,
+ F22 = 0xffd3,
+ R2 = 0xffd3,
+ F23 = 0xffd4,
+ R3 = 0xffd4,
+ F24 = 0xffd5,
+ R4 = 0xffd5,
+ F25 = 0xffd6,
+ R5 = 0xffd6,
+ F26 = 0xffd7,
+ R6 = 0xffd7,
+ F27 = 0xffd8,
+ R7 = 0xffd8,
+ F28 = 0xffd9,
+ R8 = 0xffd9,
+ F29 = 0xffda,
+ R9 = 0xffda,
+ F30 = 0xffdb,
+ R10 = 0xffdb,
+ F31 = 0xffdc,
+ R11 = 0xffdc,
+ F32 = 0xffdd,
+ R12 = 0xffdd,
+ F33 = 0xffde,
+ R13 = 0xffde,
+ F34 = 0xffdf,
+ R14 = 0xffdf,
+ F35 = 0xffe0,
+ R15 = 0xffe0,
+
+ /* Modifiers */
+
+ Shift_L = 0xffe1, /* Left shift */
+ Shift_R = 0xffe2, /* Right shift */
+ Control_L = 0xffe3, /* Left control */
+ Control_R = 0xffe4, /* Right control */
+ Caps_Lock = 0xffe5, /* Caps lock */
+ Shift_Lock = 0xffe6, /* Shift lock */
+
+ Meta_L = 0xffe7, /* Left meta */
+ Meta_R = 0xffe8, /* Right meta */
+ Alt_L = 0xffe9, /* Left alt */
+ Alt_R = 0xffea, /* Right alt */
+ Super_L = 0xffeb, /* Left super */
+ Super_R = 0xffec, /* Right super */
+ Hyper_L = 0xffed, /* Left hyper */
+ Hyper_R = 0xffee, /* Right hyper */
+
+ ISO_Level3_Shift = 0xfe03,
+
+ /*
+ * Latin 1
+ * (ISO/IEC 8859-1 = Unicode U+0020..U+00FF)
+ * Byte 3 = 0
+ */
+
+ space = 0x0020, /* U+0020 SPACE */
+ exclam = 0x0021, /* U+0021 EXCLAMATION MARK */
+ quotedbl = 0x0022, /* U+0022 QUOTATION MARK */
+ numbersign = 0x0023, /* U+0023 NUMBER SIGN */
+ dollar = 0x0024, /* U+0024 DOLLAR SIGN */
+ percent = 0x0025, /* U+0025 PERCENT SIGN */
+ ampersand = 0x0026, /* U+0026 AMPERSAND */
+ apostrophe = 0x0027, /* U+0027 APOSTROPHE */
+ quoteright = 0x0027, /* deprecated */
+ parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */
+ parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */
+ asterisk = 0x002a, /* U+002A ASTERISK */
+ plus = 0x002b, /* U+002B PLUS SIGN */
+ comma = 0x002c, /* U+002C COMMA */
+ minus = 0x002d, /* U+002D HYPHEN-MINUS */
+ period = 0x002e, /* U+002E FULL STOP */
+ slash = 0x002f, /* U+002F SOLIDUS */
+ Number0 = 0x0030, /* U+0030 DIGIT ZERO */
+ Number1 = 0x0031, /* U+0031 DIGIT ONE */
+ Number2 = 0x0032, /* U+0032 DIGIT TWO */
+ Number3 = 0x0033, /* U+0033 DIGIT THREE */
+ Number4 = 0x0034, /* U+0034 DIGIT FOUR */
+ Number5 = 0x0035, /* U+0035 DIGIT FIVE */
+ Number6 = 0x0036, /* U+0036 DIGIT SIX */
+ Number7 = 0x0037, /* U+0037 DIGIT SEVEN */
+ Number8 = 0x0038, /* U+0038 DIGIT EIGHT */
+ Number9 = 0x0039, /* U+0039 DIGIT NINE */
+ colon = 0x003a, /* U+003A COLON */
+ semicolon = 0x003b, /* U+003B SEMICOLON */
+ less = 0x003c, /* U+003C LESS-THAN SIGN */
+ equal = 0x003d, /* U+003D EQUALS SIGN */
+ greater = 0x003e, /* U+003E GREATER-THAN SIGN */
+ question = 0x003f, /* U+003F QUESTION MARK */
+ at = 0x0040, /* U+0040 COMMERCIAL AT */
+ A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */
+ B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */
+ C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */
+ D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */
+ E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */
+ F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */
+ G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */
+ H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */
+ I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */
+ J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */
+ K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */
+ L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */
+ M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */
+ N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */
+ O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */
+ P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */
+ Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
+ R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */
+ S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */
+ T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */
+ U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */
+ V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */
+ W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */
+ X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */
+ Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
+ Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */
+ bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */
+ backslash = 0x005c, /* U+005C REVERSE SOLIDUS */
+ bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */
+ asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */
+ underscore = 0x005f, /* U+005F LOW LINE */
+ grave = 0x0060, /* U+0060 GRAVE ACCENT */
+ quoteleft = 0x0060, /* deprecated */
+ a = 0x0061, /* U+0061 LATIN SMALL LETTER A */
+ b = 0x0062, /* U+0062 LATIN SMALL LETTER B */
+ c = 0x0063, /* U+0063 LATIN SMALL LETTER C */
+ d = 0x0064, /* U+0064 LATIN SMALL LETTER D */
+ e = 0x0065, /* U+0065 LATIN SMALL LETTER E */
+ f = 0x0066, /* U+0066 LATIN SMALL LETTER F */
+ g = 0x0067, /* U+0067 LATIN SMALL LETTER G */
+ h = 0x0068, /* U+0068 LATIN SMALL LETTER H */
+ i = 0x0069, /* U+0069 LATIN SMALL LETTER I */
+ j = 0x006a, /* U+006A LATIN SMALL LETTER J */
+ k = 0x006b, /* U+006B LATIN SMALL LETTER K */
+ l = 0x006c, /* U+006C LATIN SMALL LETTER L */
+ m = 0x006d, /* U+006D LATIN SMALL LETTER M */
+ n = 0x006e, /* U+006E LATIN SMALL LETTER N */
+ o = 0x006f, /* U+006F LATIN SMALL LETTER O */
+ p = 0x0070, /* U+0070 LATIN SMALL LETTER P */
+ q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */
+ r = 0x0072, /* U+0072 LATIN SMALL LETTER R */
+ s = 0x0073, /* U+0073 LATIN SMALL LETTER S */
+ t = 0x0074, /* U+0074 LATIN SMALL LETTER T */
+ u = 0x0075, /* U+0075 LATIN SMALL LETTER U */
+ v = 0x0076, /* U+0076 LATIN SMALL LETTER V */
+ w = 0x0077, /* U+0077 LATIN SMALL LETTER W */
+ x = 0x0078, /* U+0078 LATIN SMALL LETTER X */
+ y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */
+ z = 0x007a, /* U+007A LATIN SMALL LETTER Z */
+ braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */
+ bar = 0x007c, /* U+007C VERTICAL LINE */
+ braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */
+ asciitilde = 0x007e, /* U+007E TILDE */
+
+ // Extra keys
+
+ XF86AudioMute = 0x1008ff12,
+ XF86AudioLowerVolume = 0x1008ff11,
+ XF86AudioRaiseVolume = 0x1008ff13,
+ XF86PowerOff = 0x1008ff2a,
+ XF86Suspend = 0x1008ffa7,
+ XF86Copy = 0x1008ff57,
+ XF86Paste = 0x1008ff6d,
+ XF86Cut = 0x1008ff58,
+ XF86MenuKB = 0x1008ff65,
+ XF86Calculator = 0x1008ff1d,
+ XF86Sleep = 0x1008ff2f,
+ XF86WakeUp = 0x1008ff2b,
+ XF86Explorer = 0x1008ff5d,
+ XF86Send = 0x1008ff7b,
+ XF86Xfer = 0x1008ff8a,
+ XF86Launch1 = 0x1008ff41,
+ XF86Launch2 = 0x1008ff42,
+ XF86Launch3 = 0x1008ff43,
+ XF86Launch4 = 0x1008ff44,
+ XF86Launch5 = 0x1008ff45,
+ XF86LaunchA = 0x1008ff4a,
+ XF86LaunchB = 0x1008ff4b,
+ XF86WWW = 0x1008ff2e,
+ XF86DOS = 0x1008ff5a,
+ XF86ScreenSaver = 0x1008ff2d,
+ XF86RotateWindows = 0x1008ff74,
+ XF86Mail = 0x1008ff19,
+ XF86Favorites = 0x1008ff30,
+ XF86MyComputer = 0x1008ff33,
+ XF86Back = 0x1008ff26,
+ XF86Forward = 0x1008ff27,
+ XF86Eject = 0x1008ff2c,
+ XF86AudioPlay = 0x1008ff14,
+ XF86AudioStop = 0x1008ff15,
+ XF86AudioPrev = 0x1008ff16,
+ XF86AudioNext = 0x1008ff17,
+ XF86AudioRecord = 0x1008ff1c,
+ XF86AudioPause = 0x1008ff31,
+ XF86AudioRewind = 0x1008ff3e,
+ XF86AudioForward = 0x1008ff97,
+ XF86Phone = 0x1008ff6e,
+ XF86Tools = 0x1008ff81,
+ XF86HomePage = 0x1008ff18,
+ XF86Close = 0x1008ff56,
+ XF86Reload = 0x1008ff73,
+ XF86ScrollUp = 0x1008ff78,
+ XF86ScrollDown = 0x1008ff79,
+ XF86New = 0x1008ff68,
+ XF86TouchpadToggle = 0x1008ffa9,
+ XF86WebCam = 0x1008ff8f,
+ XF86Search = 0x1008ff1b,
+ XF86Finance = 0x1008ff3c,
+ XF86Shop = 0x1008ff36,
+ XF86MonBrightnessDown = 0x1008ff03,
+ XF86MonBrightnessUp = 0x1008ff02,
+ XF86AudioMedia = 0x1008ff32,
+ XF86Display = 0x1008ff59,
+ XF86KbdLightOnOff = 0x1008ff04,
+ XF86KbdBrightnessDown = 0x1008ff06,
+ XF86KbdBrightnessUp = 0x1008ff05,
+ XF86Reply = 0x1008ff72,
+ XF86MailForward = 0x1008ff90,
+ XF86Save = 0x1008ff77,
+ XF86Documents = 0x1008ff5b,
+ XF86Battery = 0x1008ff93,
+ XF86Bluetooth = 0x1008ff94,
+ XF86WLAN = 0x1008ff95,
+
+ SunProps = 0x1005ff70,
+ SunOpen = 0x1005ff73,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct XKeyEvent
+ {
+ public int type;
+ public nuint serial;
+ [MarshalAs(UnmanagedType.Bool)]
+ public bool send_event;
+ public IntPtr display;
+ public nuint window;
+ public nuint root;
+ public nuint subwindow;
+ public nuint time;
+ public int x, y;
+ public int x_root, y_root;
+ public int state;
+ public int keycode;
+ [MarshalAs(UnmanagedType.Bool)]
+ public bool same_screen;
+ }
+
+ [DllImport(LIB)]
+ public static extern Keysym XLookupKeysym(ref XKeyEvent key_event, int index);
+
+ [DllImport(LIB)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool XkbQueryExtension(IntPtr display, out int opcode_rtrn, out int event_rtrn, out int error_rtrn, ref int major_in_out, ref int minor_in_out);
+
+ [DllImport(LIB)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool XkbSetDetectableAutoRepeat(IntPtr display, [MarshalAs(UnmanagedType.Bool)] bool detectable, [MarshalAs(UnmanagedType.Bool)] out bool supported_rtrn);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct XkbKeyNameRec
+ {
+ public fixed sbyte name[4];
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct XkbKeyAliasRec
+ {
+ public fixed sbyte real[4];
+ public fixed sbyte alias[4];
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct XkbNamesRec
+ {
+ // the ulongs here are Atom, which are actually more nuint
+ // they're ulong here as you can't use nuint in a fixed buffer (thanks microsoft)
+ // TODO: maybe make this work with 32 bit
+
+ public ulong keycodes;
+ public ulong geometry;
+ public ulong symbols;
+ public ulong types;
+ public ulong compat;
+ public fixed ulong vmods[16];
+ public fixed ulong indicators[32];
+ public fixed ulong groups[4];
+ public XkbKeyNameRec* keys;
+ public XkbKeyAliasRec* key_aliases;
+ public ulong* radio_groups;
+ public ulong phys_symbols;
+
+ public byte num_keys;
+ public byte num_key_aliases;
+ public ushort num_rg;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct XkbDescRec
+ {
+ public IntPtr dpy;
+ public ushort flags;
+ public ushort device_spec;
+ public byte min_key_code;
+ public byte max_key_code;
+
+ public IntPtr ctrls;
+ public IntPtr server;
+ public IntPtr map;
+ public IntPtr indicators;
+ public XkbNamesRec* names;
+ public IntPtr compat;
+ public IntPtr geom;
+ }
+
+ [DllImport(LIB)]
+ public static extern unsafe XkbDescRec* XkbAllocKeyboard(IntPtr display);
+
+ [DllImport(LIB)]
+ public static extern unsafe void XkbFreeKeyboard(XkbDescRec* xkb, int which, [MarshalAs(UnmanagedType.Bool)] bool free_all);
+
+ [DllImport(LIB)]
+ public static extern unsafe int XkbGetNames(IntPtr display, uint which, XkbDescRec* xkb);
+
+ [DllImport(LIB)]
+ public static extern Keysym XkbKeycodeToKeysym(IntPtr display, int keycode, int group, int level);
+ }
+}
diff --git a/src/BizHawk.Common/Win32/Win32Imports.cs b/src/BizHawk.Common/Win32/Win32Imports.cs
index beb12314a73..a629eb9d85f 100644
--- a/src/BizHawk.Common/Win32/Win32Imports.cs
+++ b/src/BizHawk.Common/Win32/Win32Imports.cs
@@ -10,6 +10,7 @@ namespace BizHawk.Common
public static class Win32Imports
{
public const int MAX_PATH = 260;
+ public const uint PM_REMOVE = 0x0001U;
public delegate int BFFCALLBACK(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData);
@@ -85,6 +86,18 @@ public enum Format
}
}
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MSG
+ {
+ public IntPtr hwnd;
+ public uint message;
+ public IntPtr wParam;
+ public IntPtr lParam;
+ public uint time;
+ public int x;
+ public int y;
+ }
+
[Guid("00000002-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMalloc
@@ -103,6 +116,12 @@ public interface IMalloc
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ public static extern IntPtr DispatchMessage([In] ref MSG lpMsg);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, string windowTitle);
+
[DllImport("user32.dll")]
public static extern IntPtr GetActiveWindow();
@@ -134,6 +153,10 @@ public interface IMalloc
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
public static extern bool PathRelativePathTo([Out] StringBuilder pszPath, [In] string pszFrom, [In] FileAttributes dwAttrFrom, [In] string pszTo, [In] FileAttributes dwAttrTo);
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
+
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, ref HDITEM lParam);
@@ -157,5 +180,9 @@ public interface IMalloc
[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
public static extern uint timeBeginPeriod(uint uMilliseconds);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool TranslateMessage([In] ref MSG lpMsg);
}
}
diff --git a/submodules/sameboy/.gitignore b/submodules/sameboy/.gitignore
new file mode 100644
index 00000000000..28a478fa196
--- /dev/null
+++ b/submodules/sameboy/.gitignore
@@ -0,0 +1 @@
+/obj