Kill Bizware .NET Framework deps (#3702)

The main objective in this PR is to get rid of the main .NET Framework dependencies in Bizware packages. This PR doesn't do that completely per se, still having .NET Framework used for WinForms Controls, but that can easily be swapped over for whatever UI framework we use next as long as it exposes native window handles in some way.

For this PR, it does some reorganizing of Bizware, splitting Bizware.OpenTK3 and Bizware.DirectX into 3 packages based on usage; Bizware.Audio, Bizware.Graphics, and Bizware.Input. These packages in the future probably could have more functionality moved into them, but for now they are largely just a reshuffling of the Bizware.OpenTK3 and Bizware.DirectX packages.

As both SlimDX and OpenTK3 are .NET Framework, they have been removed in this PR. Their replacements are as follows:

SharpDX: DirectSound, Direct3D9
Vortice: XAudio2, DirectInput/XInput
Silk.NET: OpenAL, OpenGL
SDL2-CS / native SDL2: OpenGL context management, new gamepad backend (replacing OpenTK's role for gamepads)
native X11: New key input backend (replacing one of OpenTK's roles for keyboards)

GLControl has been replaced by custom made control which just uses SDL2 for context management.

The OpenTK input backend has been replaced with a combination of SDL2 and an OS tailored key input backend (DirectInput on Windows, X11 on Linux, and planned to be Quartz on macOS). This is just represented on the user side as "SDL2" without mentioning the key input backend. This does mean for a while DirectX will be mandatory on Windows again, until a RAWINPUT backend is written for handling key input on Windows for the SDL2 input backend.
This commit is contained in:
CasualPokePlayer 2023-07-23 00:35:43 -07:00 committed by GitHub
parent 26b39fb56c
commit 78f5e75534
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 5128 additions and 4080 deletions

View File

@ -1,25 +0,0 @@
<configuration>
<dllmap os="linux" dll="opengl32.dll" target="libGL.so.1"/>
<dllmap os="linux" dll="glu32.dll" target="libGLU.so.1"/>
<dllmap os="linux" dll="openal32.dll" target="libopenal.so.1"/>
<dllmap os="linux" dll="alut.dll" target="libalut.so.0"/>
<dllmap os="linux" dll="opencl.dll" target="libOpenCL.so"/>
<dllmap os="linux" dll="libX11" target="libX11.so.6"/>
<dllmap os="linux" dll="libXi" target="libXi.so.6"/>
<dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0"/>
<dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL"/>
<dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
<dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
<dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL"/>
<dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib"/>
<!-- XQuartz compatibility (X11 on Mac) -->
<dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib"/>
<dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib"/>
<dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib"/>
<dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib"/>
<dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib"/>
<dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib"/>
</configuration>

Binary file not shown.

View File

@ -0,0 +1,4 @@
<configuration>
<!-- Hack until https://github.com/dotnet/Silk.NET/commit/d5f1f295966c0790abc215ab6e900f810f464443 is in a NuGet release -->
<dllmap os="linux" dll="libdl" target="libdl.so.2"/>
</configuration>

BIN
Assets/dll/libSDL2.so Normal file

Binary file not shown.

View File

@ -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

Binary file not shown.

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<Import Project="../MainSlnCommon.props" />
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Silk.NET.OpenAL" Version="2.17.1" />
<PackageReference Include="Silk.NET.OpenAL.Extensions.Creative" Version="2.17.1" />
<PackageReference Include="Silk.NET.OpenAL.Extensions.Enumeration" Version="2.17.1" />
<PackageReference Include="Vortice.MediaFoundation" Version="2.4.2" />
<PackageReference Include="Vortice.XAudio2" Version="2.4.2" />
<PackageReference Include="SharpDX.DirectSound" Version="4.2.0" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Client.Common/BizHawk.Client.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -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 @@ namespace BizHawk.Bizware.DirectX
_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 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
_filledBufferSizeBytes = 0;
_lastWriteTime = 0;
_lastWriteCursor = 0;
int attempts = _retryCounter;
var attempts = _retryCounter;
while (!IsPlaying && attempts > 0)
{
attempts--;
@ -69,15 +73,13 @@ namespace BizHawk.Bizware.DirectX
{
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 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
// 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 @@ namespace BizHawk.Bizware.DirectX
{
_deviceBuffer.Stop();
}
catch (DirectSoundException)
catch (SharpDXException)
{
}
}
_deviceBuffer.Dispose();
_deviceBuffer = null;
BufferSizeSamples = 0;
@ -169,21 +169,20 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
_lastWriteTime = currentWriteTime;
_lastWriteCursor = writeCursor;
}
catch (DirectSoundException)
catch (SharpDXException)
{
samplesNeeded = 0;
}
@ -209,7 +208,7 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
_actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * _sound.BlockAlign)) % BufferSizeBytes;
_filledBufferSizeBytes += sampleCount * _sound.BlockAlign;
}
catch (DirectSoundException)
catch (SharpDXException)
{
_deviceBuffer.Restore();
StartPlaying();
@ -235,10 +234,7 @@ namespace BizHawk.Bizware.DirectX
}
else
{
if (_deviceBuffer != null)
{
_deviceBuffer.Restore();
}
_deviceBuffer?.Restore();
StartPlaying();
}
}

