diff --git a/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
new file mode 100644
index 00000000..6619a500
--- /dev/null
+++ b/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net5.0
+ true
+
+
+
+
+
+
+
+
diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs b/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs
new file mode 100644
index 00000000..71ef414a
--- /dev/null
+++ b/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Audio.Backends.SDL2
+{
+ class SDL2AudioBuffer
+ {
+ public readonly ulong DriverIdentifier;
+ public readonly ulong SampleCount;
+ public ulong SamplePlayed;
+
+ public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount)
+ {
+ DriverIdentifier = driverIdentifier;
+ SampleCount = sampleCount;
+ SamplePlayed = 0;
+ }
+ }
+}
diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
new file mode 100644
index 00000000..07131d1d
--- /dev/null
+++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
@@ -0,0 +1,184 @@
+using Ryujinx.Audio.Backends.Common;
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Memory;
+using Ryujinx.SDL2.Common;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
+using static SDL2.SDL;
+
+namespace Ryujinx.Audio.Backends.SDL2
+{
+ public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
+ {
+ private object _lock = new object();
+
+ private ManualResetEvent _updateRequiredEvent;
+ private List _sessions;
+
+ public SDL2HardwareDeviceDriver()
+ {
+ _updateRequiredEvent = new ManualResetEvent(false);
+ _sessions = new List();
+
+ SDL2Driver.Instance.Initialize();
+ }
+
+ public static bool IsSupported => IsSupportedInternal();
+
+ private static bool IsSupportedInternal()
+ {
+ uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
+
+ if (device != 0)
+ {
+ SDL_CloseAudioDevice(device);
+ }
+
+ return device != 0;
+ }
+
+ public ManualResetEvent GetUpdateRequiredEvent()
+ {
+ return _updateRequiredEvent;
+ }
+
+ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
+ {
+ if (channelCount == 0)
+ {
+ channelCount = 2;
+ }
+
+ if (sampleRate == 0)
+ {
+ sampleRate = Constants.TargetSampleRate;
+ }
+
+ if (direction != Direction.Output)
+ {
+ throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
+ }
+
+ lock (_lock)
+ {
+ SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
+
+ _sessions.Add(session);
+
+ return session;
+ }
+ }
+
+ internal void Unregister(SDL2HardwareDeviceSession session)
+ {
+ lock (_lock)
+ {
+ _sessions.Remove(session);
+ }
+ }
+
+ private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
+ {
+ return new SDL_AudioSpec
+ {
+ channels = (byte)requestedChannelCount,
+ format = GetSDL2Format(requestedSampleFormat),
+ freq = (int)requestedSampleRate,
+ samples = (ushort)sampleCount
+ };
+ }
+
+ internal static ushort GetSDL2Format(SampleFormat format)
+ {
+ return format switch
+ {
+ SampleFormat.PcmInt8 => AUDIO_S8,
+ SampleFormat.PcmInt16 => AUDIO_S16,
+ SampleFormat.PcmInt32 => AUDIO_S32,
+ SampleFormat.PcmFloat => AUDIO_F32,
+ _ => throw new ArgumentException($"Unsupported sample format {format}"),
+ };
+ }
+
+ // TODO: Fix this in SDL2-CS.
+ [DllImport("SDL2", EntryPoint = "SDL_OpenAudioDevice", CallingConvention = CallingConvention.Cdecl)]
+ private static extern uint SDL_OpenAudioDevice_Workaround(
+ IntPtr name,
+ int iscapture,
+ ref SDL_AudioSpec desired,
+ out SDL_AudioSpec obtained,
+ uint allowed_changes
+ );
+
+ internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback)
+ {
+ SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount);
+
+ desired.callback = callback;
+
+ uint device = SDL_OpenAudioDevice_Workaround(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
+
+ if (device == 0)
+ {
+ return 0;
+ }
+
+ bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
+
+ if (!isValid)
+ {
+ SDL_CloseAudioDevice(device);
+
+ return 0;
+ }
+
+ return device;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ while (_sessions.Count > 0)
+ {
+ SDL2HardwareDeviceSession session = _sessions[_sessions.Count - 1];
+
+ session.Dispose();
+ }
+
+ SDL2Driver.Instance.Dispose();
+ }
+ }
+
+ public bool SupportsSampleRate(uint sampleRate)
+ {
+ return true;
+ }
+
+ public bool SupportsSampleFormat(SampleFormat sampleFormat)
+ {
+ return sampleFormat != SampleFormat.PcmInt24;
+ }
+
+ public bool SupportsChannelCount(uint channelCount)
+ {
+ return true;
+ }
+
+ public bool SupportsDirection(Direction direction)
+ {
+ // TODO: add direction input when supported.
+ return direction == Direction.Output;
+ }
+ }
+}
diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
new file mode 100644
index 00000000..344dd9b6
--- /dev/null
+++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
@@ -0,0 +1,223 @@
+using Ryujinx.Audio.Backends.Common;
+using Ryujinx.Audio.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using static SDL2.SDL;
+
+namespace Ryujinx.Audio.Backends.SDL2
+{
+ class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase
+ {
+ private SDL2HardwareDeviceDriver _driver;
+ private ConcurrentQueue _queuedBuffers;
+ private DynamicRingBuffer _ringBuffer;
+ private ulong _playedSampleCount;
+ private ManualResetEvent _updateRequiredEvent;
+ private uint _outputStream;
+ private SDL_AudioCallback _callbackDelegate;
+ private int _bytesPerFrame;
+ private uint _sampleCount;
+ private bool _started;
+ private float _volume;
+ private ushort _nativeSampleFormat;
+
+ public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+ {
+ _driver = driver;
+ _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
+ _queuedBuffers = new ConcurrentQueue();
+ _ringBuffer = new DynamicRingBuffer();
+ _callbackDelegate = Update;
+ _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
+ _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
+ _sampleCount = uint.MaxValue;
+ _started = false;
+ _volume = 1.0f;
+ }
+
+ private void EnsureAudioStreamSetup(AudioBuffer buffer)
+ {
+ bool needAudioSetup = _outputStream == 0 || ((uint)GetSampleCount(buffer) % _sampleCount) != 0;
+
+ if (needAudioSetup)
+ {
+ _sampleCount = Math.Max(Constants.TargetSampleCount, (uint)GetSampleCount(buffer));
+
+ uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
+
+ if (newOutputStream == 0)
+ {
+ // No stream in place, this is unexpected.
+ throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
+ }
+ else
+ {
+ if (_outputStream != 0)
+ {
+ SDL_CloseAudioDevice(_outputStream);
+ }
+
+ _outputStream = newOutputStream;
+
+ SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1);
+
+ Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}");
+ }
+ }
+ }
+
+ // TODO: Add this variant with pointer to SDL2-CS.
+ [DllImport("SDL2", EntryPoint = "SDL_MixAudioFormat", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe uint SDL_MixAudioFormat(IntPtr dst, IntPtr src, ushort format, uint len, int volume);
+
+ private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength)
+ {
+ Span streamSpan = new Span((void*)stream, streamLength);
+
+ int maxFrameCount = (int)GetSampleCount(streamLength);
+ int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
+
+ int frameCount = Math.Min(bufferedFrames, maxFrameCount);
+
+ if (frameCount == 0)
+ {
+ // SDL2 left the responsability to the user to clear the buffer.
+ streamSpan.Fill(0);
+
+ return;
+ }
+
+ byte[] samples = new byte[frameCount * _bytesPerFrame];
+
+ _ringBuffer.Read(samples, 0, samples.Length);
+
+ samples.AsSpan().CopyTo(streamSpan);
+ streamSpan.Slice(samples.Length).Fill(0);
+
+ // Apply volume to written data
+ SDL_MixAudioFormat(stream, stream, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
+
+ ulong sampleCount = GetSampleCount(samples.Length);
+
+ ulong availaibleSampleCount = sampleCount;
+
+ bool needUpdate = false;
+
+ while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
+ {
+ ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
+ ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
+
+ ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
+ availaibleSampleCount -= playedAudioBufferSampleCount;
+
+ if (currentSamplePlayed == driverBuffer.SampleCount)
+ {
+ _queuedBuffers.TryDequeue(out _);
+
+ needUpdate = true;
+ }
+
+ Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount);
+ }
+
+ // Notify the output if needed.
+ if (needUpdate)
+ {
+ _updateRequiredEvent.Set();
+ }
+ }
+
+ public override ulong GetPlayedSampleCount()
+ {
+ return Interlocked.Read(ref _playedSampleCount);
+ }
+
+ public override float GetVolume()
+ {
+ return _volume;
+ }
+
+ public override void PrepareToClose() { }
+
+ public override void QueueBuffer(AudioBuffer buffer)
+ {
+ EnsureAudioStreamSetup(buffer);
+
+ SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
+
+ _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
+
+ _queuedBuffers.Enqueue(driverBuffer);
+ }
+
+ public override void SetVolume(float volume)
+ {
+ _volume = volume;
+ }
+
+ public override void Start()
+ {
+ if (!_started)
+ {
+ if (_outputStream != 0)
+ {
+ SDL_PauseAudioDevice(_outputStream, 0);
+ }
+
+ _started = true;
+ }
+ }
+
+ public override void Stop()
+ {
+ if (_started)
+ {
+ if (_outputStream != 0)
+ {
+ SDL_PauseAudioDevice(_outputStream, 1);
+ }
+
+ _started = false;
+ }
+ }
+
+ public override void UnregisterBuffer(AudioBuffer buffer) { }
+
+ public override bool WasBufferFullyConsumed(AudioBuffer buffer)
+ {
+ if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
+ {
+ return true;
+ }
+
+ return driverBuffer.DriverIdentifier != buffer.DataPointer;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ PrepareToClose();
+ Stop();
+
+ if (_outputStream != 0)
+ {
+ SDL_CloseAudioDevice(_outputStream);
+ }
+
+ _driver.Unregister(this);
+ }
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs b/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs
index 1e000e4c..f1f0039c 100644
--- a/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs
+++ b/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs
@@ -18,6 +18,7 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Memory;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Backends.Common
{
@@ -52,7 +53,13 @@ namespace Ryujinx.Audio.Backends.Common
protected ulong GetSampleCount(AudioBuffer buffer)
{
- return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, (int)buffer.DataSize);
+ return GetSampleCount((int)buffer.DataSize);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected ulong GetSampleCount(int dataSize)
+ {
+ return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, dataSize);
}
public abstract void Dispose();
diff --git a/Ryujinx.Common/Configuration/AudioBackend.cs b/Ryujinx.Common/Configuration/AudioBackend.cs
index 28233354..e42df039 100644
--- a/Ryujinx.Common/Configuration/AudioBackend.cs
+++ b/Ryujinx.Common/Configuration/AudioBackend.cs
@@ -4,6 +4,7 @@
{
Dummy,
OpenAl,
- SoundIo
+ SoundIo,
+ SDL2
}
}
\ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
index 092bb9dd..be9c6864 100644
--- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
///
/// The current version of the file format
///
- public const int CurrentVersion = 24;
+ public const int CurrentVersion = 25;
public int Version { get; set; }
diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs
index e65bcfcb..9ea5c282 100644
--- a/Ryujinx.Common/Configuration/ConfigurationState.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationState.cs
@@ -803,6 +803,13 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
+ if (configurationFileFormat.Version < 25)
+ {
+ Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 25.");
+
+ configurationFileUpdated = true;
+ }
+
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
diff --git a/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
index cee18996..2d61dfb8 100644
--- a/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
+++ b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
@@ -5,12 +5,9 @@
true
-
-
-
-
+
diff --git a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
index 62098383..927d7fe6 100644
--- a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
+++ b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
@@ -1,4 +1,5 @@
-using System;
+using Ryujinx.SDL2.Common;
+using System;
using System.Collections.Generic;
using static SDL2.SDL;
diff --git a/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj b/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
new file mode 100644
index 00000000..a35f8743
--- /dev/null
+++ b/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net5.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ryujinx.Input.SDL2/SDL2Driver.cs b/Ryujinx.SDL2.Common/SDL2Driver.cs
similarity index 97%
rename from Ryujinx.Input.SDL2/SDL2Driver.cs
rename to Ryujinx.SDL2.Common/SDL2Driver.cs
index f77bb1d5..edd634ee 100644
--- a/Ryujinx.Input.SDL2/SDL2Driver.cs
+++ b/Ryujinx.SDL2.Common/SDL2Driver.cs
@@ -4,9 +4,9 @@ using System.IO;
using System.Threading;
using static SDL2.SDL;
-namespace Ryujinx.Input.SDL2
+namespace Ryujinx.SDL2.Common
{
- class SDL2Driver : IDisposable
+ public class SDL2Driver : IDisposable
{
private static SDL2Driver _instance;
@@ -25,7 +25,7 @@ namespace Ryujinx.Input.SDL2
}
}
- private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK;
+ private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO;
private bool _isRunning;
private uint _refereceCount;
diff --git a/Ryujinx.sln b/Ryujinx.sln
index bd00f000..f4eec573 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -59,9 +59,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Open
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input", "Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input.SDL2", "Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.SDL2.Common", "Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Audio.Backends.SDL2", "Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -177,6 +181,14 @@ Global
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 6a441a61..1cd9595b 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -26,6 +26,7 @@
+
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 56dcf3eb..a2a00992 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -5,6 +5,7 @@ using LibHac.Common;
using LibHac.Ns;
using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.Audio.Backends.OpenAL;
+using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Configuration;
@@ -327,7 +328,18 @@ namespace Ryujinx.Ui
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
- if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
+ if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2)
+ {
+ if (SDL2HardwareDeviceDriver.IsSupported)
+ {
+ deviceDriver = new SDL2HardwareDeviceDriver();
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.Audio, "SDL2 audio is not supported, falling back to dummy audio out.");
+ }
+ }
+ else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
{
if (SoundIoHardwareDeviceDriver.IsSupported)
{
diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs
index be9dc271..94afb242 100644
--- a/Ryujinx/Ui/Windows/SettingsWindow.cs
+++ b/Ryujinx/Ui/Windows/SettingsWindow.cs
@@ -1,5 +1,6 @@
using Gtk;
using Ryujinx.Audio.Backends.OpenAL;
+using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
@@ -302,6 +303,7 @@ namespace Ryujinx.Ui.Windows
TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl);
TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo);
+ TreeIter sdl2Iter = _audioBackendStore.AppendValues("SDL2", AudioBackend.SDL2);
TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy);
_audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore);
@@ -316,6 +318,9 @@ namespace Ryujinx.Ui.Windows
case AudioBackend.SoundIo:
_audioBackendSelect.SetActiveIter(soundIoIter);
break;
+ case AudioBackend.SDL2:
+ _audioBackendSelect.SetActiveIter(sdl2Iter);
+ break;
case AudioBackend.Dummy:
_audioBackendSelect.SetActiveIter(dummyIter);
break;
@@ -328,11 +333,13 @@ namespace Ryujinx.Ui.Windows
bool openAlIsSupported = false;
bool soundIoIsSupported = false;
+ bool sdl2IsSupported = false;
Task.Run(() =>
{
openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported;
soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported;
+ sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported;
});
// This function runs whenever the dropdown is opened
@@ -342,6 +349,7 @@ namespace Ryujinx.Ui.Windows
{
AudioBackend.OpenAl => openAlIsSupported,
AudioBackend.SoundIo => soundIoIsSupported,
+ AudioBackend.SDL2 => sdl2IsSupported,
AudioBackend.Dummy => true,
_ => throw new ArgumentOutOfRangeException()
};