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