View File

@ -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<T>() where T : NativeExtension<ALContext>
=> _alc.TryGetExtension<T>(null, out var ext) ? ext : null;
private static readonly EnumerateAll _enumAllExt = GetExtensionOrNull<EnumerateAll>();
private static readonly Enumeration _enumExt = GetExtensionOrNull<Enumeration>();
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<string> GetDeviceNames()
=> _enumAllExt?.GetStringList(GetEnumerateAllContextStringList.AllDevicesSpecifier)
?? _enumExt?.GetStringList(GetEnumerationContextStringList.DeviceSpecifiers)
?? Enumerable.Empty<string>();
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<BufferPoolItem> _availableItems = new();
private readonly Queue<BufferPoolItem> _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();
}
}
}
}
}

View File

@ -4,35 +4,50 @@ using System.Linq;
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;
private static string GetDeviceId(string deviceName)
{
if (string.IsNullOrEmpty(deviceName))
{
return null;
}
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 = new XAudio2();
for (int i = 0, l = _device.DeviceCount; i < l; i++)
{
if (_device.GetDeviceDetails(i).DisplayName == chosenDeviceName)
{
_masteringVoice = new MasteringVoice(_device, _sound.ChannelCount, _sound.SampleRate, i);
return;
}
}
_masteringVoice = new MasteringVoice(_device, _sound.ChannelCount, _sound.SampleRate);
_device = XAudio2.XAudio2Create();
_masteringVoice = _device.CreateMasteringVoice(
inputChannels: _sound.ChannelCount,
inputSampleRate: _sound.SampleRate,
deviceId: GetDeviceId(chosenDeviceName));
}
public void Dispose()
@ -40,20 +55,16 @@ namespace BizHawk.Bizware.DirectX
if (_disposed) return;
_masteringVoice.Dispose();
_masteringVoice = null;
_device.Dispose();
_device = null;
_disposed = true;
}
public static IEnumerable<string> 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 @@ namespace BizHawk.Bizware.DirectX
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
};
var format = new WaveFormat(_sound.SampleRate, _sound.BytesPerSample * 8, _sound.ChannelCount);
_sourceVoice = _device.CreateSourceVoice(format);
_sourceVoice = new SourceVoice(_device, format);
_bufferPool = new BufferPool();
_bufferPool = new();
_runningSamplesQueued = 0;
_sourceVoice.Start();
@ -102,10 +104,10 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
{
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<short>());
item.AudioBuffer.AudioBytes = byteCount;
_sourceVoice.SubmitSourceBuffer(item.AudioBuffer);
_runningSamplesQueued += sampleCount;
@ -127,14 +130,13 @@ namespace BizHawk.Bizware.DirectX
private class BufferPool : IDisposable
{
private readonly List<BufferPoolItem> _availableItems = new List<BufferPoolItem>();
private readonly Queue<BufferPoolItem> _obtainedItems = new Queue<BufferPoolItem>();
private readonly List<BufferPoolItem> _availableItems = new();
private readonly Queue<BufferPoolItem> _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 @@ namespace BizHawk.Bizware.DirectX
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 @@ namespace BizHawk.Bizware.DirectX
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);
}
}
}

View File

@ -4,6 +4,6 @@ namespace BizHawk.Bizware.BizwareGL
{
OpenGL = 0,
GdiPlus = 1,
SlimDX9 = 2,
D3D9 = 2
}
}

View File

@ -65,8 +65,8 @@ namespace BizHawk.Bizware.BizwareGL
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);
/// <summary>
/// opengl sucks, man. seriously, screw this (textures from render targets are upside down)

View File

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<Import Project="../MainSlnCommon.props" />
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
<Reference Include="SlimDX" HintPath="$(ProjectDir)../../References/x64/SlimDX.dll" Private="true" />
<PackageReference Include="OpenTK" Version="3.3.3" PrivateAssets="all" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.OpenTK3/BizHawk.Bizware.OpenTK3.csproj" />
</ItemGroup>
</Project>

View File

@ -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);
}
}
}

View File

@ -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<GamePad360> Devices = new List<GamePad360>();
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<GamePad360> 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<string> _names = new List<string>();
private readonly List<Func<bool>> _actions = new List<Func<bool>>();
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<bool> pressed)
{
_names.Add(name);
_actions.Add(pressed);
NumButtons++;
}
public string ButtonName(int index) => _names[index];
public bool Pressed(int index) => _actions[index]();
/// <remarks><paramref name="left"/> and <paramref name="right"/> are in 0..<see cref="int.MaxValue"/></remarks>
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
}
}
}
}

View File

@ -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;
}
/// <exception cref="InvalidOperationException"><paramref name="required"/> is <see langword="true"/> and compilation error occurred</exception>
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;
}
}
/// <exception cref="InvalidOperationException"><paramref name="required"/> is <see langword="true"/> and compilation error occurred</exception>
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;
/// <exception cref="InvalidOperationException">
/// <paramref name="required"/> is <see langword="true"/> and either <paramref name="vertexShader"/> or <paramref name="fragmentShader"/> is unavailable (their <see cref="Shader.Available"/> property is <see langword="false"/>), or
/// one of <paramref name="vertexLayout"/>'s items has an unsupported value in <see cref="VertexLayout.LayoutItem.AttribType"/>, <see cref="VertexLayout.LayoutItem.Components"/>, or <see cref="VertexLayout.LayoutItem.Usage"/>
/// </exception>
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<UniformInfo>();
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<Tuple<string,EffectHandle>>();
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;
}
/// <exception cref="InvalidOperationException">GDI+ call returned unexpected data</exception>
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;
}
/// <exception cref="InvalidOperationException">SlimDX call returned unexpected data</exception>
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();
}
/// <exception cref="InvalidOperationException"><paramref name="control"/> does not match control passed to <see cref="BeginControl"/></exception>
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<RenderTarget> _renderTargets = new HashSet<RenderTarget>();
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;
}
/// <exception cref="NotSupportedException"><paramref name="mode"/> is not <see cref="BizwareGL.PrimitiveType.TriangleStrip"/></exception>
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();
}
}
}

View File

@ -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<KeyEvent> PendingEventList = new List<KeyEvent>();
private static readonly List<KeyEvent> EventList = new List<KeyEvent>();
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<KeyEvent> Update()
{
EventList.Clear();
lock (PendingEventList)
{
EventList.AddRange(PendingEventList);
PendingEventList.Clear();
}
return EventList;
}
}
}

View File

@ -1,29 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using BizHawk.Bizware.BizwareGL;
using BizHawk.Client.Common;
namespace BizHawk.Bizware.DirectX
{
/// <summary>An indirection, so that types from the SlimDX assembly don't need to be resolved if DirectX/XAudio2 features are never used.</summary>
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<string> GetDSSinkNames()
=> DirectSoundSoundOutput.GetDeviceNames();
public static IEnumerable<string> GetXAudio2SinkNames()
=> XAudio2SoundOutput.GetDeviceNames();
}
}

View File

@ -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<KeyEvent> 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<KeyEvent>();
var eventList = new List<KeyEvent>();
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<DistinctKey> KeyEnumMap = new List<DistinctKey>
{
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
};
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<Import Project="../MainSlnCommon.props" />
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
<PackageReference Include="Silk.NET.OpenGL.Legacy" Version="2.17.1" />
<PackageReference Include="SharpDX.Direct3D9" Version="4.2.0" />
<PackageReference Include="ppy.SDL2-CS" Version="1.0.630-alpha" ExcludeAssets="native;contentFiles" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj" />
</ItemGroup>
</Project>

View File

@ -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);
}
}

View File

@ -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
{
/// <summary>
/// Direct3D9 implementation of the BizwareGL.IGL interface
/// </summary>
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<RenderTarget> _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;
}
/// <exception cref="InvalidOperationException"><paramref name="required"/> is <see langword="true"/> and compilation error occurred</exception>
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() };
}
}
/// <exception cref="InvalidOperationException"><paramref name="required"/> is <see langword="true"/> and compilation error occurred</exception>
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;
/// <exception cref="InvalidOperationException">
/// <paramref name="required"/> is <see langword="true"/> and either <paramref name="vertexShader"/> or <paramref name="fragmentShader"/> is unavailable (their <see cref="Shader.Available"/> property is <see langword="false"/>), or
/// one of <paramref name="vertexLayout"/>'s items has an unsupported value in <see cref="VertexLayout.LayoutItem.AttribType"/>, <see cref="VertexLayout.LayoutItem.Components"/>, or <see cref="VertexLayout.LayoutItem.Usage"/>
/// </exception>
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<UniformInfo>();
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;
}
/// <exception cref="InvalidOperationException">GDI+ call returned unexpected data</exception>
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<byte>(bmpData.Scan0.ToPointer(), bmpData.Stride * bmp.Height);
var dstSpan = new Span<byte>(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;
}
/// <exception cref="InvalidOperationException">Vortice call returned unexpected data</exception>
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);
}
/// <exception cref="InvalidOperationException"><paramref name="control"/> does not match control passed to <see cref="BeginControl"/></exception>
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<DrawPrimitiveUPDelegate> _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);
/// <exception cref="NotSupportedException"><paramref name="mode"/> is not <see cref="BizwareGL.PrimitiveType.TriangleStrip"/></exception>
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();
}
}

View File

@ -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);
}
}

View File

@ -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
{
/// <summary>
/// A simple renderer useful for rendering GUI stuff.
@ -24,13 +25,13 @@ namespace BizHawk.Bizware.OpenTK3
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 @@ namespace BizHawk.Bizware.OpenTK3
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 @@ namespace BizHawk.Bizware.OpenTK3
{
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 @@ namespace BizHawk.Bizware.OpenTK3
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 @@ namespace BizHawk.Bizware.OpenTK3
}
}
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 @@ namespace BizHawk.Bizware.OpenTK3
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,
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;
}
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,
};
Texture2d tex = art.BaseTexture;
PrepDrawSubrectInternal(tex);
fixed (float* pData = &data[0])
{
Owner.BindArrayData(new(pData));
Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
PrepDrawSubrectInternal(art.BaseTexture);
Owner.BindArrayData(new(data));
Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private void PrepDrawSubrectInternal(Texture2d tex)
@ -264,31 +289,24 @@ namespace BizHawk.Bizware.OpenTK3
{
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 @@ namespace BizHawk.Bizware.OpenTK3
//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;

View File

@ -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
{
/// <summary>
/// OpenGL implementation of the BizwareGL.IGL interface
/// </summary>
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<int> SamplerLocs;
}
/// <exception cref="InvalidOperationException">
/// <paramref name="required"/> is <see langword="true"/> and either <paramref name="vertexShader"/> or <paramref name="fragmentShader"/> is unavailable (their <see cref="BizShader.Available"/> property is <see langword="false"/>), or
/// <c>glLinkProgram</c> call did not produce expected result
/// </exception>
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<AttributeInfo>();
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<UniformInfo>();
GL.GetProgram(pid, ProgramPropertyARB.ActiveUniforms, out var nUniforms);
var samplers = new List<int>();
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);
}
/// <exception cref="InvalidOperationException"><paramref name="pipeline"/>.<see cref="Pipeline.Available"/> is <see langword="false"/></exception>
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);
}
/// <exception cref="InvalidOperationException">framebuffer creation unsuccessful</exception>
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<uint> 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);
}
}
}

View File

@ -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<SDL2OpenGLContext> _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<SDL2OpenGLContext> 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();
}
}
}

View File

@ -0,0 +1,169 @@
using System;
using Silk.NET.OpenGL.Legacy;
using static SDL2.SDL;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Wraps an SDL2 OpenGL context
/// </summary>
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);
}
}
}

View File

@ -1,6 +1,6 @@
using BizHawk.Bizware.BizwareGL;
namespace BizHawk.Bizware.OpenTK3
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// An IBlendState token that just caches all the args needed to create a blend state

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<Import Project="../MainSlnCommon.props" />
<PropertyGroup>
@ -8,9 +8,9 @@
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
<PackageReference Include="OpenTK" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="OpenTK.GLControl" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Vortice.DirectInput" Version="2.4.2" />
<PackageReference Include="Vortice.XInput" Version="2.4.2" />
<PackageReference Include="ppy.SDL2-CS" Version="1.0.630-alpha" ExcludeAssets="native;contentFiles" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Client.Common/BizHawk.Client.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -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<GamePad> Devices = new List<GamePad>();
private static DirectInput _directInput;
private static readonly object SyncObj = new();
private static readonly List<DGamepad> Devices = new();
private static IDirectInput8 _directInput;
public static void Initialize(IntPtr mainFormHandle)
{
@ -20,33 +22,51 @@ namespace BizHawk.Bizware.DirectX
{
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<RawJoystickState>();
#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<string, ObjectDataFormat>)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<GamePad> EnumerateDevices()
public static IEnumerable<DGamepad> EnumerateDevices()
{
lock (SyncObj)
{
@ -89,10 +109,10 @@ namespace BizHawk.Bizware.DirectX
// ********************************** 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 @@ namespace BizHawk.Bizware.DirectX
{
try
{
if (_joystick.Acquire().IsFailure)
if (_joystick.Acquire().Failure)
return;
}
catch
@ -114,24 +134,26 @@ namespace BizHawk.Bizware.DirectX
}
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<PropertyInfo> _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)));
}
/// <summary>FOR DEBUGGING ONLY</summary>
@ -156,8 +178,8 @@ namespace BizHawk.Bizware.DirectX
public int NumButtons { get; private set; }
private readonly List<string> _names = new List<string>();
private readonly List<Func<bool>> _actions = new List<Func<bool>>();
private readonly List<string> _names = new();
private readonly List<Func<bool>> _actions = new();
private void AddItem(string name, Func<bool> callback)
{
@ -226,22 +248,22 @@ namespace BizHawk.Bizware.DirectX
// 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 @@ namespace BizHawk.Bizware.DirectX
// 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);
}
}

View File

@ -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<RawKeyboardState>();
_keyboard.Properties.BufferSize = 8;
}
}
public static void Cleanup()
{
lock (_lockObj)
{
_keyboard?.Dispose();
_keyboard = null;
_directInput?.Dispose();
_directInput = null;
}
}
public static IEnumerable<KeyEvent> 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<KeyEvent>();
var eventList = new List<KeyEvent>();
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<DInputKey, DistinctKey> KeyEnumMap = new Dictionary<DInputKey, DistinctKey>
{
[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<uint, DInputKey> VKeyToDKeyMap = new Dictionary<uint, DInputKey>
{
[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,
};
}
}

View File

@ -5,8 +5,9 @@ using System.Collections.Generic;
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 @@ namespace BizHawk.Bizware.DirectX
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<string, IReadOnlyCollection<string>> 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<string?, bool, ClientInputFocus> handleButton, Action<string?, int> 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<KeyEvent> ProcessHostKeyboards() => KeyInput.Update(_config ?? throw new Exception(nameof(ProcessHostKeyboards) + " called before the global config was passed"))
public IEnumerable<KeyEvent> 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)

View File

@ -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<XGamepad> 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<XInputGetStateExProcDelegate>(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<XGamepad> 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<string> _names = new();
private readonly List<Func<bool>> _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<bool> pressed)
{
_names.Add(name);
_actions.Add(pressed);
NumButtons++;
}
public string ButtonName(int index) => _names[index];
public bool Pressed(int index) => _actions[index]();
/// <remarks><paramref name="left"/> and <paramref name="right"/> are in 0..<see cref="int.MaxValue"/></remarks>
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
}
}
}
}

View File

@ -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<KeyEvent> PendingEventList = new();
private static readonly List<KeyEvent> 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<KeyEvent> Update()
{
EventList.Clear();
lock (PendingEventList)
{
EventList.AddRange(PendingEventList);
PendingEventList.Clear();
}
return EventList;
}
}
}

View File

@ -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
{
/// <summary>
/// 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)
/// </summary>
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<string, IReadOnlyCollection<string>> GetHapticsChannels();
public abstract void ReInitGamepads(IntPtr mainFormHandle);
public abstract void PreprocessHostGamepads();
public abstract void ProcessHostGamepads(Action<string?, bool, ClientInputFocus> handleButton, Action<string?, int> handleAxis);
public virtual IEnumerable<KeyEvent> 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;
}
}

View File

@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using static SDL2.SDL;
namespace BizHawk.Bizware.Input
{
/// <summary>
/// SDL2 Gamepad Handler
/// </summary>
internal class SDL2Gamepad : IDisposable
{
// indexed by instance id
private static readonly Dictionary<int, SDL2Gamepad> Gamepads = new();
private readonly IntPtr Opaque;
/// <summary>Is an SDL_GameController rather than an SDL_Joystick</summary>
public readonly bool IsGameController;
/// <summary>Has rumble</summary>
public readonly bool HasRumble;
/// <summary>Contains name and delegate function for all buttons, hats and axis</summary>
public readonly IReadOnlyCollection<(string ButtonName, Func<bool> GetIsPressed)> ButtonGetters;
/// <summary>For use in keybind boxes</summary>
public string InputNamePrefix { get; private set; }
/// <summary>Device index in SDL</summary>
public int DeviceIndex { get; private set; }
/// <summary>Instance ID in SDL</summary>
public int InstanceID { get; }
/// <summary>Device name in SDL</summary>
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<SDL2Gamepad> EnumerateDevices()
=> Gamepads.Values;
private List<(string ButtonName, Func<bool> GetIsPressed)> CreateGameControllerButtonGetters()
{
List<(string ButtonName, Func<bool> 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<bool> GetIsPressed)> CreateJoystickButtonGetters()
{
List<(string ButtonName, Func<bool> 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;
}
/// <remarks><paramref name="left"/> and <paramref name="right"/> are in 0..<see cref="int.MaxValue"/></remarks>
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);
}
}
}

View File

@ -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<string> SDL2_HAPTIC_CHANNEL_NAMES = new[] { "Left", "Right" };
private IReadOnlyDictionary<string, int> _lastHapticsSnapshot = new Dictionary<string, int>();
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<string, IReadOnlyCollection<string>> 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<string?, bool, ClientInputFocus> handleButton, Action<string?, int> 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<KeyEvent> ProcessHostKeyboards()
{
return _isInit
? base.ProcessHostKeyboards()
: Enumerable.Empty<KeyEvent>();
}
public override void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot)
=> _lastHapticsSnapshot = hapticsSnapshot.ToDictionary(tuple => tuple.Name, tuple => tuple.Strength);
}
}

View File

@ -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<KeyEvent> 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<KeyEvent>();
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<Keysym, DistinctKey> KeysymEnumMap = new Dictionary<Keysym, DistinctKey>
{
[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,
};
}
}

View File

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

View File

@ -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
{
/// <summary>
/// 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
/// </summary>
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<string, string> MapCodeToNative;
// public Dictionary<string, string> MapNativeToCode;
}
private class PipelineWrapper
{
public int pid;
public Shader FragmentShader, VertexShader;
public List<int> SamplerLocs;
}
/// <exception cref="InvalidOperationException">
/// <paramref name="required"/> is <see langword="true"/> and either <paramref name="vertexShader"/> or <paramref name="fragmentShader"/> is unavailable (their <see cref="Shader.Available"/> property is <see langword="false"/>), or
/// <c>glLinkProgram</c> call did not produce expected result
/// </exception>
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<AttributeInfo> attributes = new List<AttributeInfo>();
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<UniformInfo> uniforms = new List<UniformInfo>();
GL.GetProgram(pid,GetProgramParameterName.ActiveUniforms,out var nUniforms);
List<int> samplers = new List<int>();
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);
}
/// <exception cref="InvalidOperationException"><paramref name="pipeline"/>.<see cref="Pipeline.Available"/> is <see langword="false"/></exception>
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);
}
/// <exception cref="InvalidOperationException">framebuffer creation unsuccessful</exception>
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<int> _sVertexAttribEnables = new HashSet<int>();
private void PurgeStateCache()
{
sStateCurrentVertexLayout = null;
sStatePendingVertexLayout = null;
_sVertexAttribEnables.Clear();
sActiveTexture = -1;
}
} //class IGL_TK
}

View File

@ -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
{
/// <summary>
/// Modified OpenTK Gamepad Handler<br/>
/// The jump from OpenTK 1.x to 3.x broke the original <see cref="Joystick">OpenTK.Input.Joystick</see> implementation, but we gain <see cref="OpenTKGamePad">OpenTK.Input.GamePad</see> support on Unix. However, the gamepad auto-mapping is a little suspect, so we use both methods.<br/>
/// As a side-effect, it should make it easier to implement virtual→host haptics in the future.
/// </summary>
public class OTK_GamePad
{
/// <remarks>They don't have a way to query this for some reason. 4 is the minimum promised.</remarks>
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<OTK_GamePad> EnumerateDevices()
{
if (!initialized) return Enumerable.Empty<OTK_GamePad>();
lock (_syncObj) return Devices.Where(pad => pad is not null).ToList();
}
public static void UpdateAll()
{
static void DropAt(int index, IList<OTK_GamePad> 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);
}
}
}
}
/// <summary>The OpenTK device index</summary>
private readonly int _deviceIndex;
/// <summary>The object returned by <see cref="OpenTKGamePad.GetCapabilities"/></summary>
private readonly GamePadCapabilities? _gamePadCapabilities;
/// <summary>The object returned by <see cref="Joystick.GetCapabilities"/></summary>
private readonly JoystickCapabilities? _joystickCapabilities;
public readonly IReadOnlyCollection<string> HapticsChannels;
/// <summary>For use in keybind boxes</summary>
public readonly string InputNamePrefix;
/// <summary>as <see cref="InputNamePrefix"/> but without the trailing space</summary>
private readonly string InputNamePrefixShort;
/// <summary>Public check on whether mapped gamepad config is being used</summary>
public bool MappedGamePad => _gamePadCapabilities?.IsMapped == true;
/// <summary>GUID from <see cref="Joystick"/> (also used for DB) and name from <see cref="OpenTKGamePad"/> via DB</summary>
private readonly string MappingsDatabaseName;
/// <summary>Gamepad Device state information - updated constantly</summary>
private GamePadState state;
/// <summary>Joystick Device state information - updated constantly</summary>
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<bool> 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;
}
/// <summary>Contains name and delegate function for all buttons, hats and axis</summary>
public readonly IReadOnlyCollection<(string ButtonName, Func<bool> GetIsPressed)> ButtonGetters;
/// <remarks><paramref name="left"/> and <paramref name="right"/> are in 0..<see cref="int.MaxValue"/></remarks>
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));
}
}
}

View File

@ -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<DistinctKey> KeyEnumMap = new List<DistinctKey>
{
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<KeyEvent> 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<KeyEvent>();
}
var lastState = _kbState;
_kbState = newState;
if (lastState == _kbState) return Enumerable.Empty<KeyEvent>();
var eventList = new List<KeyEvent>();
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;
}
}
}

View File

@ -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<string> GetDeviceNames()
{
return !Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT")
? Enumerable.Empty<string>()
: 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<BufferPoolItem> _availableItems = new Stack<BufferPoolItem>();
private readonly Queue<BufferPoolItem> _obtainedItems = new Queue<BufferPoolItem>();
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();
}
}
}
}
}

View File

@ -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
}
/// <summary>no-op; this class' static ctor is guaranteed to be called exactly once if this is called at least once</summary>
public static void EnsureConfigurated() {}
}
}

View File

@ -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<string, int> _lastHapticsSnapshot = new Dictionary<string, int>();
public string Desc { get; } = "OpenTK 3";
public void DeInitAll() {}
public void FirstInitAll(IntPtr mainFormHandle)
{
OTK_Keyboard.Initialize();
OTK_GamePad.Initialize();
}
public IReadOnlyDictionary<string, IReadOnlyCollection<string>> 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<string?, bool, ClientInputFocus> handleButton, Action<string?, int> 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<KeyEvent> 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) {}
}
}

View File

@ -9,7 +9,6 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
<PackageReference Include="OpenTK" Version="3.3.3" PrivateAssets="all" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj" />
<EmbeddedResource Include="TestImages/**/*" />
</ItemGroup>

View File

@ -7,7 +7,7 @@ using System.Threading;
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 @@ namespace BizHawk.Bizware.Test
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

View File

@ -87,7 +87,7 @@ namespace BizHawk.Client.Common
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 @@ namespace BizHawk.Client.Common
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 @@ namespace BizHawk.Client.Common
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 @@ namespace BizHawk.Client.Common
}
}
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 @@ namespace BizHawk.Client.Common
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 @@ namespace BizHawk.Client.Common
{
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 @@ namespace BizHawk.Client.Common
_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;

View File

@ -214,7 +214,7 @@ namespace BizHawk.Client.Common
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 @@ namespace BizHawk.Client.Common
// 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; }

View File

@ -29,7 +29,7 @@
public enum EHostInputMethod
{
OpenTK = 0,
SDL2 = 0,
DirectInput = 1
}

View File

@ -6,6 +6,8 @@ using System.Collections.Generic;
namespace BizHawk.Client.Common
{
/// <remarks>this was easier than trying to make static classes instantiable...</remarks>
/// 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; }

View File

@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

View File

@ -20,7 +20,9 @@
<Reference Include="NLua, Version=1.4.1.0, Culture=neutral, PublicKeyToken=null, processorArchitecture=MSIL" SpecificVersion="false" HintPath="$(ProjectDir)../../References/NLua.dll" Private="true" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.DirectX/BizHawk.Bizware.DirectX.csproj" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.Graphics/BizHawk.Bizware.Graphics.csproj" />
<ProjectReference Include="$(ProjectDir)../BizHawk.WinForms.Controls/BizHawk.WinForms.Controls.csproj" />
<Compile Remove="Properties/Settings.Designer.cs" />
<Content Include="images/logo.ico" />

View File

@ -40,7 +40,7 @@ namespace BizHawk.Client.EmuHawk
// 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 @@ namespace BizHawk.Client.EmuHawk
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

View File

@ -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 @@ namespace BizHawk.Client.EmuHawk
{
}
private static readonly Lazy<GLManager> _lazyInstance = new Lazy<GLManager>(() => new GLManager());
private static readonly Lazy<GLManager> _lazyInstance = new(() => new());
public static GLManager Instance => _lazyInstance.Value;
@ -30,14 +29,13 @@ namespace BizHawk.Client.EmuHawk
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 @@ namespace BizHawk.Client.EmuHawk
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();
}
}
}

View File

@ -19,21 +19,18 @@ namespace BizHawk.Client.EmuHawk
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)

View File

@ -1,5 +1,5 @@
using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.OpenTK3;
using BizHawk.Bizware.Graphics;
namespace BizHawk.Client.EmuHawk
{

View File

@ -1,6 +1,6 @@
using System.Windows.Forms;
using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.OpenTK3;
using BizHawk.Bizware.Graphics;
namespace BizHawk.Client.EmuHawk
{

View File

@ -4,8 +4,7 @@ using System.Collections.Generic;
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 @@ namespace BizHawk.Client.EmuHawk
Adapter = _currentConfig.HostInputMethod switch
{
EHostInputMethod.OpenTK => new OpenTKInputAdapter(),
_ when OSTailoredCode.IsUnixHost => new OpenTKInputAdapter(),
EHostInputMethod.SDL2 => new SDL2InputAdapter(),
_ when OSTailoredCode.IsUnixHost => new SDL2InputAdapter(),
EHostInputMethod.DirectInput => new DirectInputAdapter(),
_ => throw new InvalidOperationException()
};

View File

@ -6,8 +6,7 @@ using System.IO;
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 @@ namespace BizHawk.Client.EmuHawk
{
static IEnumerable<string> 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<string>()
};

View File

@ -8,8 +8,7 @@ using System.Runtime.InteropServices;
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 @@ namespace BizHawk.Client.EmuHawk
{
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 @@ namespace BizHawk.Client.EmuHawk
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 @@ namespace BizHawk.Client.EmuHawk
}
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);
}
}

View File

@ -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 @@ namespace BizHawk.Client.EmuHawk
{
_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)
};

View File

@ -18,7 +18,7 @@ namespace BizHawk.Client.EmuHawk
public FeedbacksBindPanel(IDictionary<string, FeedbackBind> realConfigObject, ICollection<string>? 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)
{

View File

@ -62,7 +62,7 @@ namespace BizHawk.Client.EmuHawk
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 @@ namespace BizHawk.Client.EmuHawk
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))
{

View File

@ -43,7 +43,7 @@
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 @@
//
// 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 @@
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 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;

View File

@ -85,8 +85,8 @@ namespace BizHawk.Client.EmuHawk
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 @@ namespace BizHawk.Client.EmuHawk
_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 @@ namespace BizHawk.Client.EmuHawk
private void AutosaveSRAMRadioButton3_CheckedChanged(object sender, EventArgs e)
{
AutosaveSRAMtextBox.Enabled = AutosaveSRAMradioButton3.Checked;
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using BizHawk.Emulation.Cores.Nintendo.SNES;
using BizHawk.Emulation.Cores.Nintendo.SNES;
namespace BizHawk.Client.EmuHawk
{

View File

@ -2229,7 +2229,7 @@ namespace BizHawk.Client.EmuHawk
}
#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],

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;

View File

@ -160,6 +160,22 @@ namespace BizHawk.Common.CollectionExtensions
? countable.Count == n
: collection.Take(n + 1).Count() == n;
/// <summary>
/// Returns the value at <paramref name="key"/>.
/// If the key is not present, returns default(TValue).
/// backported from .NET Core 2.0
/// </summary>
public static TValue? GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
=> dictionary.TryGetValue(key, out var found) ? found : default;
/// <summary>
/// Returns the value at <paramref name="key"/>.
/// If the key is not present, returns <paramref name="defaultValue"/>.
/// backported from .NET Core 2.0
/// </summary>
public static TValue? GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
=> dictionary.TryGetValue(key, out var found) ? found : defaultValue;
/// <summary>
/// Returns the value at <paramref name="key"/>.
/// If the key is not present, stores the result of <c>defaultValue(key)</c> in the dict, and then returns that.

View File

@ -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);
}
}

View File

@ -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 @@ namespace BizHawk.Common
}
}
[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 @@ namespace BizHawk.Common
[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 @@ namespace BizHawk.Common
[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 @@ namespace BizHawk.Common
[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);
}
}

1
submodules/sameboy/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/obj